**摘要:**本文介绍LangGraph中的归并函数(Reducer)机制。在LangGraph中,节点返回的内容是局部更新,Reducer用于将多个节点的输出合并,并将局部更新写入全局State。如果没有归并函数,并行执行的多个节点同时修改同一字段时,后执行的节点会覆盖先执行的节点,导致数据丢失。通过声明Annotated类型的变量并指定写入方式(如add追加),可以实现对State中内容的追加而非覆盖。文章通过完整代码示例演示了如何使用Annotated和add归约函数,实现messages字段的追加更新,并构建、编译、运行一个包含两个节点的工作流图。
内容参考于:图灵AI大模型全栈
归并函数(Reducer)
在LangGraph中节点它返回的内容是局部的更新,Reducer是用来把多个节点输出合并起来,然后再把每个节点局部更新的状态写入进全局的State中
如果没有Reducer当一个问题要执行两个节点,这两个节点是并行执行(一起执行),然后两个节点都修改了messages这个字段,那么两个节点执行完,最终State的messages字段的值是最后一个节点的内容,第一个节点的内容会被覆盖导致丢失,我们需要的是对State中的内容做追加,而不是覆盖
如果有归并函数它就会把节点的返回值已追加的方式写到State中
未添加归并函数的效果,如下图红框,它最终的内容是node2的返回值
下图蓝框加入归并函数,通过声明Annotated类型的变量,Annotated第一个值是真实的数据类型,第二个值是写入方式(add默认往后添加),下图红框效果图,messages的值就不会被覆盖了,而是依次往后添加内容
代码
python
# 从 typing 模块导入类型工具
# Annotated:用于给类型附加元数据,LangGraph 用它来指定字段的「更新策略」
# TypedDict:定义结构化字典类型,约束状态的字段和数据类型
from typing import Annotated, TypedDict
# 导入 LangGraph 核心类 StateGraph,用于构建有状态的工作流图
from langgraph.graph import StateGraph
# 从 Python 标准库 operator 导入 add 函数
# 作用:作为列表的「归约合并函数」,实现状态字段的「追加更新」而非默认的「覆盖更新」
from operator import add
# ===================== 1. 定义全局状态结构 =====================
class MyState(TypedDict):
# messages 字段:带归约策略的字符串列表
# 语法格式:Annotated[字段数据类型, 归约函数]
# 归约函数 add:指定该字段的更新规则为「追加合并」
# 每次节点返回该字段时,新列表会被追加到原有列表末尾,而不是直接覆盖整个列表
# 典型适用场景:对话历史、操作日志、执行轨迹等需要累计保留全量数据的场景
messages: Annotated[list[str], add]
# question 字段:普通字符串,使用默认更新策略
# 默认策略:节点返回该字段时,新值直接覆盖旧值
question: str
# answer 字段:普通字符串,默认覆盖更新
answer: str
# ===================== 2. 定义工作流节点函数 =====================
# 节点1:执行第一段业务逻辑,向 messages 追加一条记录
# 参数 state:当前完整的状态字典,节点通过它读取所有历史数据
def node1(state: MyState):
print("执行节点1")
# 返回需要更新的状态字段
# 注意:因为 messages 配置了 add 归约,这里的列表会追加到原列表末尾
# 如果没有 Annotated[..., add],这里会直接清空并替换整个 messages 列表
return {"messages": ["这是节点1"]}
# 节点2:执行第二段业务逻辑,追加消息并生成最终答案
def node2(state: MyState):
print("执行节点2")
# messages:追加一条新记录(归约模式)
# answer:直接赋值覆盖(默认替换模式)
return {"messages": ["这是节点2"], "answer": "这是答案"}
# ===================== 3. 构建工作流图 =====================
# 初始化状态图,指定整个工作流使用 MyState 作为数据结构
# 参数 state_schema:状态结构定义,用于校验节点返回值、规范数据格式
graph = StateGraph(state_schema=MyState)
# 向图中注册节点
# 参数1:节点唯一ID(字符串),后续添加边、设置入口都通过这个ID引用节点
# 参数2:节点执行函数,工作流运行到该节点时自动调用
graph.add_node("node1", node1)
graph.add_node("node2", node2)
# 设置工作流入口点
# 参数:节点ID,指定工作流启动后第一个执行的节点
graph.set_entry_point("node1")
# 添加有向边,定义节点执行顺序
# 参数1:起点节点ID;参数2:终点节点ID
# 含义:起点节点执行完成后,工作流自动流向终点节点
graph.add_edge("node1", "node2")
# 添加指向结束节点的边
# __end__ 是 LangGraph 内置的终止标记节点,到达后工作流停止并返回最终状态
graph.add_edge("node2", "__end__")
# ===================== 4. 编译并运行工作流 =====================
# 编译图结构
# 作用:校验图的合法性(入口、边、节点引用是否正确),生成可执行的运行时实例
my_graph = graph.compile()
# 启动并执行工作流
# 参数:初始状态字典,作为入口节点的输入数据
# 返回值:工作流执行完成后的完整最终状态
result = my_graph.invoke({"messages": ["这是问题?"], "question": "问题"})
# 打印最终结果
print(result)
