RunnableConfig:設定がグラフ全体をどう流れるか
役割
LangGraph でグラフを走らせるには、入力データそのもの以上の「ランタイムコンテキスト」を運ぶ必要があります。recursion_limit、thread_id、checkpoint_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_store、get_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-29—var_child_runnable_configという contextvar から現在のRunnableConfigを取得します。runnable コンテキスト内でない場合はRuntimeErrorを送出します。get_store:32-123—config[CONF][CONFIG_KEY_RUNTIME].storeから store を取得します。ドキュメントにStateGraphとentrypointの 2 つの使い方が載っています。get_stream_writer:126-196—runtime.stream_writerを取得し、ノードがこれでカスタムストリームイベント (stream_mode="custom") を送ります。ensure_config:322-420— 複数の config をマージし、デフォルトrecursion_limit=DEFAULT_RECURSION_LIMITを補い、チェックポイント座標のリセットを処理します。merge_configs:147-180— 複数 config の深いマージの基盤実装。configurable辞書はシャローマージします。patch_configurable:52-62—configurable1 つのキーだけを patch し、他のフィールドは保持します——loop が動的に__pregel_checkpointerなどを注入するのに使います。CONFIG_KEY_* 常量:33-77—__pregel_send/__pregel_read/__pregel_checkpointer/__pregel_runtime/__pregel_resumingなどの予約 key の全集合。CONF 常量:91-92—CONF = "configurable"。すべての予約 key はconfig[CONF]にぶら下がります。recursion_limit 校验:2563-2564—_setup_streamでconfig["recursion_limit"]を直接読み、1 未満だとエラーにします。loop.stop:1701—self.stop = self.step + self.config["recursion_limit"] + 1で、再帰上限を「最大で何ステップ目まで走るか」に翻訳します。GraphRecursionError:3005-3011—out_of_stepsに達した時にGraphRecursionErrorを送出し、ユーザーにrecursion_limitを上げるよう促します。
データフロー
最も重要な部分は ensure_config です。contextvar から継承した config とユーザーが渡した config をマージし、明示的な checkpoint 座標があれば ambient な configurable をリセットし、recursion_limit / configurable / tags / metadata / callbacks がすべて揃うことを保証します。
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)},
)マージ完了後、Pregel._setup_stream(_setup_stream:2563) は config[CONF] から CONFIG_KEY_CHECKPOINTER / CONFIG_KEY_RUNTIME / CONFIG_KEY_CACHE を読み、今回の実行でどの checkpointer / store / cache を使うかを決定します——優先度は「config 注入 > コンストラクタ時の渡し」です。
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境界と失敗
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 を有効化したのに
configurableにthread_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.stop が recursion_limit を使ってどう無限ループを止めるかを見てください。
公式資料:LangGraph ドキュメント · README