The create_agent prebuilt wins on the standard tool-calling loop. Reach for a hand-written StateGraph when routing, state-driven topology, or interrupts need custom logic.
Imagine ordering coffee. The default menu has espresso, latte, and cappuccino: three preset orders the barista makes in seconds. If that is what you want, you say 'latte' and you are done. If you want oat milk, half the foam, an extra shot, and a sprinkle of cinnamon on top, you have to spell out each step. The preset menu does not have a button for it. The create_agent prebuilt is the preset menu: a standard tool-calling loop with one well-known shape. The hand-written StateGraph is the custom order. It takes more typing, but you can spell out every step the way you want.
Concept explanation~2 min read
Everything you need to truly understand this topic: intuition, mechanics, step by step explanation, code, formulas, and worked example. Click to expand.
Concept explanation~2 min read
Everything you need to truly understand this topic: intuition, mechanics, step by step explanation, code, formulas, and worked example. Click to expand.
There are two surfaces for building LangGraph agents. One is create_agent from langchain.agents, a prebuilt that encapsulates the most common loop and replaces the deprecated create_react_agent. The other is StateGraph, the lower-level primitive that lets you draw any graph. Choosing between them is one of the first decisions in a LangGraph project. The wrong choice shows up either as boilerplate you did not need or as a prebuilt you cannot extend.
The ReAct loop the prebuilt encodes
create_agent is a single function that assembles and compiles a LangGraph graph: a model node that calls the LLM, a tools node that executes any tool calls the model emitted, and conditional edges that loop back to the model after tools or end if there were no tool calls.
from langchain.agents import create_agent
from langchain_core.messages import HumanMessage
from langchain_openai import ChatOpenAI
from langgraph.checkpoint.memory import InMemorySaver
model = ChatOpenAI(model='gpt-5.5')
agent = create_agent(model, tools=[search_tool, calc_tool], checkpointer=InMemorySaver())
result = agent.invoke(
{'messages': [HumanMessage('What is the current temperature in Paris in F?')]},
config={'configurable': {'thread_id': 'thread-1'}},
)
Under the hood the state is AgentState, a TypedDict whose messages key uses the add_messages reducer to append to the list. The agent loop reads the latest message, decides if it is a tool call, runs the tools, appends the results, and re-invokes the model. The loop terminates when the model emits a message with no tool calls. The checkpointer persists the state at every super-step so the run is resumable.
This covers the canonical 'tool-using assistant' shape that ~70% of agent apps need. The prebuilt's value is not novelty. It is the well-tested boilerplate, the streaming defaults, the LangSmith / Langfuse tracing that needs little setup, and the consistent interrupt API.
Situations where this technique stops working.
2–4 min · Everything important, quickly.
| Axis | create_agent prebuilt | Hand-written StateGraph |
|---|---|---|
| State schema | AgentState (messages), extendable with a TypedDict state_schema | Any TypedDict you define |
| Topology | model → tools → model | Any nodes and edges you need |
| Custom routing | No | Conditional edges, per-output dispatch |
| Interrupts | HumanInTheLoopMiddleware, per tool | interrupt() inside any node |
| Parallel branches | Parallel tool calls only | Yes (super-step + reducers) |
| Best for | Tool-using agent with no custom flow | Multi-agent, plans, structured state, HITL |
Real products, models, and research that use this idea.
- LangChain's own agent templates default to create_agent for tool-using bots, swapping to StateGraph for the supervisor and swarm templates.
- LangSmith Studio shows create_agent runs as a fixed model-and-tools graph; custom StateGraphs render with whatever topology the developer drew.
What an interviewer would ask next. Try answering before peeking at the approach.
QHow would you migrate from the create_agent prebuilt to a hand-written StateGraph without losing checkpoint compatibility?
Mirror the prebuilt's messages key and its add_messages reducer in your TypedDict, and attach the same checkpointer. Then add your custom fields and routing. Existing checkpoints replay because the messages slice is byte-compatible.
Red flags & common mistakes
The phrases that signal junior thinking. Click to expand.
Red flags & common mistakes
The phrases that signal junior thinking. Click to expand.
Writing a StateGraph for a vanilla tool-calling loop. That is 40 lines of boilerplate the create_agent prebuilt would have given you.
60 second bullets to scan on the way to the call.
The ReAct loop shape (LLM, tool, LLM, done)
What state schema create_agent uses and how state_schema extends it
Primary sources. Browse if you want the original framing.
Same topic, related formats. Practice these next.