Module 3: Agents with create_agent
Introduction: From Manual Tools to Autonomous Agents
Overview
In Module 2 you built the tool execution loop by hand: the user asks, the model decides which tool to call, you execute the tool, wrap the result in a ToolMessage, hand it back to the model, and the model produces the final answer. If it needed more data, you ran the whole cycle again inside a while loop.
It worked. But every time you wanted an assistant with tools, you had to write the same boilerplate: the tool_map, the while loop, the ToolMessage handling, the max_rounds safety net. For one tool that's manageable; for a research agent that chains five tools, it turns tedious and error-prone.
create_agent solves this. One line replaces the entire manual loop: you give it a model and a list of tools, and you get an agent that runs the full cycle on its own. The model reasons about what to do, calls tools, observes results, and repeats until it has enough information to answer. This pattern is called ReAct (Reason + Act), and it's the foundation of modern LLM agents.
Where are we in the guide?
This is Module 3 of LangChain & LangGraph: From Chains to Agents. It's the last module of Block 1 (LangChain Core) before we move on to Middleware and Customization.
Block 1: LangChain Core (Modules 1-4) ← YOU ARE HERE (Module 3)
Block 2: LangGraph Fundamentals (Modules 5-7)
Block 3: Advanced LangGraph (Modules 8-10)
Block 4: Production (Modules 11-12)
Your progress in Block 1:
Module 1: Models and Providers ✅ Done
│
▼
Module 2: Tools and Tool Calling ✅ Done
│
▼
Module 3: Agents (create_agent) ← YOU ARE HERE
│
▼
Module 4: Middleware and Customization 🔒 Next
In Module 1 you learned to connect to models. In Module 2 you gave them tools and wrote the manual loop to run them. Now you're going to automate that loop: the agent takes care of the entire reasoning-and-execution cycle.
The bridge: from manual loop to autonomous agent
What you already know
In Module 2 you implemented this:
from dotenv import load_dotenv
load_dotenv()
from langchain.chat_models import init_chat_model
from langchain_core.tools import tool
from langchain_core.messages import HumanMessage, ToolMessage
@tool
def search(query: str) -> str:
"""Search the web for information."""
return f"Results for '{query}': LangChain is a framework for LLMs."
tools = [search]
tool_map = {t.name: t for t in tools}
model = init_chat_model("openai:gpt-4.1-mini")
model_with_tools = model.bind_tools(tools)
messages = [HumanMessage(content="What is LangChain?")]
for _ in range(5):
response = model_with_tools.invoke(messages)
messages.append(response)
if not response.tool_calls:
break
for tc in response.tool_calls:
try:
result = str(tool_map[tc["name"]].invoke(tc["args"]))
except Exception as e:
result = f"Error: {e}"
messages.append(ToolMessage(content=result, tool_call_id=tc["id"]))
print(response.content)
# LangChain is a framework for building applications with LLMs.
That's ~20 lines of boilerplate for every assistant you want to build. The tool_map, the for loop, the try/except, the ToolMessage, the max_rounds... all of it repeats every single time.
What you'll learn here
With create_agent, the same result in 5 lines:
from dotenv import load_dotenv
load_dotenv()
from langchain.agents import create_agent
from langchain_core.tools import tool
@tool
def search(query: str) -> str:
"""Search the web for information."""
return f"Results for '{query}': LangChain is a framework for LLMs."
agent = create_agent("openai:gpt-4.1-mini", tools=[search])
result = agent.invoke({"messages": [("user", "What is LangChain?")]})
print(result["messages"][-1].content)
# LangChain is a framework for building applications with LLMs.
No tool_map. No while loop. No manual ToolMessage. No max_rounds. The agent handles it all internally.
Why agents: the problem with the manual loop
The manual loop works for simple cases, but it runs into real problems in complex scenarios:
| Problem | Manual loop | Agent |
|---|---|---|
| Repetitive boilerplate | 15-20 lines for every assistant | 2-3 lines |
| Complex multi-round | You have to manage the while and max_rounds | Automatic with recursion_limit |
| Error handling | Manual try/except on every tool call | Built in (returns the error as a ToolMessage) |
| Parallel tool calls | You have to iterate over tool_calls | Handled internally |
| Streaming | Needs significant extra logic | agent.stream() built in |
| Persistence | Not available without extra code | checkpointer as a parameter |
The manual loop is valuable for understanding how tool calling works. The agent is what you use to build real systems.
An analogy: the dispatcher
In the manual loop, you are the dispatcher. The model tells you "I need to call search", you run search, hand back the result, and ask "anything else?". You're the middleman wiring each piece together.
With an agent, the model is the dispatcher. It takes the question, decides which tools to call, runs the full cycle, and hands you the final answer. You just define the tools and ask the question — the agent takes care of the rest.
It's the difference between driving stick and driving automatic. Both get you there, but one makes you handle every step of the process.
The ReAct pattern: Reason + Act
create_agent agents use the ReAct pattern (Reasoning + Acting). The model alternates between two phases in a loop:
┌──────────────────────────────────────────────────────────┐
│ ReAct Loop │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Reason │────▶│ Act │────▶│ Observe │───┐ │
│ │ (Model) │ │ (Tool) │ │ (Result) │ │ │
│ └──────────┘ └──────────┘ └──────────┘ │ │
│ ▲ │ │
│ └────────────────────────────────────────────┘ │
│ │
│ Stop condition: the model answers with no tool_calls │
└──────────────────────────────────────────────────────────┘
Step by step:
- Reason — The model looks at the question and decides what to do. "The user wants the weather in Madrid. I'm going to call
get_weather." - Act — The model emits a
tool_calland the agent runs the tool. - Observe — The tool's result gets appended as a
ToolMessageand the model receives it. - Repeat — The model decides whether it needs more information. If it does, back to Reason. If not, it writes the final answer.
The ReAct flow in action
User: "What's Japan's population and its GDP per capita?"
Round 1 — Reason:
"I need two data points: population and GDP. I'll look up both."
→ tool_call: search("Japan population 2024")
→ tool_call: search("Japan GDP per capita 2024")
Round 1 — Act:
→ search("Japan population 2024") = "125 million people"
→ search("Japan GDP per capita 2024") = "USD 33,800"
Round 1 — Observe:
The model receives both results.
Round 2 — Reason:
"I have everything I need. Time to answer."
→ No tool_calls → produces the final answer
Answer: "Japan has roughly 125 million people and a GDP per
capita of USD 33,800."
The model chose to run two searches in parallel (parallel tool calls), observed the results, and decided it had enough information to answer. All of that happened automatically inside the agent.
create_agent as a high-level abstraction
create_agent isn't magic — it's an abstraction over LangGraph. Under the hood it builds a graph with two nodes wired in a loop:
┌─────────────┐ tool_calls ┌─────────────┐
│ Model │─────────────────────────▶│ Tools │
│ Node │ │ Node │
│ │◀─────────────────────────│ │
└─────────────┘ ToolMessages └─────────────┘
│
│ no tool_calls
▼
[Answer]
- Model Node: Calls the LLM with the message list. If the response contains
tool_calls, it routes to the Tools Node. If not, it returns the answer (done). - Tools Node: Runs each tool call and appends the results as
ToolMessage. Back to the Model Node.
This is exactly the while loop you wrote by hand in Module 2, only packaged as a compiled LangGraph graph. Which means it inherits everything LangGraph can do: checkpointing, streaming, interrupts, and more.
You don't need to know LangGraph to use create_agent. But knowing it's a graph underneath explains why it takes parameters like checkpointer, interrupt_before, and recursion_limit.
What you'll master in this module
By the end of these 8 capsules, you'll be able to:
- ✅ Create agents with
create_agent(model, tools)in a single line - ✅ Understand the ReAct loop and its stop conditions
- ✅ Configure static and dynamic system prompts
- ✅ Manage agent state with
state_schema(TypedDict) - ✅ Stream the agent's reasoning process
- ✅ Get structured output from agents with
response_format - ✅ Translate legacy code (
AgentExecutor,LLMChain) to modern APIs
Module map
| Capsule | Topic | What you'll learn |
|---|---|---|
| 02 | create_agent and the ReAct loop | create_agent(model, tools), how it works internally, stop conditions, recursion_limit |
| 03 | Static and dynamic system prompts | system_prompt as a string/SystemMessage, @dynamic_prompt for contextual prompts |
| 04 | Agent state and memory | AgentState, state_schema with TypedDict, custom state, conversation history |
| 05 | Streaming agents | agent.stream() with stream_mode, processing chunks, streaming tool calls inside agents |
| 06 | Structured output in agents | response_format, ToolStrategy vs ProviderStrategy, structured_response |
| 07 | Legacy vs modern: API mapping | Equivalence table, why migrate, how to translate legacy code |
| 08 | Project: Research agent with tools | An agent that searches, extracts structured data, and writes a report |
Learning flow: First you'll master basic agent creation and understand the ReAct loop (02). Then you'll learn to shape its behavior with prompts (03) and custom state (04). Next you'll explore streaming (05) and structured output (06) for production. Finally, you'll learn to translate legacy code (07) and build the capstone project (08).
Connection to the project
This module's mini-project: Research Agent with Tools
In Capsule 08 you'll build a research agent that:
- Uses
create_agentwith 3+ tools (web search, data extraction, calculator) - Has a system prompt that shapes how it does research
- Streams the reasoning process — you watch the agent think and act in real time
- Produces a structured output as its final report (a Pydantic model with title, findings, sources, conclusion)
Every concept from capsules 02-07 comes together in this project.
Connection to the full guide
The agents you build here are the foundation for everything that follows:
- Module 4: The middleware system lets you intercept and change an agent's behavior without rewriting it. Want it to use a different model depending on complexity? Filter tools by permission? Middleware handles that.
- Modules 5-7: In LangGraph, you'll build workflows more complex than a single agent. But
create_agentremains your tool for 80% of cases. - Modules 8-10: Persistent memory, human-in-the-loop, and multi-agent. The
checkpointer,interrupt_before, andnameparameters ofcreate_agentare your entry point into those topics. - Module 11: Deep Agents push autonomy to the limit — planning, filesystem, subagents. Understanding
create_agentis a prerequisite for understanding what Deep Agents adds on top.
Limits: what this module does NOT cover
- ❌ Custom LangGraph workflows — Covered in Modules 5-7. Here you use
create_agentas an abstraction; you don't build graphs by hand. - ❌ Middleware and customization — Covered in Module 4. Here you configure agents with direct parameters; the middleware system comes later.
- ❌ Multi-agent systems — Covered in Module 10. Here you work with a single autonomous agent.
- ❌ Human-in-the-loop — Covered in Module 9. We'll mention
interrupt_before/interrupt_afterwithout going deep. - ❌ Persistent memory across sessions — Covered in Module 8. Here the conversation lives in memory for the duration of the session.
Technical setup
Prerequisites
Before you continue, make sure you have:
- ✅ Module 2 completed — you know how to create tools with
@tool, usebind_tools, and run the tool execution loop - ✅ Python 3.11+ installed
- ✅ At least one API key from a provider that supports tool calling (OpenAI or Anthropic recommended)
Installation
create_agent lives in langchain but uses langgraph internally to build the agent's graph. You need both packages:
pip install langchain langgraph langchain-openai python-dotenv
If you already had langchain and langchain-openai from Module 1, you just need to add langgraph:
pip install langgraph
Check that everything works
from dotenv import load_dotenv
load_dotenv()
from langchain.agents import create_agent
from langchain_core.tools import tool
@tool
def greet(name: str) -> str:
"""Greet a person by name."""
return f"Hello, {name}!"
agent = create_agent("openai:gpt-4.1-mini", tools=[greet])
result = agent.invoke({"messages": [("user", "Say hi to María")]})
print(result["messages"][-1].content)
# Expected output: Hello, María! (or something close)
If you see a response with the greeting in it, your setup is ready.
If something breaks:
| Error | Cause | Fix |
|---|---|---|
ImportError: cannot import name 'create_agent' | Old langchain version | pip install --upgrade langchain (you need v1.2+) |
ModuleNotFoundError: No module named 'langgraph' | langgraph not installed | pip install langgraph |
NotImplementedError: ... does not support tool calling | The model doesn't support tool calling | Use openai:gpt-4.1-mini, anthropic:claude-sonnet-4-20250514, or another model with support |
Signs you got it
By the end of this module, you'll know you succeeded if:
- ✅ You can create an agent with
create_agentand have it answer questions using tools - ✅ You understand the ReAct loop: reason → act → observe → repeat
- ✅ Your agent stops on its own once it has enough information
- ✅ You can configure system prompts and get structured output
- ✅ You know when to use
create_agentvs the manual loop vs a custom LangGraph - ✅ Your research project turns autonomous searches into a structured report
A look ahead: from the agent to middleware
In this module you'll build working agents with create_agent. But what happens when you need to customize the behavior without rewriting the agent?
In Module 4, you'll learn the middleware system — interceptors that modify the agent at specific points:
# Module 3: basic agent (what you'll learn here)
agent = create_agent(
"openai:gpt-4.1-mini",
tools=[search, calculator],
system_prompt="You are a research assistant."
)
# Module 4: agent with middleware (what comes next)
from langchain.agents.middleware import wrap_model_call, ModelRequest, ModelResponse
@wrap_model_call
def smart_routing(request: ModelRequest, handler) -> ModelResponse:
"""Use a cheap model for simple questions, a powerful one for complex ones."""
message_count = len(request.state["messages"])
if message_count > 10:
return handler(request.override(model=advanced_model))
return handler(request)
agent = create_agent(
"openai:gpt-4.1-mini",
tools=[search, calculator],
middleware=[smart_routing]
)
Middleware gives you control over the agent without touching its internal logic. It's like adding filters to a camera — the camera works exactly the same, but the photos come out different.
Summary
- In Module 2 you built the tool execution loop by hand — it worked, but it was tedious and repetitive
create_agentautomates the whole loop: you give it a model and tools, and the agent reasons, acts, observes, and repeats until it has the answer- The ReAct pattern (Reason + Act) is the foundation: the model alternates between reasoning about what to do and executing actions
create_agentis an abstraction over LangGraph — under the hood it builds a graph with a model node and a tools node in a loop- The key analogy: manual loop = you're the dispatcher; agent = the model is the dispatcher
create_agentinherits LangGraph's capabilities: checkpointing, streaming, interrupts, and more- This module covers agents with
create_agent; Modules 5-7 cover custom workflows with LangGraph for when you need more control - You need
langchain+langgraphinstalled (create_agent uses langgraph internally) - The capstone project is a research agent with web search, streaming, and structured output
Further reading
- LangChain Agents Documentation — Official guide to agents with create_agent
- create_agent API Reference — Full reference for parameters and types
- ReAct: Synergizing Reasoning and Acting in Language Models — The original ReAct paper (Yao et al., 2022)
- LangGraph Agents Overview — How create_agent is built on top of LangGraph
- Tool Calling — LangChain Docs — The conceptual basis for the tools the agent uses internally
- What's New in LangGraph v1 — Changes from create_react_agent to create_agent
Module 3 — LangChain & LangGraph: From Chains to Agents
Next capsule: create_agent and the ReAct Loop — you'll learn to create agents, understand how they work under the hood, and control their stop conditions.