RunnableConfig:設定如何流遍整張圖
職責
LangGraph 跑一張圖,需要攜帶的「執行期脈絡」遠不止輸入資料本身。recursion_limit、thread_id、checkpoint_id、回呼 (callback)、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 三個快捷函式,內部從 contextvar 讀出當前 context 裡的 RunnableConfig,再從 configurable 字典裡取出 Runtime 實例。所以從外到內,config 經歷了「使用者傳 → ensure_config 補齊 → loop 透傳 → contextvar 注入 → 節點取用」五個階段。
設計動機
為什麼要把一切塞進 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,避免和使用者自訂 key 撞車,見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兩種用法。get_stream_writer:126-196— 拿runtime.stream_writer,節點用它發自訂串流事件 (stream_mode="custom")。ensure_config:322-420— 合併多份 config、補預設recursion_limit=DEFAULT_RECURSION_LIMIT、處理 checkpoint 座標重設。merge_configs:147-180— 多份 config 深度合併的底層實作,configurable字典走淺合併。patch_configurable:52-62— 只 patchconfigurable這一個 key,保留其它欄位——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會重設 ambientconfigurable(explicit 座標重設:362-367),這是為了避免子圖把檢查點寫到父圖命名空間下找不到——但反過來,如果你期望子圖繼承父的configurable自訂 key,這個重設會讓你拿不到。
小結
RunnableConfig 是 LangGraph 跟 LangChain 生態對接的契約,也是檢查點定位、執行期工具注入、回呼與追蹤的唯一載體。理解了 ensure_config 的合併規則和 __pregel_* 保留 key 的分工,基本就掌握了「設定怎麼從使用者傳到節點」這條路。
繼續看 StateSnapshot 怎麼把這份 config 跟檢查點綁在一起,以及 Pregel 引擎 裡 loop.stop 怎麼用 recursion_limit 卡死迴圈。
對照官方資料:LangGraph 文件 · README