AI Agents
An AI agent is an LLM that can observe, reason, act, and reflect in a loop — not just answer a single question. Agents use tools, maintain memory, plan multi-step tasks, and self-correct. This sheet covers what agents are, how they work, and how to build them reliably.
Table of Contents
Installation / Setup
pip install langgraph langchain-openai # or: uv add langgraph langchain-openai
Core Syntax
What is an agent?
| Component |
Role |
| LLM |
The "brain" — reasons about what to do |
| Tools |
Actions the agent can take (API calls, code exec, search) |
| Memory |
Context from past interactions (conversation, learned facts) |
| Planning |
Strategy for breaking down tasks (ReAct, plan-and-execute) |
| Environment |
External world the agent operates in |
The agent loop
graph TD
A[User Input] --> B{Observe}
B --> C[Reason / Plan]
C --> D{Need tool?}
D -->|Yes| E[Select Tool]
E --> F[Execute Tool]
F --> G[Observe Result]
G --> C
D -->|No| H[Generate Answer]
H --> I[Return to User]
style A fill:#4CAF50,color:#fff
style I fill:#2196F3,color:#fff
style D fill:#FF9800,color:#fff
Agent types
| Type |
Description |
When to use |
| ReAct |
Reason → Act → Observe loop |
Most common; general-purpose |
| Plan-and-execute |
Plan all steps, then execute |
Complex multi-step tasks |
| Reflection |
Generate → critique → improve |
High-quality output needed |
| Tool-use |
Single tool call, no loop |
Simple tool invocations |
| Multi-agent |
Multiple agents collaborate |
Specialized sub-tasks |
ReAct pattern (most common)
graph LR
A[Thought] --> B[Action]
B --> C[Observation]
C --> A
A --> D[Final Answer]
Thought: I need to find the current stock price of AAPL.
Action: lookup_stock(ticker="AAPL")
Observation: AAPL is trading at $195.50
Thought: I have the price. I can answer now.
Final Answer: AAPL is currently trading at $195.50.
Common Operations
Minimal agent (LangGraph)
from langgraph.prebuilt import create_react_agent
from langchain_openai import ChatOpenAI
from langchain_core.tools import tool
@tool
def search(query: str) -> str:
"""Search the web for information."""
return f"Results for '{query}': ..."
@tool
def calculator(expression: str) -> str:
"""Evaluate a math expression."""
return str(eval(expression))
llm = ChatOpenAI(model="gpt-4o")
agent = create_react_agent(llm, [search, calculator])
result = agent.invoke({"messages": [("human", "What's 15% of AAPL's price?")]})
print(result["messages"][-1].content)
Agent with custom loop
from langchain_core.messages import HumanMessage, AIMessage, ToolMessage
def agent_loop(query, llm, tools, max_steps=10):
messages = [HumanMessage(content=query)]
tool_map = {t.name: t for t in tools}
for step in range(max_steps):
response = llm.bind_tools(tools).invoke(messages)
messages.append(response)
if not response.tool_calls:
return response.content # final answer
for tc in response.tool_calls:
result = tool_map[tc["name"]].invoke(tc["args"])
messages.append(ToolMessage(content=str(result), tool_call_id=tc["id"]))
return "Max steps reached"
Observation management
# truncate long tool outputs to prevent context overflow
def truncate_observation(result, max_chars=2000):
if len(result) > max_chars:
return result[:max_chars] + "\n... [truncated]"
return result
Practical Examples
Example 1 — Research agent
from langgraph.prebuilt import create_react_agent
from langchain_openai import ChatOpenAI
from langchain_core.tools import tool
@tool
def web_search(query: str) -> str:
"""Search the web for current information."""
# in production, use a real search API
return f"Search results for: {query}"
@tool
def read_file(path: str) -> str:
"""Read a file from the local filesystem."""
with open(path) as f:
return f.read()
agent = create_react_agent(
ChatOpenAI(model="gpt-4o"),
[web_search, read_file],
state_modifier="You are a research assistant. Use tools to find and analyze information."
)
result = agent.invoke({"messages": [("human", "Summarize the latest AI trends from the news")]})
Example 2 — Code-writing agent
@tool
def run_python(code: str) -> str:
"""Execute Python code and return the output."""
import io, contextlib
f = io.StringIO()
with contextlib.redirect_stdout(f):
exec(code)
return f.getvalue()
agent = create_react_agent(
ChatOpenAI(model="gpt-4o"),
[run_python],
state_modifier="You write and test Python code. Always run your code to verify."
)
Example 3 — Reflection agent
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4o")
def reflection_generate(task, iterations=3):
draft = llm.invoke(f"Write a response to: {task}").content
for i in range(iterations):
critique = llm.invoke(
f"Critically review this response. What's wrong?\n\n{draft}"
).content
draft = llm.invoke(
f"Improve this response based on feedback:\n\nFeedback: {critique}\n\nDraft: {draft}"
).content
return draft
Common Patterns
Pattern: guardrails
def safe_agent(query, llm, tools, max_steps=5):
"""Agent with safety limits."""
messages = [HumanMessage(content=query)]
tool_map = {t.name: t for t in tools}
for step in range(max_steps):
response = llm.bind_tools(tools).invoke(messages)
messages.append(response)
if not response.tool_calls:
return response.content
for tc in response.tool_calls:
if tc["name"] not in tool_map:
messages.append(ToolMessage(content="Tool not allowed", tool_call_id=tc["id"]))
continue
result = tool_map[tc["name"]].invoke(tc["args"])
messages.append(ToolMessage(content=str(result)[:2000], tool_call_id=tc["id"]))
return "Task incomplete — too many steps"
Pattern: state persistence
# save agent state across runs
import json
def run_agent_with_state(agent, query, state_file="agent_state.json"):
# load previous state
state = json.load(open(state_file)) if os.path.exists(state_file) else {"messages": []}
state["messages"].append({"role": "user", "content": query})
result = agent.invoke({"messages": state["messages"]})
# save state
state["messages"] = [{"type": m.type, "content": m.content} for m in result["messages"]]
json.dump(state, open(state_file, "w"))
return result["messages"][-1].content
Best Practices
- Start simple — single agent with a few tools before building multi-agent systems.
- Set max_steps — prevent infinite loops (5–10 for most tasks).
- Truncate tool outputs — long outputs waste context and confuse the LLM.
- Use structured tool descriptions — the LLM chooses tools based on descriptions.
- Log everything — agent reasoning is opaque; logs help debug.
- Add human-in-the-loop for high-stakes actions (deployments, payments).
- Test with fixed seeds — agents are non-deterministic; test with
temperature=0.
- Plan before coding — sketch the agent loop on paper first.
Common Mistakes
| Mistake |
Fix |
| No max_steps limit |
Add a step limit to prevent infinite loops |
| Tools with vague descriptions |
Write clear, specific tool descriptions |
| Not truncating tool outputs |
Cap at 2000 chars to preserve context |
| Over-engineering early |
Start with single agent; add complexity only when needed |
| Ignoring failure modes |
Add retry logic and error handling |
| No human-in-the-loop for risky actions |
Add approval gates for destructive operations |
| Trusting agent output blindly |
Validate critical outputs before acting |
Troubleshooting
| Symptom |
Cause / Fix |
| Agent loops forever |
Missing max_steps or stuck in tool call cycle |
| Agent ignores tools |
Tool descriptions are unclear — rewrite them |
| Agent gives wrong answers |
Add reflection step; reduce temperature |
| Context length exceeded |
Truncate tool outputs; reduce history |
| Agent can't find information |
Tools are limited — add more tools or RAG |
| Slow agent |
Reduce steps; use faster model; cache tool calls |
Official Documentation
Last Updated
2026-09-06 — Initial version.