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 の 3 つの集合に格納します。コンパイル時に CompiledStateGraph.attach_node / attach_edge / attach_branch が PregelNode の購読関係に翻訳します(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-144—Generic[StateT, ContextT, InputT, OutputT]。ドキュメントに「builder であり、直接 invoke できない」と明記されています。类字段:201-213—edges/nodes/branches/channels/managed/schemas/waiting_edgesの 7 つのコアコンテナ。__init__:215-269—state_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-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) の 3 点セットに分解されます。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 が 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_edgeはAdding ... 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