Building AI Agents with LangGraph
The framework for stateful, multi-step LLM workflows
LangGraph models AI agents as directed graphs. Nodes are actions, edges are transitions, state persists across steps. It's a state machine for LLM applications.
Best for: Tool-calling agents, multi-step workflows, human-in-the-loop systems, multi-agent orchestration
Code examples: github.com/mshoaibiqbal/langgraph-agents
Why LangGraph?
LangChain handles straightforward LLM chains well. But production agents need capabilities that simple chains can't provide:
| Capability | LangChain | LangGraph |
|---|---|---|
| Linear flow | ✅ | ✅ |
| Loops (retry, iterate) | ❌ | ✅ |
| Conditional branching | ⚠️ Limited | ✅ |
| Persistent state | ❌ | ✅ |
| Human-in-the-loop | ❌ | ✅ |
| Parallel execution | ⚠️ Limited / Manual | ✅ |
| Crash recovery | ❌ | ✅ |
The key insight: agents are state machines. LangGraph makes this explicit.
Core Concepts
The Graph Model
Every LangGraph application has three components:
┌───────────────────────────────────────────────────────┐
│ STATE │
│ (TypedDict that flows through all nodes) │
└───────────────────────────┬───────────────────────────┘
│
▼
┌───────────────────────────────────────────────────────┐
│ NODES │
│ (Functions that read state, do work, return updates) │
└───────────────────────────┬───────────────────────────┘
│
▼
┌───────────────────────────────────────────────────────┐
│ EDGES │
│ (Connections that define flow between nodes) │
└───────────────────────────────────────────────────────┘
State
State is a TypedDict that all nodes can read from and write to:
class AgentState(TypedDict):
messages: list[BaseMessage] # Conversation history
context: str # Retrieved information
decision: str # Agent's current decision
Each node receives the full state and returns a dict of updates. LangGraph merges updates automatically.
Nodes
Nodes are just Python functions. They receive state, perform work, and return updates:
def analyze(state: AgentState) -> dict:
# Do work with state["messages"]
return {"decision": "proceed"} # Updates only the 'decision' field
Edges
Edges connect nodes. Two types:
- Static edges - Always follow this path
- Conditional edges - Choose path based on a function's return value
graph.add_edge("start", "analyze") # Static
graph.add_conditional_edges("analyze", router_fn) # Conditional
Architectural Patterns
Pattern 1: Tool-Calling Loop
The most common pattern. The LLM decides whether to use tools or respond directly.
┌─────────┐
│ START │
└────┬────┘
│
▼
┌─────────┐
│ LLM │◀──────────┐
└────┬────┘ │
│ │
[tool calls?] │
/ \ │
/ \ │
▼ ▼ │
┌────────┐ ┌────────┐ │
│ TOOLS │ │ END │ │
└───┬────┘ └────────┘ │
│ │
└────────────────────────┘
Flow: User message → LLM decides → (use tools → execute → back to LLM) OR respond
When to use: Calculator agents, search agents, API-calling agents, any agent with tools
Example: 02_tool_calling_agent.py
Pattern 2: Fan-Out / Fan-In (Parallel Execution)
Multiple agents work in parallel, then results are combined.
┌─────────┐
│ START │
└────┬────┘
│
▼
┌─────────────────┐
│ detect_language │
└────────┬────────┘
│
┌────────────┼────────────┐
│ │ │
▼ ▼ ▼
┌──────────┐ ┌────────────┐ ┌─────────┐
│ security │ │performance │ │ style │ ← PARALLEL
└────┬─────┘ └─────┬──────┘ └────┬────┘
│ │ │
└─────────────┼─────────────┘
│
▼
┌─────────────────┐
│ coordinator │
└────────┬────────┘
│
▼
┌───────┐
│ END │
└───────┘
Key insight: When multiple edges originate from the same node, LangGraph executes them in parallel automatically.
When to use: Code review (multiple reviewers), document analysis (extract different aspects), research (gather from multiple sources)
Example: 03_code_review_agents.py
Pattern 3: Router (Conditional Branching)
Different paths based on classification or decision.
┌─────────┐
│ START │
└────┬────┘
│
▼
┌────────────┐
│ classifier │
└─────┬──────┘
│
┌─────────────┼─────────────┐
│ │ │
▼ ▼ ▼
┌─────────┐ ┌───────────┐ ┌─────────┐
│ billing │ │ technical │ │ general │
└────┬────┘ └─────┬─────┘ └────┬────┘
│ │ │
└──────────────┼──────────────┘
│
▼
┌───────┐
│ END │
└───────┘
When to use: Support ticket routing, intent classification, workflow selection
Pattern 4: Human-in-the-Loop
Pause execution for human review, then resume.
┌─────────┐
│ START │
└────┬────┘
│
▼
┌─────────┐
│ extract │
└────┬────┘
│
▼
┌──────────┐
│ validate │
└────┬─────┘
│
[confidence?]
/ \
/ \
▼ ▼
┌────────────────┐ ┌─────────────┐
│ HUMAN REVIEW │ │ auto_approve│
└───────┬────────┘ └──────┬──────┘
│ │
└─────────┬─────────┘
│
▼
┌──────────┐
│ finalize │
└────┬─────┘
│
▼
┌───────┐
│ END │
└───────┘
Key components:
MemorySaver- Checkpoints state so execution can resumeinterrupt_before- Specifies which nodes pause for human input
memory = MemorySaver()
app = graph.compile(
checkpointer=memory,
interrupt_before=["human_review"]
)
When to use: Document approval, content moderation, high-stakes decisions
Pattern 5: Iterative Refinement
Loop until quality threshold met or max iterations reached.
┌─────────┐
│ START │
└────┬────┘
│
▼
┌──────────┐
│ generate │◀─────────┐
└────┬─────┘ │
│ │
▼ │
┌──────────┐ │
│ evaluate │ │
└────┬─────┘ │
│ │
[good enough?] │
/ \ │
/ \ │
▼ ▼ │
┌────────┐ ┌────────┐ │
│ END │ │ retry │──────┘
└────────┘ └────────┘
When to use: Content generation with quality checks, self-correcting code generation, iterative summarization
State Management
Accumulating State
For fields that accumulate across parallel nodes, use Annotated with operator.add:
from typing import Annotated
import operator
class State(TypedDict):
# Each parallel node can append to this list without conflicts
reviews_complete: Annotated[list[str], operator.add]
State Design Principles
- Keep state minimal - Store IDs and references, not large objects
- Separate concerns - Input fields, processing fields, output fields
- Consider serialization - State must be JSON-serializable for checkpointing
Error Handling
Design compensating transactions for rollback scenarios:
┌───────────┐ ┌───────────┐ ┌───────────┐
│ reserve │────▶│ payment │────▶│ ship │────▶ END
│ inventory │ │ │ │ │
└─────┬─────┘ └─────┬─────┘ └───────────┘
│ │
│ [failure] │ [failure]
│ │
▼ ▼
┌─────────────────────────────────────────────────┐
│ handle_failure │
│ (release inventory, refund, notify) │
└─────────────────────────────────────────────────┘
Use dedicated error-handling nodes rather than try-catch in every node.
When to Use What
| Use Case | Recommended Approach |
|---|---|
| Simple Q&A | LangChain (no graph needed) |
| Single tool call | LangChain with tools |
| Multi-step tool use | LangGraph tool loop |
| Parallel processing | LangGraph fan-out/fan-in |
| Human approval | LangGraph with checkpointer |
| Complex workflows | LangGraph with conditional edges |
| Multi-agent systems | LangGraph with specialized nodes |
Resources
- LangGraph Documentation
- LangGraph Quickstart
- LangChain Academy – Free course
- GitHub Repository
- Example Code
LangGraph reached 1.0 in late 2025. It's production-ready and deployed by Klarna, Replit, and Elastic.