LangGraph中的Reducer是什么

📅 2026/7/24 16:33:06 👁️ 阅读次数 📝 编程学习
LangGraph中的Reducer是什么

Reducer(归约器)是一个函数,它定义了“当多个节点都想修改同一个 State 字段时,如何把这些修改合并成一个最终结果”。

或者说:Reducer 决定了 State 中某个字段的更新策略——是覆盖?是追加?是累加?还是其他自定义规则?


从问题出发理解 Reducer

核心矛盾:多个节点都要写同一个字段

在 LangGraph 中,多个节点会依次执行,每个节点都可能修改 State。这就产生了一个问题:

如果节点 A 把counter设为 1,节点 B 又把counter设为 2,那最终counter应该是多少?

有两个答案:

  1. 2(后一个覆盖前一个)—— 这是默认行为
  2. 3(1 + 2 累加起来)—— 这是 Reducer 改变后的行为

Reducer 就是用来回答这个问题的工具。


默认行为 vs Reducer 行为

默认行为:覆盖(Override)

如果不指定 Reducer,LangGraph 的默认行为是覆盖

class MyState(TypedDict): counter: int # 没有 Reducer name: str # 没有 Reducer

执行流程:

初始状态:counter = 0 节点 A 返回:{"counter": 1} → 状态变为:counter = 1 节点 B 返回:{"counter": 2} → 状态变为:counter = 2 (覆盖了节点 A 的 1) 最终结果:counter = 2

适用于:只需要最终值的场景,比如用户名、当前状态标志等。

Reducer 行为:合并(Merge)

指定 Reducer 后,行为变为合并

class MyState(TypedDict): counter: Annotated[int, operator.add] # 使用加法 Reducer

执行流程:

初始状态:counter = 0 节点 A 返回:{"counter": 1} → 状态变为:counter = 1 节点 B 返回:{"counter": 2} → 状态变为:counter = 3 (1 + 2 = 3,累加了!) 最终结果:counter = 3

适用于:需要累积的场景,比如计数器、总分、消息列表等。


Reducer 的本质:一个普通函数

Reducer 并没有什么魔法,它就是一个普通的 Python 函数,接收两个参数:

def reducer_func(current_value, new_value) -> final_value: """ current_value:当前 State 中该字段的值 new_value:节点返回的该字段的新值 return:合并后的最终值 """ # 在这里定义你的合并逻辑 return merged_result

LangGraph 内置了几个常用的 Reducer:

Reducer 函数效果等价于
operator.add数值相加lambda a, b: a + b
operator.set集合合并lambda a, b: a | b
add_messages消息列表追加特殊处理,含 ID 去重
不指定覆盖lambda a, b: b

用生活例子彻底搞懂

场景:记账本

你和室友共用一本账本,记录每天的开销。

没有 Reducer(覆盖模式)

周一:小明记了"吃饭 50元" → 账本:[吃饭 50元] 周二:小红记了"打车 30元" → 账本:[打车 30元] ← 周一的内容被覆盖了! 周三:你们吵架了,因为周一的账找不到了

有 Reducer(追加模式)

周一:小明记了"吃饭 50元" → 账本:[吃饭 50元] 周二:小红记了"打车 30元" → 账本:[吃饭 50元, 打车 30元] ← 追加在后面 周三:月底算账,清清楚楚

在这个例子里,add就是一个 Reducer,它的规则是:新来的记录追加到旧记录的后面,而不是替换它。


为什么 LangGraph 需要 Reducer?

原因一:图不是线性执行的

在 LangGraph 中,图可能有分支循环

┌→ 节点 B ─→┐ START ─→┤ ├→ 节点 D └→ 节点 C ─→┘

节点 B 和节点 C 都可能修改同一个字段。如果没有 Reducer,后执行的节点会抹掉前一个节点的修改。有了 Reducer,两者的贡献可以共存。

原因二:循环需要累积

节点 A → 条件判断 → 未满足 → 回到节点 A(循环)

