本页还没有译文
本页 101 段还没译成Español,下面显示的是英文原文。Thinking in LangGraph
还没翻译译自英文(docs.langchain.com)本页 101 段都还没译
When you build an agent with LangGraph, you will first break it apart into discrete steps called nodes. Then, you will describe the different decisions and transitions from each of your nodes. Finally, you connect nodes together through a shared state that each node can read from and write to.
In this walkthrough, we'll guide you through the thought process of building a customer support email agent with LangGraph.
Start with the process you want to automate
Imagine that you need to build an AI agent that handles customer support emails. Your product team has given you these requirements:
The agent should:
- Read incoming customer emails
- Classify them by urgency and topic
- Search relevant documentation to answer questions
- Draft appropriate responses
- Escalate complex issues to human agents
- Schedule follow-ups when needed
Example scenarios to handle:
1. Simple product question: "How do I reset my password?"
2. Bug report: "The export feature crashes when I select PDF format"
3. Urgent billing issue: "I was charged twice for my subscription!"
4. Feature request: "Can you add dark mode to the mobile app?"
5. Complex technical issue: "Our API integration fails intermittently with 504 errors"To implement an agent in LangGraph, you will usually follow the same five steps.
Step 1: Map out your workflow as discrete steps
Start by identifying the distinct steps in your process. Each step will become a node (a function that does one specific thing). Then, sketch how these steps connect to each other.
flowchart TD
A[START] --> B[Read Email]
B --> C[Classify Intent]
C -.-> D[Doc Search]
C -.-> E[Bug Track]
C -.-> F[Human Review]
D --> G[Draft Reply]
E --> G
F --> G
G -.-> H[Human Review]
G -.-> I[Send Reply]
H --> J[END]
I --> J[END]
classDef process fill:#E5F4FF,stroke:#006DDD,stroke-width:2px,color:#030710
class A,B,C,D,E,F,G,H,I,J processThe arrows in this diagram show possible paths, but the actual decision of which path to take happens inside each node.
Now that we've identified the components in our workflow, let's understand what each node needs to do:
Read Email: Extract and parse the email contentClassify Intent: Use an LLM to categorize urgency and topic, then route to appropriate actionDoc Search: Query your knowledge base for relevant informationBug Track: Create or update issue in tracking systemDraft Reply: Generate an appropriate responseHuman Review: Escalate to human agent for approval or handlingSend Reply: Dispatch the email response
Tip
Notice that some nodes make decisions about where to go next (Classify Intent, Draft Reply, Human Review), while others always proceed to the same next step (Read Email always goes to Classify Intent, Doc Search always goes to Draft Reply).
Step 2: Identify what each step needs to do
For each node in your graph, determine what type of operation it represents and what context it needs to work properly.
Use when you need to understand, analyze, generate text, or make reasoning decisions
Use when you need to retrieve information from external sources
Use when you need to perform external actions
Use when you need human intervention
LLM steps
When a step needs to understand, analyze, generate text, or make reasoning decisions:
Data steps
When a step needs to retrieve information from external sources:
Action steps
When a step needs to perform an external action:
User input steps
When a step needs human intervention:
Step 3: Design your state
State is the shared memory accessible to all nodes in your agent. Think of it as the notebook your agent uses to keep track of everything it learns and decides as it works through the process.
What belongs in state?
Ask yourself these questions about each piece of data:
Include in state
Does it need to persist across steps? If yes, it goes in state.
Don't store
Can you derive it from other data? If yes, compute it when needed instead of storing it in state.
For our email agent, we need to track:
- The original email and sender info (can't reconstruct these later)
- Classification results (needed by multiple later/downstream nodes)
- Search results and customer data (expensive to re-fetch)
- The draft response (needs to persist through review)
- Execution metadata (for debugging and recovery)
Keep state raw, format prompts on-demand
Tip
A key principle: your state should store raw data, not formatted text. Format prompts inside nodes when you need them.
This separation means:
- Different nodes can format the same data differently for their needs
- You can change prompt templates without modifying your state schema
- Debugging is clearer—you see exactly what data each node received
- Your agent can evolve without breaking existing state
Let's define our state:
from typing import TypedDict, Literal
# Define the structure for email classification
class EmailClassification(TypedDict):
intent: Literal["question", "bug", "billing", "feature", "complex"]
urgency: Literal["low", "medium", "high", "critical"]
topic: str
summary: str
class EmailAgentState(TypedDict):
# Raw email data
email_content: str
sender_email: str
email_id: str
# Classification result
classification: EmailClassification | None
# Raw search/API results
search_results: list[str] | None # List of raw document chunks
customer_history: dict | None # Raw customer data from CRM
# Generated content
draft_response: str | None
messages: list[str] | NoneNotice that the state contains only raw data—no prompt templates, no formatted strings, no instructions. The classification output is stored as a single dictionary, straight from the LLM.
Step 4: Build your nodes
Now we implement each step as a function. A node in LangGraph is just a Python function that takes the current state and returns updates to it.
Handle errors appropriately
Different errors need different handling strategies:
| Error Type | Who Fixes It | Strategy | When to Use |
|---|---|---|---|
| Transient errors (network issues, rate limits) | System (automatic) | Retry policy | Temporary failures that usually resolve on retry |
| LLM-recoverable errors (tool failures, parsing issues) | LLM | Store error in state and loop back | LLM can see the error and adjust its approach |
| User-fixable errors (missing information, unclear instructions) | Human | Pause with interrupt() | Need user input to proceed |
| Recoverable failure after retries | Developer (declarative) | error_handler | Run a compensation/recovery branch after retry exhaustion |
| Unexpected errors | Developer | Let them bubble up | Unknown issues that need debugging |
Add a retry policy to automatically retry network issues and rate limits.
Combine with timeout= to cap each attempt. See Fault tolerance for the full lifecycle.
from langgraph.types import RetryPolicy
workflow.add_node(
"search_documentation",
search_documentation,
retry_policy=RetryPolicy(max_attempts=3, initial_interval=1.0)
)Store the error in state and loop back so the LLM can see what went wrong and try again:
from langgraph.types import Command
def execute_tool(state: State) -> Command[Literal["agent", "execute_tool"]]:
try:
result = run_tool(state['tool_call'])
return Command(update={"tool_result": result}, goto="agent")
except ToolError as e:
# Let the LLM see what went wrong and try again
return Command(
update={"tool_result": f"Tool error: {str(e)}"},
goto="agent"
)Pause and collect information from the user when needed (like account IDs, order numbers, or clarifications):
from langgraph.types import Command
def lookup_customer_history(
state: State
) -> Command[Literal["lookup_customer_history", "draft_response"]]:
if not state.get('customer_id'):
user_input = interrupt({
"message": "Customer ID needed",
"request": "Please provide the customer's account ID to look up their subscription history"
})
return Command(
update={"customer_id": user_input['customer_id']},
goto="lookup_customer_history"
)
# Now proceed with the lookup
customer_data = fetch_customer_history(state['customer_id'])
return Command(update={"customer_history": customer_data}, goto="draft_response")Let them bubble up for debugging. Don't catch what you can't handle:
def send_reply(state: EmailAgentState):
try:
email_service.send(state["draft_response"])
except Exception:
raise # Surface unexpected errorsAfter retries are exhausted, run a recovery function that updates state and routes to a compensation branch.
See Fault tolerance for the full pattern.
Note
error_handler requires langgraph>=1.2.
from langgraph.errors import NodeError
from langgraph.types import Command, RetryPolicy
def payment_error_handler(state: State, error: NodeError) -> Command:
return Command(
update={"status": f"compensated: {error.error}"},
goto="finalize",
)
workflow.add_node(
"charge_payment",
charge_payment,
retry_policy=RetryPolicy(max_attempts=3, retry_on=ConnectionError),
error_handler=payment_error_handler,
)To apply the same retry_policy, timeout, or error_handler to every node in a graph without repeating them on each add_node, use StateGraph.set_node_defaults(...). Per-node values still take precedence. See Fault tolerance.
Implementing our email agent nodes
We'll implement each node as a simple function. Remember: nodes take state, do work, and return updates.
Step 5: Wire it together
Now we connect our nodes into a working graph. Since our nodes handle their own routing decisions, we only need a few essential edges.
To enable human-in-the-loop with interrupt(), we need to compile with a checkpointer to save state between runs:
from langgraph.checkpoint.memory import MemorySaver
from langgraph.types import RetryPolicy
# Create the graph
workflow = StateGraph(EmailAgentState)
# Add nodes with appropriate error handling
workflow.add_node("read_email", read_email)
workflow.add_node("classify_intent", classify_intent)
# Add retry policy for nodes that might have transient failures
workflow.add_node(
"search_documentation",
search_documentation,
retry_policy=RetryPolicy(max_attempts=3)
)
workflow.add_node("bug_tracking", bug_tracking)
workflow.add_node("draft_response", draft_response)
workflow.add_node("human_review", human_review)
workflow.add_node("send_reply", send_reply)
# Add only the essential edges
workflow.add_edge(START, "read_email")
workflow.add_edge("read_email", "classify_intent")
workflow.add_edge("send_reply", END)
# Compile with checkpointer for persistence, in case run graph with Local_Server --> Please compile without checkpointer
memory = MemorySaver()
app = workflow.compile(checkpointer=memory)
The graph structure is minimal because routing happens inside nodes through Command objects. Each node declares where it can go using type hints like Command[Literal["node1", "node2"]], making the flow explicit and traceable.
Try out your agent
Let's run our agent with an urgent billing issue that needs human review:
The graph pauses when it hits interrupt(), saves everything to the checkpointer, and waits. It can resume days later, picking up exactly where it left off. The thread_id ensures all state for this conversation is preserved together.
Summary and next steps
Key Insights
Building this email agent has shown us the LangGraph way of thinking:
Each node does one thing well. This decomposition enables streaming progress updates, durable execution that can pause and resume, and clear debugging since you can inspect state between steps.
Store raw data, not formatted text. This lets different nodes use the same information in different ways.
They take state, do work, and return updates. When they need to make routing decisions, they specify both the state updates and the next destination.
Transient failures get retries, LLM-recoverable errors loop back with context, user-fixable problems pause for input, and unexpected errors bubble up for debugging.
The interrupt() function pauses execution indefinitely, saves all state, and resumes exactly where it left off when you provide input. When combined with other operations in a node, it must come first.
Graph structure emerges naturally
You define the essential connections, and your nodes handle their own routing logic. This keeps control flow explicit and traceable - you can always understand what your agent will do next by looking at the current node.
Advanced considerations
Where to go from here
This was an introduction to thinking about building agents with LangGraph. You can extend this foundation with:
Learn how to add tool approval before execution, batch approval, and other patterns
Create subgraphs for complex multi-step operations
Add streaming to show real-time progress to users
Add observability with LangSmith for debugging and monitoring
Integrate more tools for web search, database queries, and API calls
Implement retry logic with exponential backoff for failed operations