LangGraph is a framework for building stateful, controllable, graph-based AI workflows. It lets you design LLM applications as graphs with nodes, edges, and shared state.
Build Custom Agents with Low-Level Control - LangGraph lets you define states, fallbacks, and loops for complex AI workflows.
Trusted in production by companies like LinkedIn, Uber, Klarna, Replit, Elastic, and GitLab.
While LangChain is excellent for sequential tasks, real-world AI applications often require more complexity. That’s where LangGraph comes in.
Not everything in this world is linear. The same is true for building agentic AI systems:
┌─────────────────────────────────────────────────────────────────────────┐
│ Real-World Complexity │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ What Sequential Chains Assume: │
│ ┌───┐ ┌───┐ ┌───┐ ┌───┐ │
│ │ A │───▶│ B │───▶│ C │───▶│ D │ (Always the same path) │
│ └───┘ └───┘ └───┘ └───┘ │
│ │
│ What Reality Looks Like: │
│ │
│ ┌───────────┐ │
│ ┌───▶│ Search │───┐ │
│ │ │ DB │ │ │
│ │ └───────────┘ │ │
│ ┌───────┐ ┌───┴───┐ ┌───▼───┐ ┌──────────┐ │
│ │ Query │───▶│Decide │ │Combine│───▶│ Response │ │
│ └───────┘ └───┬───┘ └───▲───┘ └────┬─────┘ │
│ │ ┌───────────┐ │ │ │
│ └───▶│ Call │───┘ │ │
│ │ API │ │ │
│ └─────┬─────┘ │ │
│ │ │ │
│ │ ┌─────────┐ │ │
│ └───▶│ Retry? │───────┘ (Loop back) │
│ └─────────┘ │
│ │
│ Decision paths, parallel execution, loops, error handling... │
└─────────────────────────────────────────────────────────────────────────┘
When building real-world agent AI systems, developers quickly realize:
That’s when LangGraph came into the picture.
| Challenge | How LangGraph Solves It |
|---|---|
| Memory across steps | Maintains state, enabling long, multi-step workflows |
| Complex control flow | Uses nodes & edges to define exactly how the agent should think and act |
| Safe tool calls | Built-in support for safe, structured tool calls inside workflows |
| Error handling | Define fallback actions for when nodes fail |
| Conditional logic | Branch based on LLM output or external conditions |
| Long-running workflows | Durable execution with checkpointing and persistence |
Nodes are the building blocks of your graph. Each node represents a discrete step in your workflow.
from langgraph.graph import StateGraph, START, END
from typing import TypedDict
# Define state schema
class AgentState(TypedDict):
messages: list
current_step: str
# Define nodes as functions
def analyze_query(state: AgentState) -> AgentState:
"""Analyze the user's query"""
# Processing logic here
return {"current_step": "analyzed"}
def search_documents(state: AgentState) -> AgentState:
"""Search relevant documents"""
# Search logic here
return {"current_step": "searched"}
def generate_response(state: AgentState) -> AgentState:
"""Generate final response"""
# Generation logic here
return {"current_step": "completed"}
Edges define how nodes connect and the flow between them.
from langgraph.graph import StateGraph, START, END
# Create the graph
graph = StateGraph(AgentState)
# Add nodes
graph.add_node("analyze", analyze_query)
graph.add_node("search", search_documents)
graph.add_node("respond", generate_response)
# Add edges (connections between nodes)
graph.add_edge(START, "analyze") # Entry point
graph.add_edge("analyze", "search")
graph.add_edge("search", "respond")
graph.add_edge("respond", END) # Exit point
# Compile the graph
app = graph.compile()
State is shared across all nodes and persists throughout the workflow.
from typing import TypedDict, List, Optional, Annotated
from langchain_core.messages import BaseMessage
from langgraph.graph.message import add_messages
class ConversationState(TypedDict):
# Chat history (with automatic message merging)
messages: Annotated[List[BaseMessage], add_messages]
# Current query being processed
current_query: str
# Retrieved context
context: Optional[str]
# Processing metadata
step_count: int
needs_clarification: bool
# Final output
response: Optional[str]
┌─────────────────────────────────────────────────────────────────────────┐
│ LangGraph Architecture │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ SHARED STATE │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │ Messages │ │ Context │ │ Metadata │ │ Output │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ GRAPH │ │
│ │ │ │
│ │ ┌─────────┐ ┌─────────┐ │ │
│ │ │ START │ │ END │ │ │
│ │ └────┬────┘ └────▲────┘ │ │
│ │ │ │ │ │
│ │ ▼ │ │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │
│ │ │ Node │────▶│ Node │────▶│ Node │──┘ │ │
│ │ │ A │ │ B │ │ C │ │ │
│ │ └────┬────┘ └────┬────┘ └────┬────┘ │ │
│ │ │ │ │ │ │
│ │ │ ┌────▼────┐ │ │ │
│ │ └─────────▶│Condition│◀─────────┘ │ │
│ │ │ Node │ │ │
│ │ └────┬────┘ │ │
│ │ │ │ │
│ │ ┌────▼────┐ │ │
│ │ │ Loop to │ (Back to Node A if needed) │ │
│ │ │ Node A │ │ │
│ │ └─────────┘ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
stateDiagram-v2
[*] --> UserQuery
UserQuery --> LLMProcessing
LLMProcessing --> DocumentRetrieval: Need context
DocumentRetrieval --> LLMProcessing: Return documents
LLMProcessing --> AnswerGeneration: Ready to respond
AnswerGeneration --> [*]
note right of LLMProcessing
LLM can loop back
to documents multiple
times if needed
end note
Here’s a practical example showing how LangGraph handles a customer support scenario with authentication, database lookup, and conditional responses:
┌─────────────────────────────────────────────────────────────────────────┐
│ Customer Support Agent Flow │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────┐ │
│ │ Query │ │
│ │ Received │ │
│ └────┬─────┘ │
│ │ │
│ ▼ │
│ ┌──────────┐ │
│ │ Classify │ │
│ │ Intent │ │
│ └────┬─────┘ │
│ │ │
│ ├────────────────┬────────────────┬─────────────────┐ │
│ ▼ ▼ ▼ ▼ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Account │ │ Product │ │Technical│ │ General │ │
│ │ Query │ │ Query │ │ Support │ │ Query │ │
│ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ │
│ │ │ │ │ │
│ ▼ │ │ │ │
│ ┌─────────┐ │ │ │ │
│ │ Auth │ │ │ │ │
│ │Required?│ │ │ │ │
│ └────┬────┘ │ │ │ │
│ Yes│ No │ │ │ │
│ ▼ │ │ │ │ │
│ ┌─────────┐│ │ │ │ │
│ │Validate ││ │ │ │ │
│ │ User ││ │ │ │ │
│ └────┬────┘│ │ │ │ │
│ │ │ │ │ │ │
│ ▼ │ │ │ │ │
│ ┌─────────┐│ │ │ │ │
│ │ Fetch ││ │ │ │ │
│ │ Data │◀─────────┘ │ │ │
│ └────┬────┘ │ │ │
│ │ │ │ │
│ └───────────────┬───────────────┴────────────────┘ │
│ ▼ │
│ ┌─────────┐ │
│ │Generate │ │
│ │Response │ │
│ └────┬────┘ │
│ │ │
│ ▼ │
│ ┌─────────┐ ┌─────────┐ │
│ │ Needs │───Yes──▶│ Loop to │ │
│ │Follow-up│ │ Classify│ │
│ └────┬────┘ └─────────┘ │
│ No │ │
│ ▼ │
│ ┌─────────┐ │
│ │ Send │ │
│ │Response │ │
│ └─────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
from typing import TypedDict, Literal
from langgraph.graph import StateGraph, START, END
from langchain_openai import ChatOpenAI
from langchain.prompts import ChatPromptTemplate
# Define state
class GraphState(TypedDict):
query: str
context: str
response: str
needs_search: bool
# Initialize LLM
llm = ChatOpenAI(model="gpt-4", temperature=0)
# Define node functions
def analyze_query(state: GraphState) -> GraphState:
"""Determine if we need to search for context"""
prompt = ChatPromptTemplate.from_template(
"Analyze this query and determine if it needs external search: {query}\n"
"Respond with 'yes' or 'no'"
)
chain = prompt | llm
result = chain.invoke({"query": state["query"]})
needs_search = "yes" in result.content.lower()
return {"needs_search": needs_search}
def search_documents(state: GraphState) -> GraphState:
"""Search for relevant documents"""
# Implement your search logic here
context = f"Relevant context for: {state['query']}"
return {"context": context}
def generate_response(state: GraphState) -> GraphState:
"""Generate the final response"""
prompt = ChatPromptTemplate.from_template(
"Context: {context}\n\nQuery: {query}\n\nProvide a helpful response."
)
chain = prompt | llm
result = chain.invoke({
"context": state.get("context", "No additional context"),
"query": state["query"]
})
return {"response": result.content}
# Define conditional edge
def route_query(state: GraphState) -> Literal["search", "respond"]:
if state.get("needs_search", False):
return "search"
return "respond"
# Build the graph
workflow = StateGraph(GraphState)
# Add nodes
workflow.add_node("analyze", analyze_query)
workflow.add_node("search", search_documents)
workflow.add_node("respond", generate_response)
# Add edges
workflow.add_edge(START, "analyze") # Entry point
workflow.add_conditional_edges(
"analyze",
route_query,
{
"search": "search",
"respond": "respond"
}
)
workflow.add_edge("search", "respond")
workflow.add_edge("respond", END) # Exit point
# Compile the graph
app = workflow.compile()
# Run the graph
result = app.invoke({
"query": "What is LangGraph?",
"context": "",
"response": "",
"needs_search": False
})
print(result["response"])
from typing import Literal
def should_continue(state: GraphState) -> Literal["continue", "end"]:
"""Decide whether to continue or end the workflow"""
if state["step_count"] >= 5:
return "end"
if state["confidence"] > 0.9:
return "end"
return "continue"
# Add conditional edges
workflow.add_conditional_edges(
"process",
should_continue,
{
"continue": "refine",
"end": END
}
)
def needs_retry(state: GraphState) -> Literal["retry", "success"]:
"""Check if we need to retry the operation"""
if state.get("error") and state["retry_count"] < 3:
return "retry"
return "success"
# Create a loop
workflow.add_conditional_edges(
"execute_tool",
needs_retry,
{
"retry": "execute_tool", # Loop back to the same node
"success": "process_result"
}
)
from langgraph.checkpoint.memory import MemorySaver
# Add persistence for human-in-the-loop
memory = MemorySaver()
app = workflow.compile(checkpointer=memory, interrupt_before=["human_review"])
# Run until human review step
config = {"configurable": {"thread_id": "user_123"}}
result = app.invoke(initial_state, config)
# Human reviews and approves...
# Resume execution
app.invoke(None, config)
For production deployments, LangGraph integrates with LangGraph Cloud (part of LangSmith) for:
| Use Case | LangChain | LangGraph |
|---|---|---|
| Simple Q&A | ✅ | ❌ |
| Linear RAG pipeline | ✅ | ❌ |
| Multi-turn conversations | ⚠️ | ✅ |
| Complex decision trees | ❌ | ✅ |
| Workflows with loops | ❌ | ✅ |
| Human-in-the-loop | ❌ | ✅ |
| Error recovery/fallbacks | ⚠️ | ✅ |
| Stateful agents | ❌ | ✅ |
| Long-running workflows | ❌ | ✅ |
Choose LangGraph when:
┌─────────────────────────────────────────────────────────────────────────┐
│ LangChain vs LangGraph │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ LangChain: Sequential, Linear Workflows │
│ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │
│ Input ──▶ Step 1 ──▶ Step 2 ──▶ Step 3 ──▶ Output │
│ │
│ Best for: Simple chains, basic RAG, straightforward tasks │
│ │
│ ───────────────────────────────────────────────────────────────────────│
│ │
│ LangGraph: Stateful, Graph-Based Workflows │
│ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │
│ │
│ ┌───────┐ │
│ ┌───▶│ Node B│───┐ │
│ │ └───────┘ │ │
│ ┌─────┐│ │┌───────┐ ┌─────┐ │
│ │Start│┤ ├▶│Node D │───▶│ End │ │
│ └─────┘│ ┌───────┐ │└───┬───┘ └─────┘ │
│ └───▶│ Node C│───┘ │ │
│ └───────┘ │ │
│ ▲ │ │
│ └──────────┘ (Loop) │
│ │
│ Best for: Complex agents, multi-step workflows, stateful applications │
│ │
└─────────────────────────────────────────────────────────────────────────┘
# Essential LangGraph imports
from langgraph.graph import StateGraph, START, END
from langgraph.checkpoint.memory import MemorySaver # For persistence
from langgraph.prebuilt import create_react_agent # Pre-built ReAct agent
# State management
from typing import TypedDict, Annotated, Literal
from langgraph.graph.message import add_messages