Skip to content

LangGraph Tutorial: Build Your First AI Agent (2026)

LangGraph lets you build AI agents as state machines — with cycles, persistence, and human-in-the-loop control — using Python. If your agent needs to loop, retry, or pause for approval, LangGraph is the framework to reach for. This tutorial takes you from zero to a working agent in under 30 minutes.

Who this is for:

  • Junior engineers: You want to build your first agent beyond a simple prompt chain
  • Senior engineers: You need stateful, production-grade agent orchestration with checkpointing

Most AI applications start as simple chains: take input, call LLM, return output. That works until it doesn’t.

Here’s where single-pass chains break down:

ScenarioWhat BreaksWhat You Need
Research agent that searches, evaluates, then searches againChain can’t loop backCycles in the graph
Customer support bot that escalates to humanChain can’t pause mid-executionInterrupts and checkpointing
Code generation agent that runs tests and retriesChain runs once and stopsConditional routing with retry logic
Multi-step workflow that crashes at step 5 of 8Restarts from scratchState persistence via checkpointers

“How do I build a LangGraph agent that can retry and loop?” — this is the #1 question in the LangChain Discord, and it’s exactly what this tutorial solves.

70% of production agent failures trace back to insufficient state management. LangGraph addresses this by treating every agent as a graph with explicit state, not a chain with implicit flow.


Think of LangGraph like a subway map. Each station is a node (a function that does work). The tracks between stations are edges (connections that route data). Some junctions have switches that send the train down different tracks depending on conditions — those are conditional edges.

The train itself? That’s your state — a typed dictionary that every node can read and update as it passes through.

  1. State — A TypedDict or MessagesState that holds all data flowing through the graph. Every node receives the full state and returns updates to it.

  2. Nodes — Python functions that do actual work: call an LLM, execute a tool, format output. Each node takes state in and returns state updates.

  3. Edges — Connections between nodes. Regular edges always go A → B. Conditional edges evaluate a function and route to different nodes based on the result.

Unlike LangChain chains that flow in one direction, LangGraph graphs can have cycles — a node can route back to a previous node. This is what enables retry loops, multi-turn conversations, and iterative refinement.


You’ll build a ReAct-style agent that can call tools, evaluate results, and decide whether to call more tools or respond to the user.

Terminal window
pip install langgraph langchain-openai

The state schema determines what data flows through your graph. MessagesState is the built-in schema for chat agents — it manages a list of messages automatically.

from langgraph.graph import MessagesState

That’s it. MessagesState gives you a messages key that automatically appends new messages rather than overwriting them. For custom state, you’d define a TypedDict with your own fields.

Every node is a plain Python function. It takes state, does work, and returns state updates.

from langchain_openai import ChatOpenAI
from langgraph.graph import MessagesState, END
# Initialize the LLM with tool binding
llm = ChatOpenAI(model="gpt-4o")
# Define tools the agent can use
from langchain_core.tools import tool
@tool
def search_web(query: str) -> str:
"""Search the web for current information."""
# Replace with your actual search implementation
return f"Search results for: {query}"
@tool
def calculate(expression: str) -> str:
"""Evaluate a math expression."""
return str(eval(expression)) # Use a safe evaluator in production
# Bind tools to the LLM
tools = [search_web, calculate]
llm_with_tools = llm.bind_tools(tools)
def call_llm(state: MessagesState):
"""Node that calls the LLM — may or may not request tool use."""
response = llm_with_tools.invoke(state["messages"])
return {"messages": [response]}
def call_tool(state: MessagesState):
"""Node that executes tool calls from the LLM response."""
from langchain_core.messages import ToolMessage
last_message = state["messages"][-1]
results = []
for tool_call in last_message.tool_calls:
# Look up the tool by name and invoke it
tool_fn = {t.name: t for t in tools}[tool_call["name"]]
result = tool_fn.invoke(tool_call["args"])
results.append(
ToolMessage(content=result, tool_call_id=tool_call["id"])
)
return {"messages": results}

The conditional edge decides whether the agent should call a tool or stop and respond.

