StateGraph:声明状态机的蓝图
职责
StateGraph 是 LangGraph 用户最常直接打交道的类——它是一个构建器 (builder),不是运行时。你把状态 schema、节点函数、边、条件边都注册到它身上,然后调 .compile() 把它编译成一个 CompiledStateGraph(继承自 Pregel)的运行体。它本身没有 invoke / stream,跑图必须先 compile(文档警告:139-144)。
它在架构里处在用户 API 与 Pregel 引擎之间:用户写 StateGraph(State).add_node(...).add_edge(...).compile(),StateGraph 负责把每个状态字段翻译成通道 (channel) 实例、把每个节点包装成 StateNodeSpec、把每条边存进 edges / waiting_edges / branches 三个集合,等编译时再交给 CompiledStateGraph.attach_node / attach_edge / attach_branch 翻译成 PregelNode 的订阅关系(StateGraph 字段:201-213)。
StateGraph 的核心约定是:节点的签名是 State -> Partial<State>,每个状态字段可以可选地用 Annotated[type, reducer] 标注一个归并函数,多个节点同时写同一字段时按 reducer 合并而不是覆盖(类 docstring:131-138)。这套 reducer 机制就是 LastValue (默认覆盖) 和 BinaryOperatorAggregate (operator.add 等) 两种通道类型的来源。
设计动机
为什么不直接写函数调用链,非要做「状态机 + 通道」这套?
- 节点之间解耦:节点只认 state,不认其它节点;谁先跑谁后跑由边决定,改流程不动节点代码。这就是状态机 (state machine) 的核心收益。
- reducer 让并行写有定义:多个节点同一超步里都写
messages字段,如果用Annotated[list, operator.add],引擎就知道该+而不是「后写覆盖前写」(reducer 文档:135-137)。无 reducer 的字段走LastValue通道,行为是「同一超步里多个写直接报错」或「覆盖」,由通道类型决定。 - 构建期校验:compile 时
validate()(validate:1116-1162)会检查「每条边的起点和终点都在 nodes 里」、「START 必须是某条边的起点」、「interrupt 的节点存在」等,把错误挡在跑图之前。 - schema 分离 input/output/state:同一个图可以
input_schema ≠ state_schema ≠ output_schema(schema 入参:217-221),对外接口窄、内部 state 宽,这是写「面向 LLM 的输入 schema ≠ 内部累积 state」这种 agent 的常见模式。 - method chaining:
add_node/add_edge/add_conditional_edges都返回Self(Returns Self:749)——支持链式调用,构建图写得像配置文件而不是命令式序列。
关键文件
StateGraph 类定义:130-144—Generic[StateT, ContextT, InputT, OutputT],文档里明确「builder,不能直接 invoke」。类字段:201-213—edges/nodes/branches/channels/managed/schemas/waiting_edges七个核心容器。__init__:215-269— 接收state_schema/context_schema/input_schema/output_schema,把旧字段名config_schema/input/output转成新名并告警;调_add_schema把 state/input/output 三个 schema 注册进channels/managed。_add_schema:342-372— 用_get_channels从 schema 推出 channels + managed values,冲突时除LastValue外都报错。validate:1116-1162— 编译前校验:遍历所有 edge source/target、检查 START 存在、检查 interrupt 节点存在;最后self.compiled = True。set_node_defaults:271-334— 给整张图设默认 retry / cache / error_handler / timeout 策略,优先级低于add_node显式传参。compile 签名:1164-1217— 接收checkpointer/store/cache/interrupt_before/interrupt_after/name等,返回CompiledStateGraph。CompiledStateGraph:1391-1409— 继承Pregel,只是加了builder/schema_to_mapper字段和 input/output JSON schema 方法。attach_node:1431-1470— 编译期把StateNodeSpec翻成PregelNode,定义_get_updates决定如何从节点返回值里挑出该写哪些通道。BranchSpec.from_path:83-120— 把add_conditional_edges的path(Runnable) +path_map翻译成「条件 -> 目标节点」的 dict,没给path_map时尝试从返回类型Literal[...]推。
数据流
下面是 StateGraph.__init__ 把 schema 注册成通道的核心段——这就是「状态字段 → 通道实例」的翻译发生处:
self.nodes = {}
self.edges = set()
self.branches = defaultdict(dict)
self.schemas = {}
self.channels = {}
self.managed = {}
self.compiled = False
self.waiting_edges = set()
self.state_schema = state_schema
self.input_schema = cast(type[InputT], input_schema or state_schema)
self.output_schema = cast(type[OutputT], output_schema or state_schema)
self.context_schema = context_schema
self._node_defaults: _NodeDefaults = _NodeDefaults()
self._add_schema(self.state_schema)
self._add_schema(self.input_schema, allow_managed=False)
self._add_schema(self.output_schema, allow_managed=False)每个 schema 被 _add_schema 拆成 (channels, managed, type_hints) 三件套,channels 进 self.channels 字典、managed 进 self.managed,同一 key 重复注册时只允许 LastValue 兼容:
self.schemas[schema] = {**channels, **managed}
for key, channel in channels.items():
if key in self.channels:
if self.channels[key] != channel:
if isinstance(channel, LastValue):
pass
else:
raise ValueError(
f"Channel '{key}' already exists with a different type"
)
else:
self.channels[key] = channel边界与失败
StateGraph不能直接invoke(warning:139-144),必须.compile();直接当 Runnable 用会缺nodes等运行期字段。- 状态字段名冲突报错(
channel 冲突:360-362),除非两者都是LastValue——这是为了防止「同一个 key 在两个 schema 里 reducer 不同」导致的隐式行为不一致。 input_schema/output_schema不允许有 managed channels(managed 检查:346-352),managed 是 state 专属的,因为它们不是简单的存值通道,而是ContextManager这种有生命周期的对象。config_schema旧名已废弃(config_schema deprecated:224-231),v1.0 起提示用context_schema,2.0 会移除。compile()在已编译图上加节点只 warning 不报错(compiled warning:932-936),add_node/add_edge都会打印Adding ... to a graph that has already been compiled,但不会反映到已编译的实例里——常见坑是链式调用顺序错了。- validate 要求至少有一条边从 START 出发(
entrypoint 检查:1129-1132),Graph must have an entrypoint,否则不知道从哪开始跑。
小结
StateGraph 是声明式的状态机蓝图:你给它 schema 和节点+边,它负责翻译成 Pregel 能跑的 PregelNode + 通道订阅。它的存在让用户不必直接面对 Pregel 的 actor 模型,只关心「state 字段 + 节点签名 + 边」三件事。
继续看 add_node / add_edge / add_conditional_edges 怎么往这个蓝图里填内容,以及 compile 怎么把它编译成 Pregel。整体运行模型见 Pregel 引擎。
对照官方资料:LangGraph 文档 · README。