首页 > 教程攻略 > ai教程 >LangGraph中的Reducer是什么

LangGraph中的Reducer是什么

来源:互联网 时间:2026-07-23 07:24:09

前情回顾

1.LangGraph 入门基础全解析

LangGraph中的Reducer是什么

Reducer(归约器),说白了就是一个函数,它专门解决一个问题:当多个节点都想修改 State 中的同一个字段时,到底该怎么合并才算公平?

换个更直白的说法:Reducer 决定了 State 中某个字段的更新策略——是简单粗暴地覆盖?是规规矩矩地追加?还是累加到一起?或者是完全自定义的规则?

从问题出发理解 Reducer

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

在 LangGraph 的世界里,多个节点按顺序执行,每一个都可能顺手修改一下 State。这就引出了一个经典的矛盾:

假如有两个答案摆在你面前:

  1. 2(后一个把前一个彻底覆盖掉)—— 这是 LangGraph 默认的脾气
  2. 3(1 + 2 累加起来,谁也别想抹掉谁)—— 这是 Reducer 出手之后的结果

Reducer 就是那个帮你做出选择的老司机。

默认行为 vs Reducer 行为

默认行为:覆盖(Override)

如果不指定 Reducer,LangGraph 的默认行为很霸道:后来者居上。

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

执行流程看下来是这样的:

初始状态:counter = 0节点 A 返回:{"counter": 1}→ 状态变为:counter = 1节点 B 返回:{"counter": 2}→ 状态变为:counter = 2(节点 A 的贡献直接被抹掉了)最终结果: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 + newreturn combined[-n:]return reducerclass 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 mergedclass MyState(TypedDict):metadata: Annotated[Dict, dict_merge]# 字典合并

示例3:取最大值

def max_reducer(old: float, new: float) -> float:"""保留最大值"""return max(old, new)class MyState(TypedDict):max_score: Annotated[float, max_reducer]# 保留最高分

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
能自己写吗?能,任何符合签名的函数都可以

一句话记住

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