Skip to content

RunnableConfig:設定がグラフ全体をどう流れるか

源码版本1.2.9

役割

LangGraph でグラフを走らせるには、入力データそのもの以上の「ランタイムコンテキスト」を運ぶ必要があります。recursion_limitthread_idcheckpoint_id、コールバック、tags、metadata、そしてノードが使う store / stream_writer は、すべて graph.invoke(input, config) を呼ぶ時に渡され、マージされ、検証され、最後に configurable 辞書に埋め込まれて各ノードの RunnableConfig にパススルーされます。この統一フォーマットの設定オブジェクトが RunnableConfig で、LangChain Core の同名型 (from langchain_core.runnables import RunnableConfig) を直接再利用し、LangGraph が独自に再発明したものではありません。

アーキテクチャの最も入り口に位置します。Pregel.invoke / Pregel.stream に入るとすぐ ensure_config(ensure_config:322) を呼び、ユーザーが渡した断片的なフィールドにデフォルト値を補い、親グラフから継承した configurable と呼び出し時に渡されたものをマージして完全な config を得て、PregelLoop に渡します。スーパーステップ (superstep) ループ全体で、この config はチェックポイント (checkpoint) の位置座標 (thread_id + checkpoint_ns + checkpoint_id) であり、ノードがランタイムツール (store / stream_writer / runtime) を取得する入り口でもあります。

ノード内部では、config 辞書から直接これらのフィールドを取るのではなく——LangGraph は get_config(get_config:17)、get_storeget_stream_writer の 3 つのショートカット関数を提供します。内部では contextvar から現在のコンテキストの RunnableConfig を読み、configurable 辞書から Runtime インスタンスを取り出します。そのため外から内へ向かって、config は「ユーザーが渡す → ensure_config で補完 → loop がパススルー → contextvar に注入 → ノードが取得」という 5 段階を経ます。

設計動機

なぜすべてを RunnableConfig に詰め込み、全新的な GraphConfig クラスを定義しないのでしょうか?

  • LangChain エコシステムの再利用:Runnable プロトコル、tracing、callbacks はすべて RunnableConfig を認識し、グラフノードは LangChain のツールチェーンに自然に互換です。ensure_config(empty = RunnableConfig(...):322-337) は tags / metadata / callbacks のマージルールも LangChain と一致させます。
  • 継承と上書きが可能:サブグラフが親グラフのノード内部で走る場合、親グラフの configurable は自動的にパススルーされます。ただしサブグラフが明示的に自身の thread_id を渡すと、「チェックポイント血統のリセット」と見なされます。ensure_config の「explicit config」ロジックを参照 (explicit checkpoint coordinate:355-367)——そうでないとサブグラフがチェックポイントを親の名前空間に書き込んで二度と見つからなくなります。
  • フィールド境界の明確化:標準フィールド (tags / metadata / callbacks / recursion_limit / configurable) は CONFIG_KEYS で、LangGraph が独自に予約したランタイムフィールドは __pregel_ プレフィックスを付けて configurable に格納し、ユーザー定義キーとの衝突を避けます。CONFIG_KEY_* 常量:33-77 を参照。
  • デフォルト値を環境変数で調整可能:recursion_limit はデフォルトで LANGGRAPH_DEFAULT_RECURSION_LIMIT 環境変数を使い (DEFAULT_RECURSION_LIMIT:32)、コードを変えずにループ上限を調整できます。

主要ファイル

  • get_config:17-29var_child_runnable_config という contextvar から現在の RunnableConfig を取得します。runnable コンテキスト内でない場合は RuntimeError を送出します。
  • get_store:32-123config[CONF][CONFIG_KEY_RUNTIME].store から store を取得します。ドキュメントに StateGraphentrypoint の 2 つの使い方が載っています。
  • get_stream_writer:126-196runtime.stream_writer を取得し、ノードがこれでカスタムストリームイベント (stream_mode="custom") を送ります。
  • ensure_config:322-420 — 複数の config をマージし、デフォルト recursion_limit=DEFAULT_RECURSION_LIMIT を補い、チェックポイント座標のリセットを処理します。
  • merge_configs:147-180 — 複数 config の深いマージの基盤実装。configurable 辞書はシャローマージします。
  • patch_configurable:52-62configurable 1 つのキーだけを patch し、他のフィールドは保持します——loop が動的に __pregel_checkpointer などを注入するのに使います。
  • CONFIG_KEY_* 常量:33-77__pregel_send / __pregel_read / __pregel_checkpointer / __pregel_runtime / __pregel_resuming などの予約 key の全集合。
  • CONF 常量:91-92CONF = "configurable"。すべての予約 key は config[CONF] にぶら下がります。
  • recursion_limit 校验:2563-2564_setup_streamconfig["recursion_limit"] を直接読み、1 未満だとエラーにします。
  • loop.stop:1701self.stop = self.step + self.config["recursion_limit"] + 1 で、再帰上限を「最大で何ステップ目まで走るか」に翻訳します。
  • GraphRecursionError:3005-3011out_of_steps に達した時に GraphRecursionError を送出し、ユーザーに recursion_limit を上げるよう促します。

