状态管理详解
深入理解 LangGraph 的状态更新机制:Reducer 和默认值
📚 学习目标
学完这篇文章后,你将能够:
- 理解为什么需要 Reducer
- 掌握如何配置 Default Value(默认值)
- 实现历史记录追加(Append)模式
- 实现数值累加(Increment)模式
前置知识
在开始学习之前,建议先阅读:
1️⃣ 为什么简单的覆盖是不够的?
在上一节中,我们看到节点返回的对象会与当前状态进行“合并”。对于简单的字段(如 string 或 boolean),这种默认的覆盖行为通常没问题——新的值替换旧的值。
但是,如果我们想要:
- 保留所有历史消息(而不是只保留最后一条)?
- 统计执行步骤(每次 +1,而不是固定的值)?
这时我们就需要更强大的工具:Reducer。
2️⃣ 使用 Reducer 和 Default
Annotation 允许我们通过配置对象来定义字段的行为:
import { Annotation } from '@langchain/langgraph';
const StateAnnotation = Annotation.Root({
// 1. 数组追加模式
history: Annotation<string[]>({
// reducer: 定义如何合并旧值(old)和新值(new)
reducer: (oldState, newState) => {
return [...oldState, ...newState]; // 将新值追加到旧数组后
},
// default: 定义初始值
default: () => ['系统启动']
}),
// 2. 数值累加模式
step: Annotation<number>({
reducer: (oldState, newState) => {
return oldState + newState; // 旧值 + 新值
},
default: () => 0
}),
// 3. 普通覆盖模式(默认行为)
input: Annotation<string>(),
});代码解析:
- history: 使用 spread operator
[...old, ...new]实现追加。这在聊天应用中非常常用,用于累积对话历史。 - step: 实现累加器。如果节点返回
step: 1,状态中的step就会增加 1。 - default: 这是一个工厂函数,当图启动且没有提供该字段的初始值时,会调用它来生成默认值。
3️⃣ Reducer 实战
让我们看看节点是如何与这些 Reducer 交互的。
输入节点
const inputNode = (state: typeof StateAnnotation.State) => {
return {
step: 1, // 触发 reducer: 0 + 1 = 1
history: [state.input] // 触发 reducer: 追加到 history 数组末尾
}
}验证节点
const validateOutputNode = (state: typeof StateAnnotation.State) => {
return {
step: 1, // 再次触发 reducer: 1 + 1 = 2
// history 这里没有返回,所以保持不变
}
}4️⃣ 运行效果
当我们运行这个图时,状态的变化如下:
- 初始状态:
history默认为['系统启动'],step默认为0。 - inputNode 执行后:
step: 0 + 1 = 1history:['系统启动', '用户输入']
- validateOutputNode 执行后:
step: 1 + 1 = 2history: 保持不变
💡 练习题
-
选择题:如果
step的 reducer 定义为(old, new) => new,那么连续两个节点都返回step: 1,最终结果是多少?- A. 1
- B. 2
- 提示:这是覆盖模式的行为。
-
代码题:修改 reducer,使得
history数组最多只保留最近的 3 条记录。
📚 参考资源
项目代码
- 查看本节完整代码:quick-start/2.annotation.ts
✅ 总结
核心要点:
- Reducer 赋予了你控制状态更新方式的能力。
- 最常用的 Reducer 模式是数组追加(用于消息历史)和数值累加(用于计数)。
- Default 保证了状态字段在未初始化时也有安全的值。
下一步:在下一篇文章《LLM 节点集成》中,我们将引入真正的大脑——大语言模型,让我们的图变得智能起来。
登录以继续阅读
解锁完整文档、代码示例及更多高级功能。