LangGraph 工程化实战:图状态机驱动的复杂 AI 工作流
适用版本:LangGraph 1.0+(兼容 0.3.x 核心 API)
技术栈:Python 3.10+ / LangGraph / LangChain / SQLite / Redis
定位:聚焦图结构工作流设计与生产级实践
1. LangGraph 架构全景
1.1 核心定义
LangGraph = LangChain + 图编排 + 状态机
基于 LangChain 构建的、面向智能体多轮交互 / 状态持久化 / 分支并行执行的图结构工作流框架。
1.2 为什么需要 LangGraph
LangChain 生态的两个困境:
| 组件 | 痛点 |
|---|---|
| Chain | 线性流水线,无法优雅处理循环和条件分支。"返回上一步"或"条件跳转"需要大量胶水代码 |
| Agent | 黑箱式决策,难以精细化控制。相同问题可能走不同路径,行为不稳定,调试困难,Token 成本不可控 |
LangGraph 的解决方案 :将工作流抽象为有向图,节点定义操作步骤,边定义控制流,精确控制执行逻辑。
1.3 核心能力
- 条件分支、循环、并行执行
- 状态持久化、断点续跑
- 时间回溯(Time-Travel)
- 人机协作(Human-in-the-Loop)
- 多智能体协作、层级架构
- 节点缓存与错误重试
1.4 与 LangChain 的关系
LangGraph 是 LangChain 工作流的高级编排层。无论图结构多复杂,单条执行链路仍是线性的,底层仍然依赖 LangChain 的 Chain 来实现。两者关系:
LangChain → 基础能力层(模型调用、Prompt、Tool、Memory)
LangGraph → 流程编排层(图结构、状态机、多智能体协调)
2. 核心四要素
LangGraph 的灵魂:State(状态)+ Nodes(节点)+ Edges(边)+ Graph(图)
2.1 最小可运行示例
python
from typing import TypedDict
from langgraph.graph import StateGraph, START, END
# 1. 定义 State
class HelloState(TypedDict):
name: str
greeting: str
# 2. 定义 Nodes
def greet(state: HelloState) -> dict:
return {"greeting": f"你好, {state['name']}"}
def add_emoji(state: HelloState) -> dict:
return {"greeting": state["greeting"] + " 🎉"}
# 3. 构建 Graph
graph = StateGraph(HelloState)
graph.add_node("greeting", greet)
graph.add_node("add_emoji", add_emoji)
graph.add_edge(START, "greeting")
graph.add_edge("greeting", "add_emoji")
graph.add_edge("add_emoji", END)
# 4. 编译 & 运行
app = graph.compile()
result = app.invoke({"name": "z3"})
print(result["greeting"]) # "你好, z3 🎉"
2.2 图的构建流程
scss
1. 初始化 StateGraph 实例(指定 State Schema)
2. add_node() 添加节点
3. add_edge() / add_conditional_edges() 定义边
4. 设置入口和出口(START / END)
5. compile() 编译图(校验边与节点的完整性)
6. invoke() / stream() 执行工作流
2.3 图可视化
scss
# ASCII 可视化(控制台直接查看)
print(app.get_graph().print_ascii())
# Mermaid 代码(可粘贴到 processon.com/mermaid 编辑器查看)
print(app.get_graph().draw_mermaid())
# 生成 PNG 文件(依赖网络,稳定性一般)
png_bytes = app.get_graph().draw_mermaid_png(max_retries=3, retry_delay=2.0)
with open("graph.png", "wb") as f:
f.write(png_bytes)
3. Graph API 深度解析
3.1 State(状态)
State 是贯穿整个工作流的共享数据结构,存储从开始到结束的所有信息(历史对话、工具结果、文档等),各节点均可读写。
3.1.1 Schema 定义
| 要素 | 说明 |
|---|---|
state_schema |
图的完整内部状态,所有节点可读写的字段,必须指定 |
input_schema |
图接受的输入,state_schema 的子集(可选,默认=state_schema) |
output_schema |
图返回的输出,state_schema 的子集(可选,默认=state_schema) |
选型建议:
- TypedDict:轻量、无运行时开销,适合字段简单的场景
- pydantic.BaseModel:自动校验、默认值、嵌套结构、字段描述,适合复杂场景
python
from langgraph.graph import StateGraph, START, END
from typing_extensions import TypedDict
# 输入/输出分离设计
class InputState(TypedDict):
question: str
class OutputState(TypedDict):
answer: str
class OverallState(InputState, OutputState):
internal_data: str # 私有中间状态
builder = StateGraph(OverallState,
input_schema=InputState,
output_schema=OutputState)
3.1.2 Reducer(状态合并策略)
Reducer 决定节点返回的增量如何合并到全局 State。每个字段可独立配置 Reducer。
| Reducer | 行为 | 适用场景 |
|---|---|---|
| 默认(无) | 直接覆盖旧值 | 普通字段更新 |
add_messages |
追加消息到列表(消息专用) | 多轮对话历史 |
operator.add |
列表追加 / 字符串拼接 / 数值累加 | 收集多路结果 |
operator.mul |
数值相乘(注意初始值陷阱) | 概率计算 |
| 自定义函数 | 完全自定义合并逻辑 | 复杂业务规则 |
python
from typing import Annotated, List
from langgraph.graph.message import add_messages
import operator
class ChatState(TypedDict):
# 消息历史:追加而非覆盖
messages: Annotated[list, add_messages]
# 标签列表:追加合并
tags: Annotated[List[str], operator.add]
# 累计分数
score: Annotated[float, operator.add]
关键陷阱:operator.mul 初始值问题
LangGraph 在执行第一个节点前会调用一次 Reducer,用类型默认值(float → 0.0)与 invoke 传入值运算。对于乘法:0.0 * 5.0 = 0.0,导致结果始终为 0。
解决方案:自定义 Reducer
sql
def safe_mul(current: float, update: float) -> float:
if current == 0.0: # 首次调用,用乘法恒等元 1.0
return 1.0 * update
return current * update
class MultiplyState(TypedDict):
factor: Annotated[float, safe_mul]
3.2 Node(节点)
Node 是 LangGraph 中的基本处理单元,绑定一个 Python 函数,可以是 Agent、LLM 调用、工具执行或任意逻辑。
3.2.1 设计原则
- 单一职责:每个节点只负责一项任务
- 无状态设计:节点不保存状态,所有数据通过 State 传递
- 幂等性:相同输入产生相同输出,确保可重试
- 可测试性:节点逻辑应易于单元测试
3.2.2 节点缓存
基于节点输入做缓存,跳过重复计算:
python
from langgraph.cache.memory import InMemoryCache
from langgraph.types import CachePolicy
# 编译时指定缓存策略
app = builder.compile(cache=InMemoryCache())
# 添加节点时指定缓存策略(ttl 单位:秒)
builder.add_node(
"expensive_node", expensive_func,
cache_policy=CachePolicy(ttl=60) # 60秒内相同输入直接返回缓存
)
3.2.3 错误重试
python
from langgraph.types import RetryPolicy
# 默认重试策略:max_attempts=5,对 Exception 重试,
# 不对 ValueError/TypeError/ImportError 等重试
builder.add_node("api_node", api_func,
retry_policy=RetryPolicy(max_attempts=5))
# 自定义重试条件
def should_retry(exc: Exception) -> bool:
return "API 调用失败" in str(exc)
builder.add_node("api_node", api_func,
retry_policy=RetryPolicy(max_attempts=5, retry_on=should_retry))
3.3 Edge(边)
Edge 定义节点之间的连接和执行顺序。
3.3.1 普通边(Normal Edge)
无条件从一个节点跳转到另一个节点:
sql
builder.add_edge(START, "node_a")
builder.add_edge("node_a", "node_b")
builder.add_edge("node_b", END)
3.3.2 条件边(Conditional Edge)
根据路由函数的返回值动态决定下一个节点:
python
def route_by_type(state: MyState) -> str:
if state["x"] == 1: return "condition_1"
elif state["x"] == 2: return "condition_2"
else: return "condition_3"
builder.add_conditional_edges(
"router_node", # 源节点
route_by_type, # 路由函数
{
"condition_1": "node_1",
"condition_2": "node_2",
"condition_3": "node_3"
}
)
3.3.3 入口点 & 条件入口点
sql
# 固定入口(语法糖,等价于 add_edge(START, "node_a"))
builder.set_entry_point("node_a")
builder.set_finish_point("node_b")
# 条件入口:根据输入动态选择起始节点
builder.add_conditional_edges(
START,
route_function,
{"greeting": "greeting_node", "question": "question_node"}
)
4. 高级控制机制
4.1 Send:Map-Reduce 并行
场景:动态创建多个执行分支,并行处理,最终汇总结果。
python
from langgraph.types import Send
from typing import Sequence
# 条件边函数:为每个主题创建一个并行任务
def map_tasks(state: OverallState) -> Sequence[Send]:
return [
Send("process_node", {"subject": subject})
for subject in state["subjects"]
]
# 注册条件边
builder.add_conditional_edges("generator", map_tasks)
builder.add_edge("process_node", END) # 所有并行任务完成后结束
Send 参数:第一个是目标节点名,第二个是传递给该节点的 State。
4.2 Command:状态更新 + 路由一体
在同一个节点中既更新状态,又决定下一步路由,常用于多智能体交接(Handoffs)和人机协作(HITL):
ini
from langgraph.types import Command
def decision_agent(state: AgentState) -> Command[AgentState]:
if state["task_completed"]:
return Command(
update={"messages": [("system", "任务完成")]},
goto=END
)
if "数学" in last_message:
return Command(
update={"current_agent": "math_agent"},
goto="math_agent"
)
return Command(
update={"task_completed": True},
goto=END
)
Command vs 条件边:
| 维度 | 条件边 | Command |
|---|---|---|
| 路由 | 只路由下一个节点 | 路由 + 状态更新 |
| 适用 | 纯流程控制 | 需要同时更新状态和路由 |
| 场景 | 简单分支 | 多智能体交接、HITL |
4.3 Runtime Context:运行时上下文
将不属于图状态的配置信息(模型名称、数据库连接、API Key 等)注入节点,实现配置与代码分离:
python
from dataclasses import dataclass
from langgraph.runtime import Runtime
@dataclass
class ContextSchema:
model_name: str
db_connection: str
api_key: str
# 节点函数通过 runtime 参数访问上下文
def process_node(state: State, runtime: Runtime[ContextSchema]) -> dict:
model = runtime.context.model_name
db = runtime.context.db_connection
return {"response": f"使用 {model} 处理完成"}
# 创建图时指定 context_schema
builder = StateGraph(State, context_schema=ContextSchema)
# 执行时传入 context
context = ContextSchema(model_name="gpt-4", db_connection="...", api_key="sk-xxx")
result = graph.invoke(initial_state, context=context)
5. 流式处理 Streaming
LangGraph 的流式传输比 LangChain 更丰富------不仅能看 LLM 逐字输出,还能实时看到流程状态。
5.1 流模式一览
| stream_mode | 输出内容 | 场景 |
|---|---|---|
values |
每步执行后的完整状态 | 监控全局进度 |
updates |
每步执行后的增量更新 | 只看变化 |
messages |
LLM 逐 token 输出 + 元数据 | 打字机效果 |
custom |
自定义数据(进度提示等) | 业务级进度推送 |
debug |
所有细节(调试用) | 开发调试 |
5.2 核心用法
ini
# 单模式
for chunk in graph.stream(input_state, stream_mode="updates"):
print(chunk)
# 多模式并存
for mode, chunk in graph.stream(input_state,
stream_mode=["values", "updates"]):
print(f"[{mode}]: {chunk}")
# LLM token 流式输出
for chunk, metadata in graph.stream(input_state, stream_mode="messages"):
print(chunk.content, end="") # metadata 包含节点名和 LLM 调用详情
5.3 自定义数据流
在节点内部通过 get_stream_writer 发送自定义数据:
arduino
from langgraph.config import get_stream_writer
def my_node(state: State):
writer = get_stream_writer()
writer({"progress": "步骤 1: 分析查询", "status": "running"})
# ... 业务处理 ...
writer({"progress": "步骤 2: 生成结果", "status": "completed"})
return {"answer": result}
# 消费自定义流
for chunk in graph.stream(inputs, stream_mode="custom"):
print(chunk) # {"progress": "...", "status": "..."}
6. 状态持久化 Persistence
6.1 核心概念
每个工作流步骤执行后,系统将完整状态保存为检查点(Checkpoint) ,通过 thread_id(会话ID,非操作系统线程)区分不同会话。
两种记忆机制:
| 类型 | 载体 | 作用 | 生命周期 |
|---|---|---|---|
| 短期记忆 | Checkpointer(MemorySaver / RedisSaver / PostgresSaver) | 保存图的运行状态,按 thread_id 续写 | 同一会话内 |
| 长期记忆 | BaseStore(InMemoryStore / RedisStore / PostgresStore) | 保存用户偏好、背景事实,支持向量检索 | 跨会话 |
6.2 内存版(开发测试)
ini
from langgraph.checkpoint.memory import InMemorySaver
graph = builder.compile(checkpointer=InMemorySaver())
config = {"configurable": {"thread_id": "user-001"}}
result = graph.invoke({"messages": ["开始"]}, config)
# 查看保存的状态
saved_state = graph.get_state(config)
print(saved_state.values)
print(saved_state.next) # 下一个待执行节点
# 获取完整执行历史
for checkpoint in graph.get_state_history(config):
print(checkpoint.values)
6.3 SQLite 版(本地生产)
pip install langgraph-checkpoint-sqlite
ini
import sqlite3
from langgraph.checkpoint.sqlite import SqliteSaver
conn = sqlite3.connect("checkpoints.db", check_same_thread=False)
graph = builder.compile(checkpointer=SqliteSaver(conn=conn))
config = {"configurable": {"thread_id": "user-001"}}
result = graph.invoke({"messages": []}, config)
6.4 持久化后端选型
| 后端 | 包名 | 适用场景 |
|---|---|---|
| InMemorySaver | langgraph(内置) |
本地测试 |
| SqliteSaver | langgraph-checkpoint-sqlite |
单机生产 |
| PostgresSaver | langgraph-checkpoint-postgres |
分布式生产 |
| RedisSaver | langgraph-checkpoint-redis |
高性能需求 |
6.5 预构建 Agent + 记忆
arduino
from langchain.agents import create_agent
from langgraph.checkpoint.memory import InMemorySaver
agent = create_agent(
model=llm,
checkpointer=InMemorySaver() # 生产换 RedisSaver/PostgresSaver
)
config = {"configurable": {"thread_id": "user-001"}}
agent.invoke({"messages": [("user", "我叫张三")]}, config)
agent.invoke({"messages": [("user", "我叫什么?")]}, config) # 能记住
7. 时间回溯 Time-Travel
7.1 核心价值
在非确定性系统中,回溯、检查、修改历史状态并从某个历史节点重新执行:
- 调试:查看 agent 在某个历史状态下的决策
- 修复:发现错误后回到该步骤,走另一条路径
- 探索:从同一历史点分叉出多个可能结果(what-if 实验)
- 人类反馈(HITL) :用户拒绝工具调用后,退回到之前状态重新走
7.2 实现步骤
ini
# 1. 运行图,生成检查点
config = {"configurable": {"thread_id": str(uuid.uuid4())}}
story = graph.invoke({}, config)
# 2. 查看历史状态,找到目标检查点
history = list(graph.get_state_history(config))
target_checkpoint = history[2] # 选择特定历史节点
# 3.(可选)修改状态,创建分支
new_config = graph.update_state(
target_checkpoint.config,
values={"character": "新角色"} # 修改历史状态
)
# 4. 从检查点恢复执行(产生新分支)
new_story = graph.invoke(None, new_config)
8. 子图 Subgraphs
8.1 核心价值
将一个完整的图作为另一个图的节点,适合:
- 复杂任务拆解为多个专业智能体
- 各子图独立开发、测试、复用
- 子图可拥有私有数据,也可与父图共享数据
8.2 方式一:直接添加子图为节点(共享 State 字段)
makefile
# 子图
sub_builder = StateGraph(SubState)
sub_builder.add_node("sub_node", sub_func)
sub_builder.add_edge(START, "sub_node")
sub_builder.add_edge("sub_node", END)
subgraph = sub_builder.compile()
# 父图:直接将子图添加为一个节点
parent_builder = StateGraph(ParentState)
parent_builder.add_node("subgraph_node", subgraph) # 子图作为节点
parent_builder.add_edge(START, "subgraph_node")
parent_builder.add_edge("subgraph_node", END)
状态合并行为 :子图执行时会触发两次 Reducer 合并(父→子、子→父),使用 operator.add 类 Reducer 时需注意重复数据。
8.3 方式二:代理节点模式(父子图 State 不同时,推荐)
当父子图状态结构完全不同时,通过代理节点手动转换:
python
# 父图状态
class ParentState(TypedDict):
user_query: str
final_answer: str | None
# 子图状态(完全不同的字段)
class SubgraphState(TypedDict):
analysis_input: str
analysis_result: str
# 代理节点:手动完成 父→子转换 + 调用子图 + 子→父映射
def call_subgraph_proxy(state: ParentState) -> ParentState:
# 步骤1:父状态 → 子输入
sub_input = {"analysis_input": state["user_query"], "analysis_result": ""}
# 步骤2:手动调用子图
sub_result = compiled_subgraph.invoke(sub_input)
# 步骤3:子输出 → 父状态
return {"final_answer": sub_result["analysis_result"]}
# 父图添加代理节点(而非直接添加子图)
parent_builder.add_node("proxy", call_subgraph_proxy)
9. 多智能体架构 A2A
9.1 MCP vs A2A:两个维度的协议
| 协议 | 本质 | 类比 |
|---|---|---|
| MCP | 工具访问协议:LLM 如何调用工具/数据 | 工具车间------让工人知道每个工具的位置和用法 |
| A2A | 代理协作协议:智能体之间如何发现、交流、合作 | 会议室------让不同专家坐在一起协调任务 |
两者互补,共同构成现代 AI 系统的通信基础设施。
9.2 多智能体连接方式
| 架构 | 结构 | 适用场景 |
|---|---|---|
| Network | 去中心化,多 Agent 平等通信 | 多视角协作、头脑风暴、并行搜索 |
| Supervisor | 中央主管 Agent 调度子 Agent | 企业助手(IT/HR/财务多领域)、智能客服 |
| Supervisor as Tools | LLM 将子 Agent 作为工具调用 | 单一核心 + 插件系统 |
| Hierarchical | 多层监督者,顶层分配给子 Supervisor | 大型项目管理、复杂管道任务 |
| Custom | 自由组合上述模式 | 高度定制的企业级应用 |
9.3 Supervisor 实战(v1.0)
pip install langgraph-supervisor
python
import os
from langchain_openai import ChatOpenAI
from langchain.agents import create_agent
from langgraph_supervisor import create_supervisor
# 1. 初始化模型
def init_llm():
return ChatOpenAI(
model="qwen-plus",
api_key=os.getenv("QWEN_API_KEY"),
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
temperature=0.1
)
# 2. 定义业务工具
def book_flight(from_airport: str, to_airport: str) -> str:
"""预订航班工具"""
return f"成功预订了从 {from_airport} 到 {to_airport} 的航班"
def book_hotel(hotel_name: str) -> str:
"""预订酒店工具"""
return f"成功预订了 {hotel_name} 的住宿"
# 3. 创建子 Agent
flight_agent = create_agent(
model=init_llm(), tools=[book_flight], name="flight_assistant")
hotel_agent = create_agent(
model=init_llm(), tools=[book_hotel], name="hotel_assistant")
# 4. 创建 Supervisor 协调者
supervisor = create_supervisor(
agents=[flight_agent, hotel_agent],
model=init_llm(),
prompt=(
"你是旅行预订系统的调度主管。\n"
"1. 分析用户需求,确定需要航班/酒店/两者\n"
"2. 分别调用对应助手\n"
"3. 汇总结果后向用户报告"
)
).compile()
# 5. 流式执行
for chunk in supervisor.stream({
"messages": [{"role": "user", "content": "帮我预订北京到深圳的机票和如家酒店"}]
}):
print(chunk)
9.4 Handoffs(交接)实战
Agent 之间通过 Command + Send 实现控制权交接:
ini
from langgraph.types import Command, Send
from langgraph.prebuilt.tool_node import InjectedState
# 通用 Handoff 工具工厂
def create_handoff_tool(*, agent_name: str, description: str):
@tool(f"transfer_to_{agent_name}", description=description)
def handoff_tool(
task_description: Annotated[str, "描述下一个 Agent 该做什么"],
state: Annotated[MessagesState, InjectedState],
) -> Command:
return Command(
goto=[Send(agent_name, {**state, "messages": [{"role": "user", "content": task_description}]}]),
graph=Command.PARENT,
)
return handoff_tool
# 各 Agent 持有对方的 handoff 工具
flight_agent = create_agent(
model=model,
tools=[book_flight, create_handoff_tool(agent_name="hotel_assistant", description="移交给酒店助手")],
name="flight_assistant"
)
hotel_agent = create_agent(
model=model,
tools=[book_hotel, create_handoff_tool(agent_name="flight_assistant", description="移交给航班助手")],
name="hotel_assistant"
)
# 构建多 Agent 图
multi_agent_graph = (
StateGraph(MessagesState)
.add_node(flight_agent)
.add_node(hotel_agent)
.add_edge(START, "flight_assistant")
.compile()
)
附录:API 速查表
| 功能 | API |
|---|---|
| 添加节点 | graph.add_node("name", func, cache_policy=..., retry_policy=...) |
| 普通边 | graph.add_edge("from_node", "to_node") |
| 条件边 | graph.add_conditional_edges("from_node", route_func, mapping_dict) |
| 入口/出口 | graph.set_entry_point("node") / graph.set_finish_point("node") |
| 编译 | graph.compile(checkpointer=..., cache=..., context_schema=...) |
| 执行 | graph.invoke(state, config, context=...) |
| 流式 | graph.stream(state, stream_mode="updates/values/messages/custom/debug") |
| 查看状态 | graph.get_state(config) |
| 历史 | graph.get_state_history(config) |
| 更新状态 | graph.update_state(config, values={...}) |
作者正在寻找 AI 工程方向的机会,欢迎交流。