create_agent and middleware
The prebuilt tool-calling loop, and the 1.0 mechanism for customising it without subclassing anything.
The front door
from langchain.agents import create_agent
agent = create_agent(
model="anthropic:claude-sonnet-4-6",
tools=[search, lookup_order],
)
agent.invoke({"messages": [
{"role": "user", "content": "Where is order 123?"}
]})That is a working agent. The model string is provider:model, so switching
providers is a string change rather than an import change.
create_agent returns a compiled graph. Everything from
Nodes, edges and routing applies — you
can stream it, checkpoint it, and inspect its state, because it is the same
runtime underneath.
Naming:
create_react_agentis the pre-1.0 name for this. Using it in conversation dates your knowledge to before October 2025.
The arguments worth knowing
| Argument | Does |
|---|---|
model |
"provider:model" or an instance |
tools |
callables or LangChain tools |
system_prompt |
shapes behaviour |
response_format |
a Pydantic model for typed output |
checkpointer |
persistence across turns |
middleware |
hooks into the loop |
state_schema |
extra state keys beyond messages |
context_schema |
per-run configuration |
response_format is the one people miss. Pass a Pydantic model and the final
answer is validated against it, which turns “parse the model’s prose” into a
typed object — see
Function Calling, Tool Use, and Structured Output.
from pydantic import BaseModel
class Answer(BaseModel):
order_id: str
status: str
eta_days: int
agent = create_agent(
model="anthropic:claude-sonnet-4-6",
tools=[lookup_order],
response_format=Answer,
)Middleware: the actual 1.0 idea
Before 1.0, customising the loop meant subclassing and overriding. That made every customisation a fork. Middleware makes them composable units that hook the loop at defined points.
The formula the docs use: agent = model + harness. Middleware is how you change the harness without rewriting it.
What people build with it:
| Concern | Middleware does |
|---|---|
| Summarisation | compact history before the call |
| Guardrails | inspect or block a tool call |
| Approval | pause before a risky action |
| Retry | catch a failure, try again |
| Steering | inject context per turn |
The value is that each is independent. Summarisation does not know about approval; both compose onto the same agent. Contrast the pre-1.0 world where adding the second meant editing the class that implemented the first.
agent = create_agent(
model="anthropic:claude-sonnet-4-6",
tools=[refund_order],
middleware=[summarisation, approval_gate],
)The interview point: middleware is where policy lives. Approval gates, PII redaction and budget checks belong in a hook that runs on every tool call, not in prompt text asking the model to behave. Policy in code at the boundary is enforceable; policy in a prompt is a suggestion. See Guardrails and output validation.
When to drop to StateGraph
create_agent is a good default and it is one shape: a loop that calls tools
until the model stops asking. Move to an explicit graph when:
- The flow has stages that are not a loop — ingest, then verify, then report, with different tools available at each.
- You need parallel branches with a merge.
- The routing is mostly deterministic and only one decision needs a model.
- You need state the agent loop does not model.
The migration is not a rewrite. create_agent produces a graph, so you can
start with it, hit its edges, and rebuild the parts you need explicitly while
keeping the same state and checkpointer.
Gotcha: reaching for
StateGraphfirst is the more common error. A hand-built graph that reimplements the tool loop is code you now maintain, and it will drift behind the prebuilt one. Start prebuilt.
Related
Interview angle 5
- “How do you build an agent in LangGraph?” -
create_agentfromlangchain.agentswith a model string and tools. It returns a compiled graph, so streaming, checkpointing and state inspection all work on it — it is not a separate abstraction. - “What is middleware?” - composable hooks into the agent loop, and the 1.0 replacement for subclassing. Summarisation, guardrails, approval and retry each become an independent unit instead of a fork of the loop class.
- “Where should approval logic live?” - in middleware, running on every tool call, not in prompt text. Policy in code at the boundary is enforceable; policy in a prompt is a suggestion the model can be talked out of.
- “When would you use StateGraph instead?” - when the flow has stages rather than a loop, needs parallel branches, or is mostly deterministic with one model decision. Start prebuilt though — a hand-built tool loop is maintenance you took on for nothing.
- “How do you get typed output?” -
response_formatwith a Pydantic model, which validates the final answer and turns prose parsing into an object.