Skip to content

RunnableConfig:設定如何流遍整張圖

源码版本1.2.9

職責

LangGraph 跑一張圖,需要攜帶的「執行期脈絡」遠不止輸入資料本身。recursion_limitthread_idcheckpoint_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_storeget_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,文件裡給了 StateGraphentrypoint 兩種用法。
  • 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 — 只 patch configurable 這一個 key,保留其它欄位——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_stream 裡直接讀 config["recursion_limit"],小於 1 就報錯。
  • loop.stop:1701self.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 全部到位。

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.invokeentrypoint 裝飾的函式才能取到。
  • 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 表示「從父圖繼承」,根圖沒父圖,所以必須顯式給一個 BaseCheckpointSaverFalse
  • 開了 checkpointer 但沒給 configurable 裡的 thread_id 等座標會報錯(checkpointer 要求座標:2589-2593),提示需要 thread_id / checkpoint_ns / checkpoint_id 之一。
  • 子圖顯式傳 thread_id 會重設 ambient configurable(explicit 座標重設:362-367),這是為了避免子圖把檢查點寫到父圖命名空間下找不到——但反過來,如果你期望子圖繼承父的 configurable 自訂 key,這個重設會讓你拿不到。

小結

RunnableConfig 是 LangGraph 跟 LangChain 生態對接的契約,也是檢查點定位、執行期工具注入、回呼與追蹤的唯一載體。理解了 ensure_config 的合併規則和 __pregel_* 保留 key 的分工,基本就掌握了「設定怎麼從使用者傳到節點」這條路。

繼續看 StateSnapshot 怎麼把這份 config 跟檢查點綁在一起,以及 Pregel 引擎loop.stop 怎麼用 recursion_limit 卡死迴圈。

對照官方資料:LangGraph 文件 · README