一、从 Chain 到 Graph:一次思维范式的跃迁
早期的 LLM 应用开发,习惯用 Chain 的线性思维:输入、处理、输出。但真实业务场景远比这复杂------Agent 需要调用工具、根据工具结果决定下一步、甚至在出错时自我修正。线性 Chain 在这些场景下捉襟见肘。
LangGraph 的核心价值,在于将 Agent 工作流建模为一个"有状态图"(Stateful Graph)。这不是简单的库替换,而是思维模式的升级:
- Chain 是函数式编程,关注数据流转。
- Graph 是状态机编程,关注状态变迁与生命周期。
一句话概括:Node 负责做事,Edge 负责决定下一步,State 负责保存整个过程中的数据。
二、核心架构三要素:State、Node、Edge
LangGraph 把 Agent 工作流建模成图,所有节点围绕同一个 State 协作。理解 State、Node、Edge 这三个概念,就掌握了 LangGraph 的核心。
2.1 State:全局共享的"单一事实来源"
很多初学者误以为 State 就是聊天历史(messages),这是极大的误区。State 是图运行时的共享快照,是业务数据的结构化载体。
在真实项目中,State 应该保存结构化业务数据,而不只是聊天历史:
python
from typing_extensions import TypedDict
class AgentState(TypedDict):
user_input: str # 用户原始输入
intent: str # 识别出的意图
order_id: str # 提取的订单号
messages: list # 对话历史
tool_result: dict # 工具执行结果
final_answer: str # 最终回复
State 设计原则:
- 字段语义清晰 ------不要把所有东西塞到 messages 或 metadata 里。
- 只放流程需要的数据 ------无关数据不进全局 State。
- 中间结果结构化 ------order_status 比自然语言"订单还没发货"更容易判断。
- 注意并行更新 ------多个节点写同一字段时,必须定义 Reducer。
- 区分 State 和 Context ------user_id、trace_id 这类不一定要持久化的上下文,考虑用 runtime context。
2.2 Node:原子化的执行单元
Node 是图中真正"做事"的地方。Node 可以是普通函数、异步函数、LLM 调用、Tool 调用、Agent,甚至是子图。
节点函数签名很简洁:输入当前 State,输出 State 的增量更新(Partial State)。
python
def classify_intent(state: AgentState):
text = state["user_input"]
if "订单" in text:
return {"intent": "order"}
if "快递" in text:
return {"intent": "logistics"}
return {"intent": "general"}
关键点:节点不返回完整 State,只返回需要更新的字段。 LangGraph 会自动合并。
Node 设计原则:
- 职责单一------一个 Node 只做一件事。
- 输入输出清晰------State 进,Partial State 出。
- 不偷偷修改外部全局状态------返回结构化更新。
- 出错时容易定位------利于 Tracing 调试。
反面教材: 把意图识别、数据库查询、LLM 生成、审批判断全塞进一个"巨型 Node"。更好的做法是拆成 classify_intent -> extract_order_id -> query_order -> decide_need_human -> generate_answer,每个节点职责单一。
2.3 Edge:控制流的决策者
Edge 决定图如何流动。LangGraph 提供两类核心连接:
|------------------|------------------|------------|
| 类型 | 说明 | 适用场景 |
| 普通 Edge | 固定从 A 到 B | 固定流水线、线性审批 |
| Conditional Edge | 根据 State 动态选择下一步 | 意图识别、分类路由 |
Conditional Edge 是 LangGraph 动态路由的核心能力。 它接收 State,根据业务逻辑返回下一个节点的名称:
python
def route_by_intent(state: AgentState):
if state["intent"] == "order":
return "query_order"
if state["intent"] == "logistics":
return "query_logistics"
return "general_answer"
架构原则: Edge 中的路由函数应当是轻量的。复杂判断逻辑下沉到 Node 中,Router 只做基于 State 字段的简单分发。不要在路由函数里查数据库、调模型、写日志。
三、消息传递模型:LangGraph 的执行本质
LangGraph 的执行不是简单的函数调用链,而是图消息传递模型 。理解这一点,就理解了 LangGraph 的运行时行为:
- 某个节点收到当前 State。
- 节点执行业务逻辑。
- 返回部分 State 更新。
- LangGraph 合并更新到全局 State。
- 沿 Edge 激活下一个节点。
节点之间不直接互相调用,而是通过状态更新和边 来协作。这种解耦设计意味着每个节点只关心"读什么、写什么",不需要知道上下游节点的存在。
StateGraph 只是构建器,不能直接执行。 必须调用 compile() 编译后才能使用:
python
builder = StateGraph(State)
builder.add_node("node", node_func)
builder.add_edge(START, "node")
builder.add_edge("node", END)
# 编译后才能执行
graph = builder.compile()
graph.invoke(inputs)
graph.stream(inputs)
compile() 会做结构检查:是否有入口、节点名是否存在、是否有孤立节点、图结构是否合法,以及是否配置 checkpointer 等运行能力。
四、实战:构建一个完整的客服路由 Agent
下面通过一个完整的客服场景,展示 LangGraph 的编排能力。目标:输入用户问题 -> 识别意图 -> 根据意图动态路由 -> 查询订单或物流 -> 生成最终回答。
4.1 定义 State 和节点
python
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END
class ServiceState(TypedDict):
user_input: str
intent: str
order_id: str
result: str
final_answer: str
# 意图识别节点
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"]}
4.2 定义路由函数
路由函数和节点的关键区别:路由函数返回字符串(下一个节点名),节点返回 State 更新。 两者都需要接收 State。
python
def route_by_intent(state: ServiceState):
if state["intent"] == "order":
return "query_order"
if state["intent"] == "logistics":
return "query_logistics"
return "general_answer"
4.3 构建 StateGraph
python
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",
},
)
# 汇聚到 final_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()
4.4 执行与流式输出
生产环境中通常使用 stream 模式获取实时反馈:
python
# 同步执行
result = graph.invoke({"user_input": "帮我查一下订单物流"})
print(result["final_answer"])
# 流式执行:每次打印 State 中变更的内容
for chunk in graph.stream(
{"user_input": "帮我查一下快递到哪里了"},
stream_mode="updates"
):
print(chunk)
流式输出的每一步都体现 State 的增量变更------你能看到意图被识别、订单号被提取、物流被查询、最终答案被组装的完整过程。
五、进阶:Runtime Context 与 State 的分离
除了 State,LangGraph 还支持 Runtime Context。两者各司其职:
- State :保存图运行过程中会变化、需要持久化的业务数据。可以被 Checkpoint。
- Context :保存运行时配置,如 user_id、tenant_id、trace_id、数据库连接信息、权限信息。不持久化。
python
from dataclasses import dataclass
from langgraph.runtime import Runtime
@dataclass
class UserContext:
user_id: str
tenant_id: str
class State(TypedDict):
userId: str
builder = StateGraph(state_schema=State, context_schema=UserContext)
def get_user_id(state: State, runtime: Runtime[UserContext]):
user_id = runtime.context.user_id
return {"userId": user_id}
builder.add_node(get_user_id)
builder.add_edge(START, "get_user_id")
builder.add_edge("get_user_id", END)
graph = builder.compile()
result = graph.invoke(
{"userId": ""},
context=UserContext(user_id="1000000001", tenant_id="0001")
)
简单记忆:State 管业务流程走到哪了,Context 管这次运行是谁、在哪个环境、带着什么配置。
六、常见图结构
|-------|---------------------|------------------------------|
| 图结构 | 特点 | 适用场景 |
| 线性图 | A -> B -> C | 固定审批、文档处理 |
| 条件分支图 | A -> B 或 C | 意图识别、分类路由 |
| 循环图 | A -> B -> C -> B | Tool Calling Agent、代码 review |
| 并行图 | A -> B + C -> D | 多路检索、多 Agent 并行 |
| 子图 | Parent -> Subgraph | 大型项目模块化 |
循环图需要注意设置终止条件,避免无限循环。并行图经常需要 Reducer 来处理多节点对同一字段的并发写入。
七、避坑指南:常见误区
误区一:LangGraph 就是画流程图。 不是。LangGraph 的重点不是画图,而是让图可执行、可持久化、可恢复、可观测。
误区二:Node 必须调用 LLM。 不是。Node 可以是普通函数、工具调用、数据库查询、规则判断、Agent 或子图。有些 Node 只做业务逻辑,不碰 LLM。
误区三:State 就是 messages。 不是。messages 只是 State 的一个字段。真实项目中应该使用结构化 State,而非把所有东西塞进 messages。
误区四:Edge 只能固定连接。 不是。LangGraph 支持 Conditional Edge、Command、Send 等动态控制方式。
误区五:compile 后图结构还能改。 通常不会直接改 Compiled Graph。需要修改时,应该改 Builder 后重新 compile。
误区六:Reducer 是可选项。 只要有并行更新、Map-Reduce、多 Agent 汇总,就必须理解 Reducer。
八、总结
LangGraph 的核心架构可以浓缩成一句话:State 是数据,Node 是动作,Edge 是流程。
构建 LangGraph 应用的基本步骤:
- 定义 State
- 编写 Node
- 添加 Node(add_node)
- 添加 Edge(add_edge / add_conditional_edges)
- 编译(compile)
- 执行(invoke / stream)
后续所有高级能力------Memory、Human-in-the-loop、Multi-Agent、Subgraph------本质上都是围绕这套 State + Node + Edge 架构展开的。掌握了这个三位一体,就掌握了 LangGraph 的根基。