from typing import Literal
def should_continue(state: MessagesState) -> Literal["call_tool", END]:
"""Route based on whether the LLM wants to use a tool."""
last_message = state["messages"][-1]
if last_message.tool_calls:
return "call_tool"
return END

Now wire everything together:

from langgraph.graph import StateGraph, START
# Create the graph
builder = StateGraph(MessagesState)
# Add nodes
builder.add_node("call_llm", call_llm)
builder.add_node("call_tool", call_tool)
# Add edges
builder.add_edge(START, "call_llm") # Start by calling the LLM
builder.add_conditional_edges(
"call_llm",
should_continue,
["call_tool", END] # Route to tool or finish
)
builder.add_edge("call_tool", "call_llm") # After tool, go back to LLM
# Compile
agent = builder.compile()
from langchain_core.messages import HumanMessage
result = agent.invoke({
"messages": [HumanMessage(content="What is 25 * 48?")]
})
for msg in result["messages"]:
print(f"{msg.type}: {msg.content}")

The agent will: (1) receive the question, (2) decide to call the calculate tool, (3) get the result, (4) formulate a natural language response.


A LangGraph agent executes as a superstep loop: the LLM node decides whether to call a tool or return a final answer, with the checkpointer persisting state after every step.

LangGraph ReAct Agent — Execution Flow

State flows through nodes; conditional edges create the tool-calling loop

EntryUser sends message
HumanMessage added to state
Route to call_llm
LLM NodeDecides action
Receives full message history
Returns AIMessage (with or without tool_calls)
RouterConditional edge
Has tool_calls? → call_tool
No tool_calls? → END
Tool NodeExecutes tools
Runs each tool_call
Appends ToolMessage to state
Routes back to LLM
Idle

LangGraph Architecture Layers

From your agent code down to the execution runtime

Your Agent Graph
Nodes, edges, state schema
LangGraph Compiler
Validates graph, resolves edges
Execution Runtime
Manages supersteps, state updates
Checkpointer
SQLite, PostgreSQL, or Redis persistence
LangChain Components
LLMs, tools, prompts, parsers
Idle

Each execution tick is called a superstep. In one superstep, a node runs and returns state updates. The runtime applies those updates, evaluates edges, and routes to the next node. If a checkpointer is configured, state is persisted after every superstep — so crashes don’t lose progress.


These two patterns — checkpointing for durable execution and human-in-the-loop interrupts — are what separate a production LangGraph agent from a prototype.

Here’s what makes LangGraph production-ready — durable execution with checkpointing:

import sqlite3
from langgraph.checkpoint.sqlite import SqliteSaver
# Create a persistent checkpointer
checkpointer = SqliteSaver(sqlite3.connect("agent_state.db"))
# Compile with checkpointing enabled
agent = builder.compile(checkpointer=checkpointer)
# Each conversation gets a unique thread_id
config = {"configurable": {"thread_id": "user-123"}}
# First message
result = agent.invoke(
{"messages": [HumanMessage(content="Search for LangGraph pricing")]},
config=config
)
# Later — continue the same conversation (state is restored)
result = agent.invoke(
{"messages": [HumanMessage(content="Now compare it with CrewAI")]},
config=config
)

The thread_id uniquely identifies a conversation. If your process crashes between supersteps, the agent resumes from the last checkpoint — not from scratch.

Use interrupt to pause the graph and wait for human approval:

from langgraph.types import interrupt
@tool
def send_email(to: str, subject: str, body: str) -> str:
"""Send an email — requires human approval."""
# This pauses the graph until a human resumes it
response = interrupt({
"action": "send_email",
"to": to,
"subject": subject,
"message": "Approve sending this email?"
})
if response.get("approved"):
return f"Email sent to {to}"
return "Email cancelled by user"

This pattern is critical for any agent that takes real-world actions — sending emails, making API calls, or modifying databases.


LangGraph is the right tool when you need cycles, persistence, or conditional routing — but it adds boilerplate cost that simple linear chains do not justify.

LangGraph vs Simple LangChain Chains

