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