Skip to content

StateGraph:声明状态机的蓝图

源码版本1.2.9

职责

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-144Generic[StateT, ContextT, InputT, OutputT],文档里明确「builder,不能直接 invoke」。
  • 类字段:201-213edges / 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_edgespath (Runnable) + path_map 翻译成「条件 -> 目标节点」的 dict,没给 path_map 时尝试从返回类型 Literal[...] 推。

数据流

下面是 StateGraph.__init__ 把 schema 注册成通道的核心段——这就是「状态字段 → 通道实例」的翻译发生处:

python
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)

(字段初始化:251-269)

每个 schema 被 _add_schema 拆成 (channels, managed, type_hints) 三件套,channels 进 self.channels 字典、managed 进 self.managed,同一 key 重复注册时只允许 LastValue 兼容:

python
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

(_add_schema 注册逻辑:353-364)

边界与失败

  • 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