LangChain LCEL
Simple, linear, stateless
  • Fast to prototype — 5 lines for a RAG chain
  • No boilerplate — just pipe operators
  • Cannot loop or retry
  • No built-in persistence
  • No human-in-the-loop support
VS
LangGraph
Stateful, cyclic, persistent
  • Full control over execution flow
  • Built-in checkpointing and resumption
  • Human-in-the-loop via interrupts
  • More code — state schema + edge definitions
  • Steeper learning curve for simple tasks
Verdict: Use LangChain LCEL for simple one-shot pipelines (RAG, summarization). Use LangGraph when your agent needs loops, persistence, or human approval.
Use case
Building AI agents that take multi-step actions

Other common mistakes:

  • Forgetting thread_id in config — Without it, every invocation starts fresh. Your “conversational” agent has no memory.
  • Mutating state directly — Nodes should return state updates, not modify state in place. LangGraph applies updates immutably between supersteps.
  • Infinite loops — If your conditional edge never routes to END, the agent loops forever. Always add a maximum iteration guard or timeout.
  • Not handling tool errors — If a tool raises an exception, the graph crashes. Wrap tool calls in try/except and return error messages as ToolMessage content.

These four questions test whether you understand LangGraph’s state machine model and when to reach for it over a simpler LangChain pipeline.

Q1: “What is LangGraph and how does it differ from LangChain?”

Section titled “Q1: “What is LangGraph and how does it differ from LangChain?””

What they’re testing: Do you understand the fundamental architecture difference, or do you think LangGraph is just “LangChain 2.0”?

Strong answer: “LangChain is a pipeline framework — data flows linearly from step to step. LangGraph is a state machine framework — it models agents as directed graphs with typed state, conditional edges, and cycles. They’re complementary: LangChain components work inside LangGraph nodes.”

Weak answer: “LangGraph is the newer version of LangChain.”

Q2: “When would you use LangGraph over a simple chain?”

Section titled “Q2: “When would you use LangGraph over a simple chain?””

What they’re testing: Can you identify the architectural trigger, not just parrot features?

Strong answer: “I reach for LangGraph when the workflow has cycles — like a ReAct agent that loops between thinking and acting — or when I need durable execution that survives process restarts. For a one-shot RAG pipeline, LangChain is the right choice.”

Q3: “How does LangGraph handle state persistence?”

Section titled “Q3: “How does LangGraph handle state persistence?””

What they’re testing: Do you understand checkpointing at an implementation level?

Strong answer: “LangGraph uses checkpointers that implement BaseCheckpointSaver. After every superstep, the full state is serialized and saved. You configure a thread_id to identify each conversation. If the process crashes, calling invoke with the same thread_id resumes from the last checkpoint, not from the beginning.”

Q4: “How would you add human approval to a LangGraph agent?”

Section titled “Q4: “How would you add human approval to a LangGraph agent?””

What they’re testing: Production readiness — can you build agents that don’t run unsupervised?

Strong answer: “Use the interrupt() function inside a tool. When the graph hits an interrupt, it pauses and persists state. The human reviews the pending action, then the graph resumes with Command(resume=...) containing the approval or modification.”


At scale, you’ll see these patterns in production LangGraph deployments:

State management: Teams use PostgreSQL checkpointers (not SQLite) for concurrent multi-user agents. Each user gets a unique thread_id, and the checkpointer handles concurrent writes.

Observability: Integrate LangSmith for trace visualization. Every superstep, node execution, and state transition becomes a traceable span. Without this, debugging a 15-step agent workflow is nearly impossible.

Cost control: Add a max_iterations counter to your state. Increment it in the LLM node and check it in the conditional edge. Production agents need hard limits — an uncapped ReAct loop can burn through your API budget in minutes.

Deployment: LangGraph Cloud provides managed infrastructure for deploying stateful agents. Alternatively, you can self-host with FastAPI + Redis checkpointer for custom deployments.

