LangGraph 核心架构详解:State、Node、Edge 的实战编排
导读:LangGraph 用"有状态图"重新定义了 Agent 的编排方式。State 是数据,Node 是动作,Edge 是流程。本文从项目真实代码出发,完整讲解 TypedDict 状态设计、节点编写规范、条件路由边、compile 编译过程,以及 6 个常见图结构(线性、分支、循环、并行、子图、多 Agent)。读完可以直接落地到自己的 LangGraph 项目中。
适合读者:
- 已经会用 LangChain 但还没接触过 LangGraph 的开发者
- 被传统 AgentExecutor 循环困扰的工程师
- 需要构建有状态、可持久化、可恢复 Agent 系统的架构师
- 想了解为什么 “State + Node + Edge” 比单纯 prompt 工程更强大的人
阅读收益:
- 理解 LangGraph 相比传统 AgentExecutor 的 5 大核心改进
- 掌握 TypedDict 定义 State 的规范写法
- 学会设计职责单一的 Node(函数/LLM/工具/子图)
- 理解普通 Edge vs Conditional Edge 的区别和适用场景
- 拿到完整客服 Agent 的图结构代码(意图识别 → 路由 → 查询 → 回复)
目录
- 为什么需要 LangGraph
- State:图的共享状态
- Node:图中的执行单元
- Edge:节点之间的连接
- compile:从 Builder 到可执行图
- 常见图结构速查
- 完整案例:客服 Agent 工作流
- 踩坑清单:LangGraph 的 10 个常见错误
- 总结
- 文末互动
1. 为什么需要 LangGraph
1.1 从 LLM 到 Agent
最早的 LLM 应用是单向的:
用户输入 → Prompt → LLM → 模型输出
但当用户说"帮我查一下订单状态,如果还没发货就提醒客服加急"时,需要多步决策:
理解意图 → 查询订单系统 → 判断状态 → 必要时调用客服系统 → 生成回复 → 记录日志
这就是 Agent 场景,核心是 Think → Act → Observe 循环。
1.2 传统 AgentExecutor 的 5 大局限
| 问题 | 说明 |
|---|---|
| 控制流不清晰 | 下一步调用什么工具、是否继续、何时结束,都由模型决定,难以保证执行顺序 |
| 状态管理混乱 | 所有内容塞进 messages,越来越长,token 成本高,中间变量难以验证 |
| 难以恢复 | 服务重启后丢失上下文,无法从中断位置继续 |
| 人工介入不自然 | 需要人工审批时只能临时写 if/else,无法暂停→保存→恢复 |
| 多 Agent 协作困难 | 谁先执行、谁负责审查、如何共享状态,缺乏标准机制 |
1.3 LangGraph 的核心思想
LangGraph = Lang + Graph
把 Agent 应用建模成一个有状态的图。
图由三部分组成:
| 概念 | 含义 |
|---|---|
| Node | 执行单元(函数、LLM、工具、Agent、子图) |
| Edge | 节点之间的连接,决定执行顺序 |
| State | 图运行过程中的共享状态 |
LangGraph 解决的不是"怎么写 prompt",而是 Agent 的编排问题:
- 执行流程怎么控制
- 状态怎么更新和持久化
- 中断后怎么恢复
- 人工怎么介入
- 多 Agent 怎么协作
2. State:图的共享状态
2.1 使用 TypedDict 定义 State
from typing_extensions import TypedDict
class State(TypedDict):
user_input: str # 用户原始输入
intent: str # 识别出的意图
answer: str # 最终回复
这表示图的状态包含三个字段:
{
"user_input": "...",
"intent": "...",
"answer": "..."
}
2.2 Node 如何读写 State
读取:
def classify_intent(state: State):
text = state["user_input"]
if "订单" in text:
return {"intent": "order"}
return {"intent": "general"}
写入:
def answer_node(state: State):
return {
"answer": f"当前意图是:{state['intent']}"
}
关键:节点返回的是 Partial[State],也就是 state 的一部分,不是完整的 state。
2.3 State ≠ messages
很多初学者把 state 理解成聊天消息,这是不完整的。
真实项目中 state 应该保存结构化业务数据:
class AgentState(TypedDict):
messages: list # 聊天历史(只是其中一个字段)
user_id: str
intent: str
order_id: str
tool_result: dict
final_answer: str
2.4 State 设计原则
- 字段语义清晰——不要所有东西都塞到 messages 或 metadata
- 只放流程需要的数据——不要把无关数据放进全局 state
- 中间结果结构化——
order_status比自然语言"订单还没发货"更容易判断 - 注意并行更新——多个节点写同一个字段时,要定义 reducer
- 区分 runtime context 和 state——
user_id、trace_id用 context,不放入 state
2.5 可选字段:total=False
class State(TypedDict, total=False):
user_input: str
intent: str
answer: str
total=False 表示 invoke 时可以不传递所有字段。但使用时要注意:
# 如果某个字段可能不存在,用 get 避免 KeyError
intent = state.get("intent", "general")
3. Node:图中的执行单元
3.1 Node 可以是这些
- 普通 Python 函数
- 异步函数
- LLM 调用
- Tool 调用
- Agent
- Subgraph(子图)
- Runnable
3.2 最简单的 Node
def greet(state: State):
return {
"answer": f"你好,你说的是:{state['user_input']}"
}
节点函数签名:
State -> Partial[State]
输入当前状态 → 输出状态更新
3.3 注册 Node
# 显式命名(推荐生产环境)
builder.add_node("classify_intent", classify_intent)
# 省略名称(用函数名)
builder.add_node(greet)
建议生产项目显式命名,图结构更清晰,日志和 tracing 也更容易看。
3.4 Node 中调用 LLM
from langchain_core.messages import HumanMessage
def llm_node(state: State):
response = model.invoke([
HumanMessage(content=state["user_input"])
])
return {
"answer": response.content
}
LangGraph 不要求每个 node 都调用 LLM。有些 node 只是普通业务逻辑:
def extract_order_id(state: State):
# 正则、规则、数据库查询
return {"order_id": "10086"}
3.5 异步 Node
async def async_node(state: State):
response = await model.ainvoke(state["user_input"])
return {"answer": response.content}
调用异步图:
result = await graph.ainvoke(inputs)
# 或
async for chunk in graph.astream(inputs):
print(chunk)
3.6 Node 设计原则
一个好的 node 应该:
- 职责单一
- 输入输出清晰
- 不偷偷修改外部全局状态
- 返回结构化更新
- 出错时容易定位
不要写成:
def giant_agent_node(state):
# 识别意图
# 查数据库
# 调工具
# 生成回答
# 写日志
# 审批判断
...
更好的拆法:
classify_intent → extract_order_id → query_order → decide_need_human → generate_answer
4. Edge:节点之间的连接
4.1 普通 Edge
builder.add_edge("classify_intent", "query_order")
含义:classify_intent 执行完成后,进入 query_order。
完整线性流程:
builder.add_edge(START, "classify_intent")
builder.add_edge("classify_intent", "query_order")
builder.add_edge("query_order", "final_answer")
builder.add_edge("final_answer", END)
对应:START → classify_intent → query_order → final_answer → END
4.2 START 和 END 节点
from langgraph.graph import START, END
# START:图的入口,必须指定
builder.add_edge(START, "classify_intent")
# END:图的结束,显式连接更清晰
builder.add_edge("final_answer", END)
4.3 Conditional Edge(条件路由)
def route_by_intent(state: State):
if state["intent"] == "order":
return "query_order"
if state["intent"] == "logistics":
return "query_logistics"
return "general_answer"
builder.add_conditional_edges(
"classify_intent", # 源节点
route_by_intent, # 路由函数
{
"query_order": "query_order",
"query_logistics": "query_logistics",
"general_answer": "general_answer",
},
)
执行逻辑:
classify_intent 执行完成
↓
调用 route_by_intent(state)
↓
根据返回值选择下一个节点
4.4 Conditional Edge 的简化写法
如果路由函数返回的字符串和节点名一样:
builder.add_conditional_edges("classify_intent", route_by_intent)
4.5 Edge 设计原则
- Edge 应该表达流程关系
- 不要把大量业务逻辑藏在 route 函数里
- 复杂逻辑放 node 中,route 只做轻量判断
推荐:
def route_by_intent(state):
return state["intent"] # 直接返回意图,简洁
不推荐:
def route_by_intent(state):
# 查数据库
# 调模型
# 写日志
# 做审批
# 再决定路由
...
5. compile:从 Builder 到可执行图
5.1 compile 的作用
StateGraph 只是构建器,不能直接执行。必须调用:
graph = builder.compile()
compile 会做结构检查:
- 是否有入口(START)
- 节点名是否存在
- 是否有孤立节点
- 图结构是否合法
5.2 compile 后调用
# 同步执行
result = graph.invoke(inputs)
# 异步执行
result = await graph.ainvoke(inputs)
# 流式执行
for chunk in graph.stream(inputs, stream_mode="updates"):
print(chunk)
# 获取历史状态
graph.get_state(thread_id)
graph.get_state_history(thread_id)
5.3 配置 Checkpointer(持久化)
from langgraph.checkpoint.memory import InMemorySaver
graph = builder.compile(
checkpointer=InMemorySaver() # 生产环境换 Redis / Postgres
)
有了 checkpointer:
- 保存执行状态
- 查看历史状态
- 中断后恢复
- 支持多轮对话
- 支持 Human-in-the-Loop
6. 常见图结构速查
6.1 线性图
START → A → B → C → END
适合固定流程:文档处理、固定审批、流水线。
6.2 条件分支图
START → A
├→ B
└→ C
适合意图识别、分类路由、不同业务路径。
6.3 循环图
A → B → C
↑ ↓
└───┘
适合 Tool Calling Agent、代码生成后 review、反复检索直到足够。
注意:必须设置终止条件,避免无限循环。
6.4 并行图
START → A → D
├→ B ┘
└→ C
适合多路检索、多文档处理、多 Agent 并行。
注意:并行图经常需要 reducer 合并结果。
6.5 子图
Parent Graph
↓
Subgraph
↓
Parent Graph
适合大型项目模块化,把复杂系统拆成可复用的子图。
7. 完整案例:客服 Agent 工作流
7.1 定义 State
from typing_extensions import TypedDict
class ServiceState(TypedDict):
user_input: str
intent: str
order_id: str
result: str
final_answer: str
7.2 定义节点
def classify_intent(state: ServiceState):
text = state["user_input"]
if "订单" in text:
intent = "order"
elif "物流" in text or "快递" in text:
intent = "logistics"
else:
intent = "general"
return {"intent": intent}
def extract_order_id(state: ServiceState):
return {"order_id": "10086"}
def query_order(state: ServiceState):
return {
"result": f"订单 {state['order_id']} 当前状态:已支付"
}
def query_logistics(state: ServiceState):
return {
"result": f"订单 {state['order_id']} 的物流状态:运输中"
}
def general_answer(state: ServiceState):
return {
"result": "我可以帮你查询订单或物流。"
}
def final_answer(state: ServiceState):
return {
"final_answer": state["result"]
}
7.3 定义路由
def route_by_intent(state: ServiceState):
if state["intent"] == "order":
return "query_order"
if state["intent"] == "logistics":
return "query_logistics"
return "general_answer"
7.4 构建图
from langgraph.graph import StateGraph, START, END
builder = StateGraph(ServiceState)
# 添加节点
builder.add_node("classify_intent", classify_intent)
builder.add_node("extract_order_id", extract_order_id)
builder.add_node("query_order", query_order)
builder.add_node("query_logistics", query_logistics)
builder.add_node("general_answer", general_answer)
builder.add_node("final_answer", final_answer)
# 添加边
builder.add_edge(START, "classify_intent")
builder.add_edge("classify_intent", "extract_order_id")
builder.add_conditional_edges(
"extract_order_id",
route_by_intent,
{
"query_order": "query_order",
"query_logistics": "query_logistics",
"general_answer": "general_answer",
},
)
builder.add_edge("query_order", "final_answer")
builder.add_edge("query_logistics", "final_answer")
builder.add_edge("general_answer", "final_answer")
builder.add_edge("final_answer", END)
graph = builder.compile()
7.5 调用图
result = graph.invoke({
"user_input": "帮我查一下订单物流",
"intent": "",
"order_id": "",
"result": "",
"final_answer": "",
})
print(result["final_answer"])
# 输出:你的订单物流正在运输中
执行流程:
START → classify_intent → extract_order_id → route_by_intent → query_logistics → final_answer → END
8. 踩坑清单:LangGraph 的 10 个常见错误
坑1:StateGraph 就是画流程图
LangGraph 的重点不是画图,而是让图可执行、可持久化、可恢复、可观测。
坑2:Node 必须是 LLM
Node 可以是普通函数、工具调用、数据库查询、规则判断、Agent 或子图。
坑3:State 就是 messages
messages 只是 state 的一个字段。真实项目应该用结构化 state。
坑4:Edge 只能固定连接
LangGraph 支持 conditional edge、Command、Send 等动态控制方式。
坑5:compile 后还能随便改图
通常不会直接改 compiled graph。应该修改 builder 后重新 compile。
坑6:Reducer 是可选小知识
只要有并行更新、Send、Map Reduce、多 Agent 汇总,就必须理解 reducer。
坑7:节点返回完整 state
节点通常返回 Partial[State],也就是局部更新,不需要返回全部字段。
坑8:忘记指定 START 入口
必须指定 START,否则 LangGraph 不知道从哪里开始:
builder.add_edge(START, "first_node") # 必须有这条
坑9:路由函数做太多事
路由函数只做轻量判断,复杂逻辑放到 node 中:
# 错误:路由里查数据库
def route(state):
result = db.query(...) # 不应该在这里做
return "a" if result else "b"
# 正确:路由只做判断
def route(state):
return state["intent"] # 意图已经由上一个节点计算好
坑10:invoke 时没传全字段
如果 total=True(默认),invoke 必须传递所有字段:
# 错误:漏传 order_id
graph.invoke({"user_input": "...", "intent": ""}) # 报错
# 正确:传全所有字段
cass State(TypedDict, total=False):
# 或传全所有字段
...
9. 总结
9.1 核心记忆
State 是数据,Node 是动作,Edge 是流程。
构建 LangGraph 的 6 个步骤:
1. 定义 State(TypedDict)
2. 编写 Node(函数)
3. 添加 Node(builder.add_node)
4. 添加 Edge(builder.add_edge / add_conditional_edges)
5. compile(builder.compile())
6. invoke / stream(graph.invoke(...))
9.2 LangGraph vs AgentExecutor
| 特性 | AgentExecutor | LangGraph |
|---|---|---|
| 状态管理 | 隐式(messages) | 显式(TypedDict State) |
| 流程控制 | LLM 自由决定 | 开发者显式编排 |
| 持久化 | 不支持 | checkpointer 支持 |
| 恢复 | 不支持 | 支持 |
| 人工介入 | 临时 if/else | interrupt / resume 原生支持 |
| 多 Agent | 困难 | graph / subgraph 支持 |
| 调试 | 困难 | stream 实时输出 |
9.3 延伸方向
- Checkpointer:从 InMemorySaver 升级到 Redis / Postgres 持久化
- Human-in-the-Loop:用 interrupt 实现审批流程
- Streaming:stream_mode=“updates” 实时查看节点执行
- Subgraph:把复杂系统拆成模块化子图
- Multi-Agent:Supervisor + 多个子 Agent 协作
10. 文末互动
思考题:
- 你现在的项目用的是什么 Agent 方案?是简单 prompt 调用、LangChain AgentExecutor,还是已经用了 LangGraph?
- 你的业务场景中,最迫切需要 LangGraph 解决的是哪个问题(流程控制、状态管理、持久化、人工介入)?
- 你会如何设计一个"生成代码 → 运行测试 → 如果失败则修复 → 再测试"的 LangGraph 循环?
如果本文对你有帮助:
- 点赞支持,让更多做 Agent 工程的开发者看到
- 收藏备用,设计 LangGraph 工作流时翻出来参考
- 评论交流,说说你的 State 设计经验
作者的话:LangGraph 的出现标志着 Agent 应用从"玩具 Demo"走向"生产系统"。理解 State + Node + Edge 的核心理念,比记住任何 API 都重要。当你能把业务逻辑拆成清晰的节点,用边把它们连接起来,你就已经掌握了 LangGraph 的精髓。

340

被折叠的 条评论
为什么被折叠?



