State、Node、Graph 三件套:状态模式、节点签名与追加 Reducer

State、Node、Graph 三件套:状态模式、节点签名与追加 Reducer
Asaakii本文是「LangGraph 核心与实战」系列专栏的第 2 篇。专栏总览参见:《LangGraph 核心架构全景:用状态图构建可控 Agent 工作流》。
任何 LangGraph 应用在底层都由三个核心部件构成:状态(State)、节点(Node)与图(Graph)。
虽然很多入门示例只有十来行代码,但在深入开发复杂系统时,开发者往往会遭遇几类典型困惑:为什么节点返回的字典不能包含原有的全部数据?为什么历史消息列表在第二步直接被覆盖成了最后一条?节点内部能不能访问外部全局变量?
理清这三者的契约规范与运行机制,是编写稳健状态图工作流的基础。
环境安装与基准依赖
LangGraph 是一个轻量级编排层,核心运行机制仅依赖 Python 标准库与基础类型支持:
1 | # 核心依赖安装 |
1. State(状态):整张图的共享内存
State 是图执行过程中的全局数据上下文。图中的每一个节点在被调用时,都会接收到当前的 State 实例;执行完毕后,节点输出的数据会再次合并回 State 中。
用 TypedDict 进行强类型数据建模
LangGraph 最推荐使用 Python 标准库中的 typing.TypedDict 来定义 State:
1 | from typing import TypedDict, List, Optional |
TypedDict 在代码编写期提供了 IDE 的自动补全与类型检查能力,同时在运行时保持了原生 Python 字典的低开销特性。
状态更新机制:局部增量合并(Delta Merge)
初学者最容易犯的错误是在节点函数中试图修改整个 State 并原样返回:
1 | # 错误示范:试图返回全量 State |
LangGraph 的核心法则是:节点必须只返回“当前需要更新的字段增量字典”:
1 | # 正确示范:只返回发生变化的键值 |
LangGraph 在底层执行的是浅层字典合并(Shallow Merge):
- 节点返回
{"sanitized_text": "..."}时,系统仅覆盖 State 中的该键; - State 中的其他字段(如
raw_input、token_count)保持原样不变; - 这种机制避免了多节点传递中由于全量深拷贝引发的内存暴涨,也保证了数据变更记录的纯粹性。
列表字段的覆盖与追加:使用 Reducer
默认情况下,如果一个字段的值是列表,后续节点返回同名列表会直接覆盖前序节点的内容:
1 | # 默认行为是覆盖 (Overwrite) |
为了让列表字段支持“追加(Append)”,必须使用 Python 的 Annotated 语法为该字段注入 归约函数(Reducer):
1 | from typing import Annotated, List, TypedDict |
当为字段附加了 operator.add 之后:
- 节点 A 返回
{"history": ["用户: 你好"]}; - 节点 B 返回
{"history": ["助手: 您好,有什么可以帮您?"]}; - LangGraph 底层会自动执行
history = operator.add(history, new_items),状态最终聚合为包含两条记录的完整列表。
2. Node(节点):图的操作执行单元
节点是承载具体计算的逻辑实体。在代码层面,一个 Node 就是一个普通的 Python 函数。
节点签名的三大工程准则
标准节点函数的类型签名必须满足:
编写节点时应严格恪守以下三项原则:
flowchart LR
subgraph P1 ["原则 1: 单一职责"]
N1[一个节点只完成一道独立工序]
end
subgraph P2 ["原则 2: 纯函数特性"]
N2[入参仅依赖 state,输出仅靠 return]
end
subgraph P3 ["原则 3: 增量返回"]
N3[仅返回待修改字段,严禁全量覆写]
end
P1 --- P2 --- P3
- 原则一:单一职责(Single Responsibility):不要把“调外部 API”、“数据清洗”、“计算打分”与“持久化写库”写进同一个节点。节点越原子化,图在后续重构或插入重试边时就越灵活;
- 原则二:输入确定性(Pure Function):节点的内部逻辑应当尽量只读取
state参数,避免在节点内读取外部未受控的全局变量。依赖外部服务时(如调用大模型或数据库),应通过外部依赖注入,便于单独进行单元测试; - 原则三:局部输出:只向外输出确需写入全局上下文的数据,临时计算变量不要注入状态,防止状态体积过度膨胀。
3. Graph(图):装配拓扑与编译执行
有了状态契约与处理节点后,使用 StateGraph 将它们连接为计算拓扑:
1 | from langgraph.graph import StateGraph, START, END |
两种执行模式:invoke 与 stream
编译生成的 app 提供了两种运行方式:
模式 A:app.invoke()(批量同步交付)
适用于传统 Web API 接口或单次批处理,等待整个图跑完全部节点后,一次性返回最终收敛的完整 State:
1 | final_state = app.invoke({"raw_input": " 这是一段需要清洗的测试数据 "}) |
模式 B:app.stream()(流式逐步观测)
在调试排障、长程任务或需要向前端提供实时进度条的场景下,使用 stream 逐节点消费执行事件。每次 yield 产出一个字典,键为当前刚执行完的节点名称,值为该节点返回的增量更新:
1 | for event in app.stream({"raw_input": " 这是一段需要清洗的测试数据 "}): |
完整可运行实战:文本结构化处理流水线
以下代码整合了强类型 State、operator.add 增量日志追加、多节点顺序编排与 stream 观测:
1 | from typing import TypedDict, Annotated, List, Optional |
运行输出:
1 | --- 启动流式执行 --- |











