LangGraph Tutorial: Build a Graph Agent in Python (2026)
A hands-on LangGraph tutorial for 2026: build the same tool-using AI agent two ways, with the five-line create_agent helper and as an explicit StateGraph you can customize. Fully runnable Python.

On this page
Quick answer (July 2026): LangGraph (v1.2.9, released July 10, 2026) lets you build an AI agent as an explicit state graph: nodes do the work, edges decide what runs next, and a loop lets the model call tools until it is finished. This tutorial builds the same tool-using agent two ways, first with the five-line prebuilt create_agent, then as a hand-written StateGraph so you can see and customize every step. Everything below is runnable with only an Anthropic API key.
The stack: LangChain / LangGraph,
Python 3.10+, and
Anthropic Claude as the model.
The five ideas you actually need
LangGraph has a small vocabulary. Learn these and the rest is detail.
Scroll to see more
| Concept | What it is | In code |
|---|---|---|
| State | A typed dict passed between steps. Fields can accumulate. | class State(TypedDict): ... |
| Node | A plain function that reads state and returns an update. | def llm_call(state): ... |
| Edge | A fixed "after A, run B" arrow. | builder.add_edge("tools", "llm") |
| Conditional edge | A function that picks the next node at runtime. | builder.add_conditional_edges(...) |
| Compile | Freezes the builder into a runnable graph. | graph = builder.compile() |
The agent loop is just three of these wired together: llm runs the model, a conditional edge checks whether the model asked for a tool, tools runs the tool and sends the result back to llm. Repeat until the model stops asking for tools.
What you will build
A small store-support agent with two local tools:
search_policylooks up a value in a policy dictionary.calculatorevaluates a simple arithmetic expression.
Ask it "A customer paid $240 and wants to return the item. What is the refund window, and what restocking fee will they be charged?" and it must chain the two tools: look up the fee percentage, then compute it. Both tools run locally, so only the model needs an API key.
Setup
pip install "langgraph==1.2.9" langchain langchain-anthropic
export ANTHROPIC_API_KEY="sk-ant-..."
Define the tools once. Both parts of the tutorial reuse them.
from langchain.tools import tool
POLICY = {"refund_window_days": 30, "restocking_fee_pct": 15}
@tool
def search_policy(key: str) -> str:
"""Look up a store policy value. Keys: refund_window_days, restocking_fee_pct."""
if key not in POLICY:
return f"No policy value named {key!r}. Known keys: {list(POLICY)}"
return f"{key} = {POLICY[key]}"
@tool
def calculator(expression: str) -> str:
"""Evaluate a simple arithmetic expression, e.g. '240 * 0.15'."""
allowed = set("0123456789+-*/(). ")
if not set(expression) <= allowed:
return "Rejected: only numbers and + - * / ( ) are allowed."
try:
return str(eval(expression, {"__builtins__": {}}, {}))
except Exception as err:
return f"Error: {err}"
Notice the tools return their errors as strings instead of raising. That is deliberate, and the second half of this tutorial shows why it matters.
Part 1: the five-line prebuilt agent
The fastest path is create_agent, which wires the whole loop for you.
from langchain.agents import create_agent
agent = create_agent(
model="anthropic:claude-sonnet-4-6", # Anthropic model string, July 2026
tools=[search_policy, calculator],
system_prompt="You are a store support agent. Use the tools; never guess policy values.",
)
question = ("A customer paid $240 and wants to return the item. "
"What is the refund window, and what restocking fee will they be charged?")
result = agent.invoke({"messages": [{"role": "user", "content": question}]})
print(result["messages"][-1].content)
Run it and you get something like: "The refund window is 30 days. A 15% restocking fee on $240 is $36." Under the hood, create_agent built the exact llm-tools-loop graph described above. That is perfect when the default loop is all you need.
Part 2: the same agent as an explicit graph
create_agent hides the loop. The moment you want to add a human-approval step, custom routing, a guardrail node, or a second model, you write the graph yourself. Here is the identical agent, spelled out.
import operator
from typing import Annotated, TypedDict
from langchain.chat_models import init_chat_model
from langchain_core.messages import AnyMessage, SystemMessage, ToolMessage
from langgraph.graph import StateGraph, START, END
class State(TypedDict):
messages: Annotated[list[AnyMessage], operator.add] # new messages append
tools = [search_policy, calculator]
tools_by_name = {t.name: t for t in tools}
model = init_chat_model("anthropic:claude-sonnet-4-6").bind_tools(tools)
SYSTEM = SystemMessage("You are a store support agent. Use the tools; never guess policy values.")
def llm_call(state: State) -> dict:
reply = model.invoke([SYSTEM] + state["messages"])
return {"messages": [reply]}
def tool_node(state: State) -> dict:
last = state["messages"][-1]
results = []
for call in last.tool_calls:
output = tools_by_name[call["name"]].invoke(call["args"])
results.append(ToolMessage(content=str(output), tool_call_id=call["id"]))
return {"messages": results}
def should_continue(state: State) -> str:
return "tools" if state["messages"][-1].tool_calls else END
builder = StateGraph(State)
builder.add_node("llm", llm_call)
builder.add_node("tools", tool_node)
builder.add_edge(START, "llm")
builder.add_conditional_edges("llm", should_continue, {"tools": "tools", END: END})
builder.add_edge("tools", "llm")
graph = builder.compile()
state = graph.invoke({"messages": [{"role": "user", "content": question}]})
print(state["messages"][-1].content)
Read the wiring bottom-up: START flows into llm. After llm, should_continue looks at the last message. If the model requested a tool, control goes to tools; otherwise the graph ends. tools always flows back to llm, which closes the loop. That single conditional edge is the entire agent.
Because the state field uses Annotated[list, operator.add], every node that returns {"messages": [...]} appends to the running transcript rather than overwriting it. That accumulator is what lets the model see its own earlier tool results on the next pass.
Watch it think: stream the steps
invoke gives you only the final answer. During development, stream the intermediate messages so you can see each tool call.
for step in graph.stream({"messages": [{"role": "user", "content": question}]},
stream_mode="values"):
step["messages"][-1].pretty_print()
You will see the model's first turn request search_policy, the tool result come back, then a calculator call, then the final natural-language answer. That transcript is the single most useful debugging surface LangGraph gives you.
Four gotchas that bite first-time graph builders
- Set a recursion limit on purpose. A tool loop that never resolves will raise
GraphRecursionError. That is a safety feature, not a bug. Control it:graph.invoke(inputs, {"recursion_limit": 25}). - Return tool errors, do not raise them. Our
calculatorreturns"Error: ..."as a string. That lets the model read the failure and retry with a corrected expression. A raised exception kills the whole run instead. - Use the model alias, not a dated pinned id.
"anthropic:claude-sonnet-4-6"tracks the current Sonnet. Hardcoding a fully dated snapshot means editing code every time the model updates. create_agent, notcreate_react_agent. The older LangChain examples importcreate_react_agent. In current LangChain (2026) the prebuilt helper iscreate_agent(from langchain.agents import create_agent). If a tutorial shows the old name, it is written against an older version.
Prebuilt or explicit graph?
Reach for create_agent when the standard "call tools in a loop" behavior is exactly what you want, which is most of the time. Write the StateGraph yourself when you need to insert your own nodes: a validation gate before a tool runs, a human sign-off on risky actions, branching to different models by task, or persisting state across sessions. If you are weighing LangGraph against Anthropic's own harness, we put them head to head in Claude Agent SDK vs LangGraph, and if you would rather build the same agent without LangGraph at all, see Build an AI agent with the Claude Agent SDK in Python.
LangGraph itself is open source under the MIT license (langchain-ai/langgraph on GitHub), so the only thing you pay for is model tokens. The full API surface used above is documented in the LangGraph docs, and current Claude model pricing is on Anthropic's pricing page.
Written by
Ren OkabeFrequently asked questions
Is LangGraph free to use?
Yes. LangGraph is open source under the MIT license, so the framework itself costs nothing. You pay only for the model tokens your agent consumes, for example Anthropic Claude API usage.
Do I need both LangChain and LangGraph installed?
Yes. The prebuilt create_agent helper lives in the langchain package, while StateGraph, START, and END live in langgraph. For Claude models add langchain-anthropic. Install all three: pip install langgraph langchain langchain-anthropic (2026).
What is the difference between create_agent and create_react_agent?
create_react_agent is the older name for the prebuilt agent helper. In current LangChain (2026) the prebuilt helper is create_agent, imported from langchain.agents. If a tutorial imports create_react_agent it was written against an older version.
Can I use a model other than Claude with LangGraph?
Yes. Both create_agent and init_chat_model accept a provider-prefixed model string, so you can swap anthropic:claude-sonnet-4-6 for another supported provider without changing the graph structure.
When should I write the graph myself instead of using create_agent?
Use create_agent when the standard call-tools-in-a-loop behavior is all you need. Write the StateGraph yourself when you need custom nodes such as a validation gate before a tool runs, a human approval step, branching to different models, or persisting state across sessions.
Related tutorials
Claude Agent SDK vs LangGraph (2026): When Each Wins
A runnable comparison of the Claude Agent SDK and LangGraph in 2026, with the same agent built in both and an honest decision matrix for when each wins.
How to Build an AI Agent With the Claude Agent SDK in Python (2026)
A runnable Python quickstart: install the Claude Agent SDK, stream a run with query(), then give the agent your own tools with the @tool decorator and ClaudeSDKClient. Builds a weather agent that chains two tools.
How to Make an AI Agent in 2026 (Step by Step)
The five parts every AI agent has, a runnable Python example in about 40 lines, and an honest guide to the guardrails and framework choices most tutorials skip.