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 既是检查点的定位坐标 (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