データフロー

最も重要な部分は ensure_config です。contextvar から継承した config とユーザーが渡した config をマージし、明示的な checkpoint 座標があれば ambient な configurable をリセットし、recursion_limit / configurable / tags / metadata / callbacks がすべて揃うことを保証します。

python
empty = RunnableConfig(
    tags=[],
    metadata=ChainMap(),
    callbacks=None,
    recursion_limit=DEFAULT_RECURSION_LIMIT,
    configurable={},
)
if var_config := var_child_runnable_config.get():
    empty.update(
        {k: v.copy() if k in COPIABLE_KEYS else v
         for k, v in var_config.items() if _is_not_empty(v)},
    )

(ensure_config 取默认值:331-345)

マージ完了後、Pregel._setup_stream(_setup_stream:2563) は config[CONF] から CONFIG_KEY_CHECKPOINTER / CONFIG_KEY_RUNTIME / CONFIG_KEY_CACHE を読み、今回の実行でどの checkpointer / store / cache を使うかを決定します——優先度は「config 注入 > コンストラクタ時の渡し」です。

python
if self.checkpointer is False:
    checkpointer: BaseCheckpointSaver | None = None
elif CONFIG_KEY_CHECKPOINTER in config.get(CONF, {}):
    checkpointer = config[CONF][CONFIG_KEY_CHECKPOINTER]
elif self.checkpointer is True:
    raise RuntimeError("checkpointer=True cannot be used for root graphs.")
else:
    checkpointer = self.checkpointer

(checkpointer 解析:2579-2586)

境界と失敗

  • get_config は runnable コンテキスト外で呼ぶと直接 RuntimeError(get_config 报错:29)。そのためユニットテストで直接 get_store() を呼ぶと失敗します——必ず graph.invoke または entrypoint デコレータを通った関数の中で取得する必要があります。
  • Python 3.11 未満の非同期環境では get_store / get_stream_writer が使えない(async 警告:53-57)。contextvar の伝播が 3.11 で導入された asyncio.create_task の挙動に依存しているためです。
  • recursion_limit < 1 は直接エラー(校验:2563-2564)。デフォルト値へのサイレントフォールバックはしません。
  • checkpointer=True はルートグラフに使えない(True 报错:2583-2584)。True は「親グラフから継承」を意味し、ルートグラフには親がないため、必ず明示的に BaseCheckpointSaver または False を渡す必要があります。
  • checkpointer を有効化したのに configurablethread_id などの座標がないとエラー(checkpointer 要求坐标:2589-2593)。thread_id / checkpoint_ns / checkpoint_id のいずれかが必要です。
  • サブグラフが明示的に thread_id を渡すと ambient な configurable がリセットされる(explicit 坐标重设:362-367)。これはサブグラフがチェックポイントを親グラフの名前空間に書き込んで見つからなくなるのを防ぐためです——ただし逆に、サブグラフに親の configurable カスタムキーを継承させたい場合、このリセットのせいで取得できなくなります。

まとめ

RunnableConfig は LangGraph が LangChain エコシステムと対接する契約で、チェックポイント位置決め、ランタイムツールの注入、コールバックとトレーシングの唯一の担体でもあります。ensure_config のマージルールと __pregel_* 予約キーの分担を理解すれば、「設定がユーザーからノードまでどう伝わるか」という経路をほぼ把握できます。

続けて StateSnapshot がこの config をどうチェックポイントと紐付けるか、そして Pregel エンジンloop.stoprecursion_limit を使ってどう無限ループを止めるかを見てください。

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