LangGraph in Practice
LangGraph models agents as directed graphs where every step is explicit. This lesson builds the benchmark research agent in LangGraph - with the state model, tool node, conditional edges, checkpoint persistence, and human-in-the-loop interrupt all annotated.
LangGraph’s Mental Model
LangGraph is built on a single idea: an agent is a directed graph where each node is a Python function, each edge is a transition, and the state is a typed dict that flows through the graph. You define the graph explicitly - every node, every edge condition, every loop - rather than relying on the framework to decide what happens next. This makes LangGraph the most verbose of the three frameworks and also the most debuggable: you can inspect the graph structure before running it and trace exactly which path was taken after.
The core components:
- StateGraph - the graph container. You add nodes and edges to it, then compile it into a runnable graph.
- State (TypedDict) - the shared data structure passed between nodes. Nodes read from state and return updates to it.
- Nodes - Python functions that receive the current state and return a state update.
- Edges - transitions between nodes. Conditional edges use a routing function to decide the next node based on state.
- Checkpointer - a persistence layer (in-memory, SQLite, or external store) that saves state after every node execution.
Defining the State
from typing import TypedDict, Annotated, List
import operator
class ResearchState(TypedDict):
# The original research question
question: str
# Accumulated list of LangChain messages (auto-appended)
messages: Annotated[list, operator.add]
# Sourced claims collected so far
claims: List[dict]
# Whether the agent has enough information
research_complete: bool
# Number of search iterations (circuit breaker)
search_count: int
Annotated[list, operator.add]: LangGraph merges state updates by replacing fields by default. For the messages list, we want appending instead - the Annotated reducer tells LangGraph to use operator.add to combine updates. This is LangGraph’s mechanism for fields that accumulate rather than overwrite.Defining the Nodes
from langchain_anthropic import ChatAnthropic
from langchain_core.messages import ToolMessage
from langgraph.prebuilt import ToolNode
import json
llm = ChatAnthropic(model="claude-opus-4-8")
tools = [web_search, fetch_document] # tools defined in Lesson 3
llm_with_tools = llm.bind_tools(tools)
def agent_node(state: ResearchState) -> dict:
"""
The reasoning node: calls the LLM with current messages.
Returns a state update containing the model's response.
"""
response = llm_with_tools.invoke(state["messages"])
return {"messages": [response]}
# ToolNode handles tool dispatch, execution, and result formatting
# It also catches tool exceptions and formats them as tool error messages
# rather than letting them propagate as unhandled exceptions
tool_node = ToolNode(tools)
def write_report_node(state: ResearchState) -> dict:
"""
Final node: synthesizes accumulated messages into the output report.
Runs only when research_complete is True.
"""
synthesis_prompt = f"""Based on your research, write a structured report
that answers: {state['question']}
Include: (1) at least 3 sourced claims with URLs, (2) any information gaps."""
from langchain_core.messages import HumanMessage
messages = state["messages"] + [HumanMessage(content=synthesis_prompt)]
response = llm_with_tools.invoke(messages)
return {"messages": [response]}
Wiring the Graph
from langgraph.graph import StateGraph, END
from langgraph.checkpoint.memory import MemorySaver
from langchain_core.messages import AIMessage
def should_continue(state: ResearchState) -> str:
"""
Routing function: decides which node comes after the agent node.
Returns the name of the next node.
"""
messages = state["messages"]
last = messages[-1]
# Circuit breaker: stop after 5 searches to prevent runaway loops
if state["search_count"] >= 5:
return "write_report"
# If the model called tools, execute them
if isinstance(last, AIMessage) and last.tool_calls:
return "tools"
# No tool calls: agent is done researching
return "write_report"
# Build the graph
builder = StateGraph(ResearchState)
# Add nodes
builder.add_node("agent", agent_node)
builder.add_node("tools", tool_node)
builder.add_node("write_report", write_report_node)
# Entry point
builder.set_entry_point("agent")
# Conditional edge: after agent, route based on should_continue()
builder.add_conditional_edges("agent", should_continue)
# After tools run, always return to agent
builder.add_edge("tools", "agent")
# write_report is the terminal node
builder.add_edge("write_report", END)
# Compile with an in-memory checkpointer (swap for SQLite for persistence)
checkpointer = MemorySaver()
graph = builder.compile(checkpointer=checkpointer)
# Run it
config = {"configurable": {"thread_id": "research-001"}}
result = graph.invoke(
{
"question": "What are the main approaches to AI agent memory?",
"messages": [{"role": "user", "content":
"Research: What are the main approaches to AI agent memory?"}],
"claims": [],
"research_complete": False,
"search_count": 0
},
config
)
Adding Human-in-the-Loop
LangGraph’s interrupt_before mechanism pauses the graph before a specified node and waits for an external signal to resume. This is the production pattern for agent approval gates:
# Compile with interrupt_before the tools node
graph_with_hitl = builder.compile(
checkpointer=checkpointer,
interrupt_before=["tools"] # pause before every tool execution
)
# First invocation runs until it reaches the "tools" node
snapshot = graph_with_hitl.invoke(initial_state, config)
# snapshot.next == ("tools",) - graph is paused here
# Inspect the pending tool calls before approving
pending = snapshot.values["messages"][-1].tool_calls
print(f"Agent wants to call: {[tc['name'] for tc in pending]}")
# Human approves - resume by calling invoke again with same config
# LangGraph reads the saved checkpoint and continues from the interrupt
result = graph_with_hitl.invoke(None, config)
Observability: Inspecting the Graph Run
LangGraph integrates with LangSmith for production tracing. For local debugging, the stream_mode="updates" option emits each state update as it happens:
for event in graph.stream(initial_state, config, stream_mode="updates"):
node_name = list(event.keys())[0]
update = event[node_name]
print(f"--- {node_name} ---")
if "messages" in update:
last_msg = update["messages"][-1]
print(f" type: {type(last_msg).__name__}")
if hasattr(last_msg, "tool_calls") and last_msg.tool_calls:
print(f" tools: {[tc['name'] for tc in last_msg.tool_calls]}")
LangGraph: What It’s Good At
- Complex, branching flows - the graph model handles multi-path agents, parallel branches, and conditional routing naturally. If your agent has genuinely complex control flow, LangGraph is the most expressive tool for it.
- Checkpoint-based resumption - production agents that may run for minutes or hours, pause for human approval, or recover from partial failures benefit most from LangGraph’s checkpointing.
- Debuggability - the explicit graph structure means you can visualize what the agent will do before running it (
graph.get_graph().draw_mermaid()) and trace exactly what happened after.
LangGraph: What It Costs
- Verbosity - simple agents require the same graph machinery as complex ones. A two-step agent in LangGraph involves StateGraph, nodes, edges, conditional routing, and compilation - more code than the equivalent raw loop.
- Learning curve - the graph/state/reducer model is different from both raw API calls and the role-based model in CrewAI. Budget 1-2 weeks for a developer to become productive.
- LangChain dependency - LangGraph is built on LangChain. You get the full LangChain ecosystem; you also get its version churn and its opinionated message format.
Ready to Go Deeper?
Live instructor-led courses from our partners. Affiliate disclosure.
AI & ML Courses - 30% Off
Live instructor-led AI, machine learning, data science, and cloud courses for working professionals. Use code Limited30 at checkout.
EdurekaDataCamp - AI & Data Science
Hands-on Python, machine learning, and AI courses with interactive exercises and real projects.
DataCampedX - Top AI Courses
University-level AI courses from MIT, Harvard, Stanford. Earn certificates that employers recognize.
edX