LangGraph Guide 2025: Build Stateful AI Agents with LangChain
LangGraph is the go-to framework for building production-grade stateful AI agents in Python. It models agent behavior as a directed graph — giving you explicit control over state, routing, and memory that pure LLM loops lack.
What Is LangGraph?
LangGraph (released 2024, built by the LangChain team) extends LangChain with a graph execution engine. Instead of linear chains, you define:
- Nodes — Python functions that read the shared state and return updates
- Edges — directed connections between nodes (always-on or conditional)
- State — a typed dictionary shared across all nodes in the graph
- Checkpoints — snapshots of state persisted to SQLite or Postgres after each step
LangGraph powers the agent loop in tools like LangChain's own products and many production AI systems. It has 10k+ GitHub stars and is used by teams at Elastic, Klarna, and hundreds of startups.
Installation
# Install LangGraph + LangChain core pip install langgraph langchain-core # With OpenAI provider pip install langgraph langchain-openai # With Anthropic provider pip install langgraph langchain-anthropic # SQLite checkpointing (built-in, no extra install needed) # Postgres checkpointing pip install langgraph-checkpoint-postgres
StateGraph vs MessageGraph
StateGraph (general purpose)
You define the state shape using a TypedDict. Nodes receive the full state and return a dict of updates. Use this when you need custom fields alongside messages — counters, flags, tool call results, scratchpads.
MessageGraph (chat shortcut)
The state is pre-defined as a list of LangChain messages. Nodes receive and return messages. Use for simple chatbot agents where conversation history is the only state you need.
Quickstart: 3-Node ReAct Agent
A minimal agent that calls an LLM, decides whether to use a tool, calls the tool, then loops back:
from typing import TypedDict, Annotated
from langgraph.graph import StateGraph, END
from langgraph.prebuilt import ToolNode
from langchain_openai import ChatOpenAI
from langchain_core.messages import BaseMessage
import operator
# 1. Define state schema
class AgentState(TypedDict):
messages: Annotated[list[BaseMessage], operator.add]
# 2. Define the LLM node
model = ChatOpenAI(model="gpt-4o-mini").bind_tools([my_tool])
def call_model(state: AgentState):
response = model.invoke(state["messages"])
return {"messages": [response]}
# 3. Routing function
def should_continue(state: AgentState):
last = state["messages"][-1]
if last.tool_calls:
return "tools"
return "end"
# 4. Build the graph
graph = StateGraph(AgentState)
graph.add_node("agent", call_model)
graph.add_node("tools", ToolNode([my_tool]))
graph.set_entry_point("agent")
graph.add_conditional_edges(
"agent",
should_continue,
{"tools": "tools", "end": END}
)
graph.add_edge("tools", "agent") # loop back after tool call
app = graph.compile()
# 5. Run
result = app.invoke({"messages": [("user", "What is 42 * 7?")]})
print(result["messages"][-1].content) Checkpointing & Memory
Checkpointing persists graph state after every node, enabling long-term memory and human-in-the-loop interrupts. Pass a checkpointer when compiling:
from langgraph.checkpoint.memory import MemorySaver
from langgraph.checkpoint.sqlite import SqliteSaver
# In-memory (dev / testing only)
memory = MemorySaver()
app = graph.compile(checkpointer=memory)
# SQLite (local persistence)
with SqliteSaver.from_conn_string("checkpoints.db") as saver:
app = graph.compile(checkpointer=saver)
# Each run needs a thread_id to scope checkpoints
config = {"configurable": {"thread_id": "user-42-session-1"}}
app.invoke({"messages": [("user", "Hello")]}, config=config)
# Resume the same thread later — LangGraph loads saved state
app.invoke({"messages": [("user", "What did I just say?")]}, config=config) Human-in-the-Loop Interrupts
LangGraph can pause execution before a node and wait for human input. Use interrupt_before at compile time:
# Pause before the "tools" node to let a human approve
app = graph.compile(
checkpointer=memory,
interrupt_before=["tools"]
)
config = {"configurable": {"thread_id": "approval-flow-1"}}
# Run until the interrupt
app.invoke({"messages": [("user", "Delete all temp files")]}, config=config)
# → graph pauses, prints state
# Human reviews, then resumes with None (no new input needed)
app.invoke(None, config=config) Multi-Agent Supervisor Pattern
For complex workflows, use a supervisor agent that routes between specialized sub-agents:
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
# Supervisor decides which worker to call next
supervisor_prompt = ChatPromptTemplate.from_messages([
("system", "You are a supervisor routing to: {members}. Reply with the next worker."),
("human", "{input}")
])
members = ["researcher", "coder", "writer"]
supervisor = supervisor_prompt | ChatOpenAI(model="gpt-4o").with_structured_output(
{"next": str}
)
# Routing function reads supervisor output
def route(state):
return state["next"] # "researcher" | "coder" | "writer" | "FINISH" LangGraph vs CrewAI vs AutoGen
| Factor | LangGraph | CrewAI | AutoGen |
|---|---|---|---|
| Abstraction level | Low (explicit graph) | High (role-based agents) | Medium (conversational) |
| State management | Typed state + checkpointing | Built-in memory types | Message history |
| Routing control | Full (conditional edges) | Sequential / hierarchical | Agent decides via chat |
| Human-in-the-loop | First-class interrupt API | Limited | UserProxyAgent |
| Production readiness | High (streaming, Postgres) | Medium | Medium |
| Best for | Complex production agents | Quick multi-agent prototypes | Conversational multi-agent |
See also: CrewAI Guide and Claude Code Guide for agent tooling comparisons.
Monitor Your AI Agent Stack
LangGraph agents depend on OpenAI, Anthropic, and other AI APIs. Track uptime for every provider your agents rely on from one dashboard — get instant alerts when any dependency degrades.
Monitor AI API Status Free →