[Ch 1] Introduction to AI Agents — Why Do We Need Them?
Before you write a single line of LangGraph, you need a clear mental model of what an AI agent actually is — and more importantly, what it isn’t. This chapter builds that foundation.
What Is an AI Agent?
An AI agent is a system that uses a language model to decide which actions to take, executes those actions (via tools), observes the results, and then decides what to do next — repeating this cycle until a goal is reached.
The key word is decide. Unlike a simple LLM call that takes input and produces output in one shot, an agent has a control loop. It can take multiple steps, use external tools, and adapt based on intermediate results.
The Spectrum: Chatbot → Pipeline → Agent
Most AI systems fall somewhere on this spectrum:
| Type | Control | Steps | Tools | Example |
|---|---|---|---|---|
| Chatbot | Static | 1 | None | FAQ bot, direct LLM reply |
| LLM Pipeline | Fixed | N (predetermined) | Optional | Summarize → Translate → Format |
| AI Agent | Dynamic | N (decided at runtime) | Yes | Research → Plan → Write → Verify |
A chatbot is a single LLM call: user sends a message, LLM replies. It has no memory between sessions and no ability to use external tools.
An LLM pipeline chains multiple LLM calls in a predetermined order. The steps are fixed: you always summarize, then translate, then format. There’s no branching — the flow doesn’t change based on what the model produces.
An AI agent decides its own next step based on the current state. It might call a search tool, decide the result is insufficient, call it again with a refined query, then write an answer. The number and order of steps are not known in advance.
The Agent Loop (ReAct)
The dominant pattern for AI agents is ReAct (Reason + Act), introduced by Yao et al. (2022). The agent alternates between two phases:
- Reason — the LLM thinks about what to do given the current context
- Act — the agent executes an action (tool call) and receives an observation
This cycle repeats until the LLM decides it has enough information to produce a final answer.
The loop terminates when the LLM produces a regular message (no tool call). In practice this means the LLM’s output is inspected after each step: if it contains a tool call, the system runs it and feeds the result back; if not, the loop ends.
Real-World Use Cases
Agents shine when a task requires dynamic decision-making with external information or actions:
| Use Case | Why It Needs an Agent |
|---|---|
| Automated report generation | Must retrieve data, validate it, format by section — each step depends on previous |
| Code review assistant | Reads a PR, calls static analysis tools, queries docs, writes structured feedback |
| Presentation builder | Parses user intent, generates slide content, creates charts, assembles file |
| Test case generator | Understands spec, identifies scenarios, generates cases, checks for coverage |
| Customer support | Looks up account info, checks policy docs, escalates if needed |
In every case, the number of steps is not fixed in advance. The agent decides what to do based on what it finds.
When NOT to Use an Agent
This is just as important.
Use a simple LLM pipeline when:
- The task has a fixed, known structure (always: extract → classify → output)
- Latency is critical — each agent step adds 1–5 seconds
- The task is well-defined enough that a deterministic pipeline won’t miss edge cases
Use a basic LLM call when:
- You just need to summarize, translate, or classify a single piece of text
- There’s nothing to look up externally
- You want 100% predictable, testable behavior
Agents introduce non-determinism — the same input can take different paths. They are also slower and more expensive per task. Don’t reach for an agent when a two-step pipeline does the job.
⚠️ A common mistake: wrapping every feature in an agent “for flexibility.” The result is a slow, expensive, hard-to-test system. Start with the simplest thing that works.
The Agent Loop in Plain Python
Before introducing any framework, here’s what the agent loop looks like as pure Python pseudocode. This will help you understand exactly what LangGraph is doing for you later.
# agent_loop.py — Plain Python pseudo-code (no framework)
import os
import json
from openai import OpenAI
client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY"))
# --- Tool registry ---
def search_web(query: str) -> str:
"""Simulate a web search (replace with real implementation)."""
return f"[Search results for '{query}': ...]"
def calculate(expression: str) -> str:
"""Evaluate a math expression safely."""
try:
result = eval(expression, {"__builtins__": {}})
return str(result)
except Exception as e:
return f"Error: {e}"
TOOLS = {
"search_web": search_web,
"calculate": calculate,
}
# OpenAI-format tool schemas
TOOL_SCHEMAS = [
{
"type": "function",
"function": {
"name": "search_web",
"description": "Search the web for current information.",
"parameters": {
"type": "object",
"properties": {"query": {"type": "string"}},
"required": ["query"],
},
},
},
{
"type": "function",
"function": {
"name": "calculate",
"description": "Evaluate a mathematical expression.",
"parameters": {
"type": "object",
"properties": {"expression": {"type": "string"}},
"required": ["expression"],
},
},
},
]
# --- The agent loop ---
def run_agent(user_input: str, max_steps: int = 10) -> str:
messages = [
{"role": "system", "content": "You are a helpful assistant. Use tools when needed."},
{"role": "user", "content": user_input},
]
for step in range(max_steps):
print(f"\n[Step {step + 1}] Calling LLM...")
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=messages,
tools=TOOL_SCHEMAS,
tool_choice="auto",
)
message = response.choices[0].message
# No tool call → agent is done, return final answer
if not message.tool_calls:
print("[Step] Final answer reached.")
return message.content
# Execute each requested tool call
messages.append(message) # add assistant message with tool_calls
for tool_call in message.tool_calls:
fn_name = tool_call.function.name
fn_args = json.loads(tool_call.function.arguments)
print(f"[Step] Calling tool: {fn_name}({fn_args})")
result = TOOLS[fn_name](**fn_args)
print(f"[Step] Tool result: {result[:100]}...")
# Add the observation back into the conversation
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": result,
})
return "Max steps reached without a final answer."
if __name__ == "__main__":
answer = run_agent("What is the square root of the number of days in a leap year?")
print(f"\nFinal Answer: {answer}")
What this shows:
- The conversation history (
messages) is the agent’s working memory - The loop continues as long as the model keeps making tool calls
- Each tool result is appended as a
"role": "tool"message before the next LLM call max_stepsis a safety guard against infinite loops
This is essentially what LangGraph automates — the graph manages the loop, the state replaces the raw messages list, and nodes replace the explicit tool execution code.
.env.example
# .env.example
OPENAI_API_KEY=your-api-key-here
💡 Using Ollama instead? Replace the
OpenAIclient with:from openai import OpenAI client = OpenAI(base_url="http://localhost:11434/v1", api_key="ollama") # Use model="llama3.2" or any model you have pulledMake sure Ollama is running (
ollama serve) and the model supports tool use.
Summary
| Concept | Key Takeaway |
|---|---|
| AI Agent | An LLM in a control loop that decides actions dynamically |
| ReAct | Alternate between Reasoning (LLM) and Acting (tools) |
| When to use | Dynamic tasks requiring external info or multi-step decisions |
| When NOT to use | Fixed pipelines, latency-sensitive tasks, simple transformations |
| The loop | LLM call → tool call → observation → repeat until final answer |
In the next chapter, we’ll break down the four core components of an agent system and introduce one of the most important (and most overlooked) concepts in production AI: Context Engineering.
← Ch 0: Series Overview | Ch 2: Components & Context Engineering →
