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