上一篇把 State 比作一块共用的白板:每个节点读整块白板,只写回自己负责的那几栏。那张图里每个字段都只有一个节点在写,所以"新值直接覆盖旧值"毫无问题。可一旦进入对话场景,用户、模型、工具都要往同一份消息列表里追加内容,覆盖写入就彻底不够用了。这一篇把 State 讲透:字段的合并规则怎么定、消息列表为什么需要专门的处理、State 有哪几种写法,以及怎样控制"谁能看到什么数据"。
一、覆盖写入,在哪里会出问题#
先回顾默认行为:节点返回某个字段的新值,LangGraph 就用它替换白板上的旧值。用一个记录执行日志的小例子感受一下:
class State(TypedDict):
logs: list[str]
def step_1(state): return {"logs": ["第一步完成"]}
def step_2(state): return {"logs": ["第二步完成"]}
# START → step_1 → step_2 → END,初始输入 {"logs": ["开始"]}
# 运行结果:{'logs': ['第二步完成']}
我们想要的是一份完整的日志,结果只剩最后一条——每一步都把前面的记录覆盖掉了。如果两个节点并行写同一个字段,情况更糟,上一篇已经见过:直接报 InvalidUpdateError。
把这个问题放大到对话场景:用户说一句、模型回一句、工具返回一段结果,这些都要按顺序存进同一个 messages 列表。覆盖写入意味着每一轮都会把历史清空,Agent 也就失去了上下文。
问题的根源在于:不同的字段需要不同的合并方式。有的字段(比如最终回答)就该覆盖,有的字段(比如日志、消息)应该追加。LangGraph 把这个"合并方式"交给你来指定,它叫 Reducer。
二、Reducer:给字段定一条合并规则#
Reducer 是一个函数,接收两个参数:白板上的当前值和节点刚返回的新值,返回合并之后的结果。每当节点更新某个字段,LangGraph 都会调用这个字段的 Reducer:
flowchart LR
O[白板上的当前值] --> R{{Reducer}}
N[节点返回的新值] --> R
R --> M[合并后的值,写回白板]没有指定时,默认的 Reducer 就是"直接用新值"。要换成追加,只需在字段的类型上附加一个合并函数:
import operator
from typing import Annotated
class State(TypedDict):
logs: Annotated[[], operator.add]


还没有公开留言,成为第一个写下回声的人。