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 の 3 つの集合に格納します。コンパイル時に CompiledStateGraph.attach_node / attach_edge / attach_branchPregelNode の購読関係に翻訳します(StateGraph 字段:201-213)。

StateGraph の中心的な約束は、ノードのシグネチャが State -> Partial<State> であることです。各状態フィールドは Annotated[type, reducer] で任意にリデューサー (reducer) を注釈でき、複数ノードが同じフィールドに同時に書き込む際は上書きではなく reducer でマージします(类 docstring:131-138)。この reducer 機構が LastValue (デフォルトで上書き) と BinaryOperatorAggregate (operator.add など) の 2 つのチャネル型の由来です。

設計動機

なぜ関数呼び出しチェーンを直接書くのではなく「状態機械 + チャネル」を採用するのでしょうか?

  • ノード間の疎結合:ノードは state だけを認識し、他のノードを認識しません。誰が先に走るか後に入るかはエッジが決めるため、フローを変えてもノードコードは不要です。これが状態機械 (state machine) の核心的な利益です。
  • reducer が並行書き込みを定義:複数ノードが同一スーパーステップ (superstep) で 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 の 7 つのコアコンテナ。
  • __init__:215-269state_schema / context_schema / input_schema / output_schema を受け取り、旧フィールド名 config_schema / input / output を新名に変換しつつ警告します。_add_schema で state/input/output の 3 つの 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-1217checkpointer / store / cache / interrupt_before / interrupt_after / name などを受け取り、CompiledStateGraph を返します。
  • CompiledStateGraph:1391-1409Pregel を継承し、builder / schema_to_mapper フィールドと input/output JSON schema メソッドを追加しただけです。
  • attach_node:1431-1470 — コンパイル期に StateNodeSpecPregelNode に翻訳し、_get_updates を定義してノード戻り値からどのチャネルに書き込むかを決定します。
  • BranchSpec.from_path:83-120add_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) の 3 点セットに分解されます。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 が 2 つの 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_edgeAdding ... to a graph that has already been compiled を表示しますが、コンパイル済みインスタンスには反映されません——よくある落とし穴はチェーン呼び出しの順序誤りです。
  • validate は START から出るエッジが最低 1 本必要(entrypoint 检查:1129-1132)。Graph must have an entrypoint。でないとどこから走り始めるか分かりません。

まとめ

StateGraph は宣言的な状態機械のブループリントです。schema とノード+エッジを渡すと、Pregel が走らせられる PregelNode + チャネル購読へ翻訳します。これがあるおかげで、ユーザーは Pregel の actor モデルを直接意識せず「state フィールド + ノードシグネチャ + エッジ」の 3 つだけに集中できます。

続けて add_node / add_edge / add_conditional_edges がこのブループリントにどう中身を詰めるか、そして compile がどう Pregel にコンパイルするかを見てください。全体の実行モデルは Pregel エンジン にあります。

公式資料:LangGraph ドキュメント · README