在循环中,每次经过节点 A 都可能产生新数据。Reducer 确保这些数据是累积的,而不是每次循环都重置。

原因三:可预测的状态变化

Reducer 让状态变化变得透明和可预测。你知道每个字段的更新规则是什么,不会出现“咦,这个值怎么不见了?”的困惑。


实战:自定义 Reducer

内置 Reducer 不能满足需求时,可以自己写:

示例1:限制列表长度

def last_n_reducer(n: int): """只保留最近 n 条记录""" def reducer(old: list, new: list) -> list: combined = old + new return combined[-n:] return reducer class MyState(TypedDict): recent_logs: Annotated[List, last_n_reducer(10)] # 只保留最近10条

示例2:字典合并

def dict_merge(old: dict, new: dict) -> dict: """合并两个字典,新值覆盖旧值""" merged = old.copy() merged.update(new) return merged class MyState(TypedDict): metadata: Annotated[Dict, dict_merge] # 字典合并

Reducer 的完整工作流程

为了让你彻底明白,这里是 LangGraph 内部处理 Reducer 的完整流程:

1. 节点执行完毕,返回一个字典,比如 {"counter": 5} 2. LangGraph 遍历这个字典的每个键值对 3. 对于每个键(比如 "counter"): a. 查找 State 定义中这个键有没有 Reducer b. 如果有 Reducer: - 获取当前 State 中 "counter" 的旧值(比如 3) - 调用 Reducer 函数:reducer(旧值=3, 新值=5) - 把 Reducer 的返回值(比如 8)写入 State c. 如果没有 Reducer: - 直接用新值(5)覆盖旧值(3) 4. 更新后的 State 传递给下一个节点

面试级总结

问题答案
Reducer 是什么?一个定义 State 字段更新规则的函数
默认行为是什么?覆盖(新值替换旧值)
Reducer 改变什么?从"覆盖"变成"合并/累加/自定义"
Reducer 的参数?(current_value, new_value) → merged_value
为什么需要它?因为图有分支和循环,需要累积而非覆盖
内置的有哪些?operator.addoperator.setadd_messages
能自己写吗?能,任何符合签名的函数都可以

一句话记住

没有 Reducer 的 State 字段,后浪直接把前浪拍死在沙滩上;有 Reducer 的字段,后浪和前浪汇合在一起,变成更大的浪。

现在你应该能理解,为什么messages: Annotated[List, add_messages]这么重要了吧?没有它,你的聊天 Agent 永远只能记住最后一句话。

学习资源推荐

如果你想更深入地学习大模型,以下是一些非常有价值的学习资源,这些资源将帮助你从不同角度学习大模型,提升你的实践能力。

一、全套AGI大模型学习路线

AI大模型时代的学习之旅:从基础到前沿,掌握人工智能的核心技能!​

因篇幅有限,仅展示部分资料,需要点击文章最下方名片即可前往获取

二、640套AI大模型报告合集

这套包含640份报告的合集,涵盖了AI大模型的理论研究、技术实现、行业应用等多个方面。无论您是科研人员、工程师,还是对AI大模型感兴趣的爱好者,这套报告合集都将为您提供宝贵的信息和启示

​因篇幅有限,仅展示部分资料,需要点击文章最下方名片即可前往获取

三、AI大模型经典PDF籍

随着人工智能技术的飞速发展,AI大模型已经成为了当今科技领域的一大热点。这些大型预训练模型,如GPT-3、BERT、XLNet等,以其强大的语言理解和生成能力,正在改变我们对人工智能的认识。 那以下这些PDF籍就是非常不错的学习资源。

因篇幅有限,仅展示部分资料,需要点击文章最下方名片即可前往获取

四、AI大模型商业化落地方案

作为普通人,入局大模型时代需要持续学习和实践,不断提高自己的技能和认知水平,同时也需要有责任感和伦理意识,为人工智能的健康发展贡献力量。