For more on agent architecture patterns and how they map to LangGraph implementations, see our agentic design patterns guide.


  • LangGraph models agents as state machines — nodes do work, edges route between them, and state flows through the entire graph
  • Three building blocks: State (typed data), Nodes (Python functions), Edges (routing logic including conditional edges)
  • Cycles are the key differentiator — unlike chains, LangGraph graphs can loop, enabling ReAct agents, retry logic, and iterative refinement
  • Checkpointers enable durable execution — state persists across process restarts via SQLite, PostgreSQL, or Redis
  • Human-in-the-loop via interrupts — pause the graph, get human approval, resume with modifications
  • Start simple — use LangChain LCEL for linear pipelines, migrate to LangGraph only when you need cycles or persistence
  • Always set thread_id and add iteration limits to prevent runaway loops in production

Frequently Asked Questions

What is LangGraph and how does it differ from LangChain?

LangGraph is a framework for building AI agents as state machines with cycles, persistence, and human-in-the-loop control. Unlike LangChain chains that flow in one direction, LangGraph graphs can have cycles — a node can route back to a previous node, enabling retry loops, multi-turn conversations, and iterative refinement.

What are the core building blocks of a LangGraph agent?

LangGraph has three core building blocks. State is a TypedDict or MessagesState that holds all data flowing through the graph. Nodes are Python functions that do actual work like calling an LLM or executing a tool. Edges are connections between nodes — regular edges always go A to B, while conditional edges evaluate a function and route to different nodes based on the result.

When should I use LangGraph instead of a simple LangChain chain?

Use LangGraph when your agent needs to loop back (retry logic, iterative refinement), pause mid-execution for human approval, persist state across crashes via checkpointers, or handle conditional routing based on intermediate results. If your workflow is a straight pipeline with no cycles or interrupts, a simple chain is sufficient.

How do I add tools to a LangGraph agent?

Define tools as Python functions decorated with @tool from langchain_core.tools, then bind them to the LLM using llm.bind_tools(tools). Create a call_tool node that executes tool calls from the LLM response, and add a conditional edge (should_continue) that routes to the tool node when the LLM requests tool use or to END when it responds directly. The tool node routes back to the LLM node, creating the ReAct loop.

What is a superstep in LangGraph?

A superstep is one execution tick in a LangGraph agent. In one superstep, a node runs and returns state updates. The runtime applies those updates, evaluates edges, and routes to the next node. If a checkpointer is configured, state is persisted after every superstep, so crashes do not lose progress.

How does checkpointing work in LangGraph?

LangGraph uses checkpointers that implement BaseCheckpointSaver to persist state. You compile the graph with a checkpointer (SQLite, PostgreSQL, or Redis) and pass a unique thread_id in the config for each conversation. After every superstep, the full state is serialized and saved. If the process crashes, invoking with the same thread_id resumes from the last checkpoint rather than starting over.

How do I add human-in-the-loop approval to a LangGraph agent?

Use the interrupt() function inside a tool to pause the graph and wait for human approval. When the graph hits an interrupt, it persists state and halts execution. The human reviews the pending action, then the graph resumes with Command(resume=...) containing the approval or modification. This pattern is critical for agents that take real-world actions like sending emails or modifying databases.

What is MessagesState in LangGraph?

MessagesState is a built-in state schema for chat agents in LangGraph. It provides a messages key that automatically appends new messages rather than overwriting them. This makes it the standard choice for conversational agents where you need to maintain message history. For custom state beyond chat messages, you define your own TypedDict with additional fields.

How do conditional edges work in LangGraph?

Conditional edges evaluate a Python function against the current state and route to different nodes based on the result. For example, a should_continue function checks whether the LLM response contains tool_calls. If it does, the edge routes to the tool node; if not, it routes to END. This is what creates the ReAct loop where the agent alternates between thinking and acting.

What are common mistakes when building LangGraph agents?

Common mistakes include forgetting to set thread_id in config so every invocation starts fresh with no memory, mutating state directly instead of returning state updates, creating infinite loops by never routing to END without a maximum iteration guard, and not handling tool errors with try/except blocks. Wrapping tool calls in error handling and adding iteration limits are essential for production agents.

Last updated: February 2026 | LangGraph v0.3+ / Python 3.10+