1. 阶段概述
1.1 学习目标
掌握 LangGraph 1.0 的核心概念,从零构建一个生产级的 ReAct Agent:
- 理解 LangGraph 的状态机编程模型
- 掌握 StateGraph 的节点、边、条件边概念
- 理解 ReAct(Reason + Act)循环的原理与实现
- 掌握 Checkpoint 机制实现会话记忆持久化
- 学会多工具场景下的 Agent 设计
- 对比理解 Java 实现思路
1.2 验证结果
|-------|---------------------------------|------|
| Step | 验证内容 | 结果 |
| Step1 | (15+25)*2 = 80,ReAct 循环 | PASS |
| Step2 | Checkpoint 记忆持久化,同 session 多轮对话 | PASS |
| Step3 | 多工具(日期/天气/计算)并行调用 | PASS |
| Step4 | Java 状态机实现思路 | PASS |
2. LangGraph 核心架构
2.1 什么是 LangGraph
LangGraph 是 LangChain 生态中的图执行引擎 ,用于构建有状态的多步骤 Agent 应用。与 LangChain 0.x 的链式调用不同,LangGraph 将 Agent 建模为一个状态机:
python
LangChain 0.x: Chain = Linear Pipeline
Input → LLM → Output(单次调用)
LangGraph: Graph = State Machine
Nodes (Functions) + Edges (Transitions) + State
支持循环、条件分支、人机交互中断
2.2 核心概念
|----------------------|--------------------|---------------------|
| 概念 | 说明 | 类比 |
| State | 应用状态的快照(TypedDict) | Java Bean / POJO |
| Node | 图中的计算节点(Python 函数) | 微服务 / 函数 |
| Edge | 节点之间的有向边 | API 调用链路 |
| Conditional Edge | 条件边(根据状态决定走向) | if-else 路由 |
| Checkpoint | 状态快照持久化 | 数据库事务日志 |
| thread_id | 会话隔离标识 | HttpSession.getId() |
2.3 工作原理
User Input
│
▼
┌──────────────────────────────────────────────────────────┐
│ app.invoke(input_state, config={"thread_id": "s1"}) │
│ │
│ 1. 从 Checkpoint 恢复 thread_id 的历史状态 │
│ 2. 状态 + 输入 → 节点函数 → 新状态 │
│ 3. 条件边判断 → 选择下一个节点 │
│ 4. 循环直到到达 END 或 remaining_steps=0 │
│ 5. 保存最终状态到 Checkpoint │
└──────────────────────────────────────────────────────────┘
│
▼
Output / Final Response
3. LangGraph 1.0 API 变化
3.1 版本差异速查
LangGraph 从 0.x 升级到 1.0 有大量破坏性变更,学习时需注意版本:
|----------------|------------------------------------|-------------------------------------------------|
| 特性 | LangGraph 0.x | LangGraph 1.0 |
| 创建 Agent | create_react_agent(model, tools) | 手写 StateGraph 或 langchain.agents.create_agent |
| 循环控制 | max_iterations=N 参数 | remaining_steps + 条件边 |
| 状态定义 | 任意 dict | TypedDict + Annotated |
| 消息语义 | 列表替换 | add_messages 语义(追加) |
| tool_calls | tc["function"]["name"] | tc["name"] / tc["args"] |
| Checkpoint | MemorySaver() | 同上,API 稳定 |
3.2 create_react_agent 的废弃
python
# LangGraph 0.x(旧版)
from langchain.agents import create_react_agent
agent = create_react_agent(llm, tools, messages_modifier)
# LangGraph 1.0(新版):无等价 API,需手写状态机
# 正确做法:用 StateGraph 自己搭
3.3 循环控制:remaining_steps
python
# 旧版:用 max_iterations 控制循环次数(已废弃)
agent = create_react_agent(llm, tools, max_iterations=5) # ❌
# 新版:在状态中手动维护 remaining_steps
class AgentState(TypedDict):
messages: Annotated[list, add_messages]
remaining_steps: int # ← 必须自己加到状态里
def model_node(state: AgentState) -> dict:
new_steps = state["remaining_steps"] - 1
response = llm.bind_tools(tools).invoke(state["messages"])
return {"messages": [response], "remaining_steps": new_steps}
4. 状态定义:TypedDict 与 Annotated
4.1 基础状态定义
python
from typing import TypedDict, Annotated
class AgentState(TypedDict):
messages: Annotated[list, add_messages]
remaining_steps: int
4.2 TypedDict vs 普通 dict
|-------------|----------------|---------|
| 特性 | TypedDict | 普通 dict |
| 类型提示 | 有(IDE 友好) | 无 |
| 运行时检查 | 无 | 无 |
| Pydantic 验证 | 无 | 无 |
| 使用场景 | LangGraph 状态定义 | 通用数据容器 |
4.3 Annotated 与 Reducer 语义
Annotated[list, add_messages] 的含义:
Annotated[list, add_messages]
│ │
│ └── Reducer 函数:定义如何合并两次状态
│ add_messages = 追加语义(list.extend)
│ 效果:messages 永远只追加,不覆盖
│
└── 被标注的类型
Reducer 函数对比:
|----------------|----------|---------------|
| Reducer | 效果 | 类比 |
| add_messages | 新消息追加到列表 | List.add() |
| operator.add | 数值相加 | int + int |
| operator.or_ | 集合并集 | Set.union() |
5. 节点函数设计
5.1 节点函数的签名规范
LangGraph 节点函数接收 state(当前状态),返回 dict(状态更新):
python
def model_node(state: AgentState) -> dict:
# state: 当前完整状态
# 返回: 要更新的字段(dict),其他字段不变
return {"messages": [new_message], "remaining_steps": new_steps}
5.2 model_node:LLM 决策节点
python
def model_node(state: AgentState) -> dict:
"""LLM 节点:调用模型,决定是否需要调用工具"""
new_steps = state["remaining_steps"] - 1
# bind_tools 让 LLM 知道有哪些工具可用
response = llm.bind_tools(tools).invoke(state["messages"])
return {
"messages": [response],
"remaining_steps": new_steps
}
5.3 tool_node:工具执行节点
python
def tool_node(state: AgentState) -> dict:
"""工具节点:执行 LLM 请求的每个工具调用"""
messages = state["messages"]
last_msg = messages[-1]
if not isinstance(last_msg, AIMessage) or not last_msg.tool_calls:
return {"messages": []}
results = []
for tc in last_msg.tool_calls:
tool_name = tc["name"] # LangGraph 1.0 格式
tool_args = tc["args"] # LangGraph 1.0 格式
matched_tool = next((t for t in tools if t.name == tool_name), None)
if matched_tool:
result = matched_tool.invoke(tool_args)
results.append(ToolMessage(
content=str(result),
tool_call_id=tc["id"],
name=tool_name
))
return {"messages": results}
5.4 should_continue:条件边函数
python
def should_continue(state: AgentState) -> str:
"""条件边:决定 model 节点后下一步走哪里"""
if state["remaining_steps"] <= 0:
return "end"
last_msg = state["messages"][-1]
if isinstance(last_msg, AIMessage) and last_msg.tool_calls:
return "continue" # 有工具调用 → 继续
return "end" # 无工具调用 → 结束
6. 图的构建:StateGraph API
6.1 构建流程
python
StateGraph(AgentState)
│
├── add_node("model", model_node) # 注册节点
├── add_node("tools", tool_node)
│
├── add_edge(START, "model") # 入口边
│
├── add_conditional_edges( # 条件边
│ "model",
│ should_continue,
│ {"continue": "tools", "end": END}
│ )
│
└── add_edge("tools", "model") # 循环边
│
▼
builder.compile(checkpointer=...)
6.2 完整图结构
┌─────────────────┐
│ START │
└────────┬────────┘
│
▼
┌─────────────────┐
│ model │ ◄── LLM 决策节点
└────────┬────────┘
│
┌──────────────┴──────────────┐
│ should_continue() 条件判断 │
└──────────────┬──────────────┘
┌────────┴────────┐
│ │
"continue" "end"
│ │
▼ ▼
┌───────────┐ ┌───────┐
│ tools │ │ END │
└─────┬─────┘ └───────┘
│
▼
┌───────────┐
│ model │ ──────► (循环直到无 tool_calls)
└───────────┘
6.3 特殊节点:START 和 END
python
from langgraph.graph import START, END
print(START) # "__start__" 入口标记
print(END) # "__end__" 结束标记
# START 是虚拟入口,不需要定义,但必须有一条边从 START 出发
# END 是虚拟终点,所有路径最终都应到达 END
7. Checkpoint 机制
7.1 概念
Checkpoint 是 LangGraph 的状态快照持久化机制,类似于数据库的 WAL(Write-Ahead Log):
|------------------|-----------------------|
| 组件 | 作用 |
| Checkpoint | 某时刻的完整状态快照 |
| thread_id | 会话隔离标识,类似 HttpSession |
| Checkpointer | 持久化策略(内存/文件/数据库) |
7.2 MemorySaver
python
from langgraph.checkpoint.memory import MemorySaver
checkpointer = MemorySaver()
app = builder.compile(checkpointer=checkpointer)
# 调用时指定 thread_id
result = app.invoke(
input_state,
config={"configurable": {"thread_id": "user_001"}}
)
7.3 Checkpoint 存储层级
app.invoke(state, config={"configurable": {"thread_id": "s1"}})
thread_id="s1"
│
▼
┌─────────────────────────────────────────┐
│ Checkpointer │
│ ┌─────────────────────────────────┐ │
│ │ thread_id: "s1" │ │
│ │ checkpoint: {messages:[...], │ │
│ │ remaining_steps:8} │ │
│ └─────────────────────────────────┘ │
│ ┌─────────────────────────────────┐ │
│ │ thread_id: "s2" │ │
│ │ checkpoint: {...} │ │
│ └─────────────────────────────────┘ │
└─────────────────────────────────────────┘
8. 多工具调用
8.1 工具定义
python
from langchain_core.tools import tool
@tool
def calculator(expression: str) -> str:
"""数学计算器"""
try:
result = eval(expression, {"__builtins__": {}}, {})
return f"计算结果:{result}"
except Exception as e:
return f"计算错误:{e}"
@tool
def get_weather(city: str) -> str:
"""获取城市天气(模拟数据)"""
weather_db = {
"Hangzhou": "Sunny, 25C",
"Beijing": "Cloudy, 22C",
}
return weather_db.get(city, f"{city} 天气数据不可用")
tools = [calculator, get_weather]
8.2 @tool 装饰器做了什么
Python 函数 ──@tool──► StructuredTool 对象
│
├── name: 函数名
├── description: 文档字符串
└── args_schema: Pydantic Model(从类型注解生成)
8.3 多工具调用执行路径
python
User: "今天日期?杭州天气?12+34?"
│
▼
┌──────────────────────────────────────────┐
│ model_node(): │
│ LLM 决定并行调用 3 个工具 │
│ 输出: AIMessage(tool_calls=[ │
│ {name:"date_today", args:{}}, │
│ {name:"get_weather", args:{"city": │
│ "Hangzhou"}}, │
│ {name:"calculator", args:{"expressi- │
│ on":"12+34"}} │
│ ]) │
└──────────────────┬───────────────────────┘
│ tool_calls
▼
┌──────────────────────────────────────────┐
│ tool_node(): │
│ 遍历 3 个 tool_call │
│ → date_today.invoke({}) → "2026-05-21"│
│ → get_weather.invoke({city:"..."}) │
│ → calculator.invoke({expression:"12+34"})
│ │
│ 输出: [ToolMessage, ToolMessage, Tool- │
│ Message] │
└──────────────────┬───────────────────────┘
│ ToolMessages
▼
回到 model_node()
LLM 整合结果,返回最终回答
9. Java 对比实现
9.1 核心思路
Java 没有 LangGraph,但可以用设计模式实现类似的状态机:
|-----------------------|----------------------|
| LangGraph | Java 实现 |
| StateGraph | 自定义 StateMachine 类 |
| Node 函数 | Command / Handler 接口 |
| add_edge | 状态转换表(Map) |
| add_conditional_edges | if-else 或策略模式 |
| remaining_steps | AtomicInteger 递减计数器 |
| Checkpoint | Redis Hash / 数据库 |
| thread_id | HttpSession.getId() |
9.2 简化实现思路
java
// 状态
public class AgentState {
public List<Message> messages;
public int remainingSteps;
}
// 节点接口
public interface Node {
AgentState process(AgentState state);
}
// LLM 节点(类似 model_node)
public class ModelNode implements Node {
@Override
public AgentState process(AgentState state) {
// 1. 构建消息历史
// 2. 调用 LLM API(HTTP)
// 3. 解析响应中的 tool_calls
// 4. remainingSteps--
return newState;
}
}
// 状态机
public class AgentStateMachine {
private Map<String, Node> nodes = new HashMap<>();
private Map<String, Function<AgentState, String>> transitions = new HashMap<>();
public void addNode(String name, Node node) { ... }
public void addEdge(String from, String to) { ... }
public void addConditionalEdge(String from, Function<AgentState, String> condition) { ... }
public AgentState run(AgentState initial, String threadId) {
// 1. 从 Redis/H2 恢复 checkpoint(threadId)
// 2. 循环直到 terminal 状态
// 3. 保存 checkpoint
return result;
}
}
10. 生产环境持久化方案
10.1 方案对比
|---------------------|-------|---------|---------|-------|
| 方案 | 存储 | 优点 | 缺点 | 推荐场景 |
| MemorySaver | 内存 | 零配置、极快 | 进程重启丢失 | 开发调试 |
| SQLiteSaver | 文件 | 零依赖、持久化 | 并发差 | 单机项目 |
| PostgreSQLSaver | 数据库 | 生产级、并发 | 需数据库 | 生产环境 |
| RedisSaver | Redis | 高性能、分布式 | 需 Redis | 高并发项目 |
10.2 PostgreSQL 连接池版本(推荐生产)
python
import psycopg2
from psycopg2 import pool
from langgraph.checkpoint.postgres import PostgresSaver
class PostgresConnectionManager:
_instance = None
_connection_pool = None
def initialize(self, host, port, database, user, password,
min_connections=5, max_connections=20):
self._connection_pool = psycopg2.pool.ThreadedConnectionPool(
minconn=min_connections,
maxconn=max_connections,
host=host, port=port, database=database,
user=user, password=password
)
def get_pool(self):
return self._connection_pool
# 使用
DB_CONFIG = {
"host": "localhost",
"port": 5432,
"database": "langgraph_db",
"user": "postgres",
"password": "your_password",
"min_connections": 5,
"max_connections": 20
}
pool_manager = PostgresConnectionManager()
pool_manager.initialize(**DB_CONFIG)
checkpointer = PostgresSaver(pool_manager.get_pool())
checkpointer.setup() # 首次建表
app = builder.compile(checkpointer=checkpointer)
11. 常见错误与教训
11.1 LangGraph 1.0 API 破坏性变更
错误 : 使用 create_react_agent(已废弃)
# 报错:AttributeError 或参数不存在
agent = create_react_agent(model, tools, max_iterations=5) # ❌
# 正确:手写 StateGraph
builder = StateGraph(AgentState) # ✅
11.2 忘记添加 remaining_steps
# 报错:节点返回的 state 缺少 remaining_steps
app.invoke({"messages": [...], "remaining_steps": 10}) # ✅
app.invoke({"messages": [...]}) # ❌ 无 remaining_steps
11.3 tool_calls 格式变更
# LangGraph 0.x(旧格式)
tc["function"]["name"] # ❌
tc["function"]["arguments"]
# LangGraph 1.0(新格式)
tc["name"] # ✅
tc["args"] # ✅
11.4 Windows emoji 打印问题
# Windows cmd 下 LLM 输出可能含 emoji,打印时报错
# UnicodeEncodeError: 'gbk' codec can't encode character
# 解决:过滤非 ASCII 字符
import re
def safe_print(text):
text = re.sub(r'[\U0001F300-\U0001F9FF\U00002600-\U000026FF]', '', str(text))
print(text)