Skip to content

RunnableConfig: cómo la configuración fluye por todo el grafo

源码版本1.2.9

Responsabilidades

Ejecutar un grafo en LangGraph requiere un «contexto de ejecución» que va mucho más allá de los propios datos de entrada. recursion_limit, thread_id, checkpoint_id, callbacks, tags, metadata, así como el store / stream_writer que usan los nodos, deben poder pasarse al llamar graph.invoke(input, config), fusionarse, validarse y finalmente incrustarse en el dict configurable que se propaga al RunnableConfig de cada nodo. Este objeto de configuración con formato unificado se llama RunnableConfig y reutiliza directamente el tipo homónimo de LangChain Core (from langchain_core.runnables import RunnableConfig); no es una rueda reinventada por LangGraph.

Cae en la entrada misma de la arquitectura: Pregel.invoke / Pregel.stream en cuanto entran llaman a ensure_config (ensure_config:322) para completar los campos sueltos que pasó el usuario con valores por defecto, fusionar el configurable heredado del grafo padre con el que viene en la llamada, y obtener un config completo que se entrega a PregelLoop. A lo largo del bucle de superpasos (superstep), este config es a la vez la coordenada del punto de control (checkpoint) (thread_id + checkpoint_ns + checkpoint_id) y la entrada por la que los nodos toman las herramientas de runtime (store / stream_writer / runtime).

Dentro del nodo, no se accede a estos campos directamente desde el dict de config —LangGraph expone tres funciones atajo: get_config (get_config:17), get_store y get_stream_writer; leen el RunnableConfig del contextvar de contexto actual y, a partir del dict configurable, obtienen la instancia Runtime. Así, de fuera hacia dentro, el config pasa por cinco fases: «lo pasa el usuario → ensure_config lo completa → el loop lo propaga → se inyecta en el contextvar → el nodo lo consume».

Motivación de diseño

¿Por qué meter todo en RunnableConfig en lugar de definir una clase nueva GraphConfig?

  • Reutilizar el ecosistema LangChain: el protocolo Runnable, el tracing y los callbacks reconocen RunnableConfig; los nodos del grafo son compatibles de fábrica con la cadena de herramientas de LangChain. ensure_config (empty = RunnableConfig(...):322-337) replica incluso las reglas de merge de tags / metadata / callbacks de LangChain.
  • Herencia con override: cuando un subgrafo corre dentro de un nodo del grafo padre, el configurable del padre se propaga automáticamente; pero si el subgrafo da explícitamente su propio thread_id, se considera «reinicio del linaje de checkpoints» —véase la lógica de «explicit config» en ensure_config (explicit checkpoint coordinate:355-367)—. De lo contrario el subgrafo escribiría sus checkpoints bajo el namespace del padre y no los encontraría después.
  • Frontera de campos clara: los campos estándar (tags / metadata / callbacks / recursion_limit / configurable) son CONFIG_KEYS; los campos de runtime reservados por LangGraph se colocan en configurable con el prefijo __pregel_ para no colisionar con claves definidas por el usuario —véase CONFIG_KEY_* 常量:33-77.
  • Defaults configurables por variable de entorno: recursion_limit por defecto se toma de la variable LANGGRAPH_DEFAULT_RECURSION_LIMIT (DEFAULT_RECURSION_LIMIT:32), así se puede subir el límite de bucle sin tocar código.

Archivos clave

  • get_config:17-29 — lee el RunnableConfig actual desde el contextvar var_child_runnable_config; si no hay contexto runnable, lanza RuntimeError.
  • get_store:32-123 — obtiene el store desde config[CONF][CONFIG_KEY_RUNTIME].store; la docstring muestra los dos usos con StateGraph y entrypoint.
  • get_stream_writer:126-196 — devuelve runtime.stream_writer, con el que un nodo emite eventos de stream personalizados (stream_mode="custom").
  • ensure_config:322-420 — fusiona varios config, aplica el recursion_limit=DEFAULT_RECURSION_LIMIT por defecto y gestiona el reinicio de coordenadas de checkpoint.
  • merge_configs:147-180 — implementación del merge profundo de varios config; el dict configurable se fusiona en shallow.
  • patch_configurable:52-62 — parchea sólo la clave configurable, conservando los demás campos; el loop lo usa para inyectar __pregel_checkpointer dinámicamente.
  • CONFIG_KEY_* 常量:33-77 — catálogo completo de claves reservadas: __pregel_send / __pregel_read / __pregel_checkpointer / __pregel_runtime / __pregel_resuming, etc.
  • CONF 常量:91-92CONF = "configurable"; todas las claves reservadas cuelgan de config[CONF].
  • recursion_limit 校验:2563-2564_setup_stream lee config["recursion_limit"] y lanza un error si es menor que 1.
  • loop.stop:1701self.stop = self.step + self.config["recursion_limit"] + 1 traduce el límite de recursión a «hasta qué paso se puede correr».
  • GraphRecursionError:3005-3011 — al llegar a out_of_steps se lanza GraphRecursionError, sugiriendo subir recursion_limit.

Flujo de datos

La pieza clave es ensure_config: fusiona el config heredado del contextvar con el que pasa el usuario y, si encuentra coordenadas de checkpoint explícitas, resetea el configurable ambiente, asegurando que recursion_limit / configurable / tags / metadata / callbacks están todos presentes.

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)

Tras la fusión, Pregel._setup_stream (_setup_stream:2563) lee CONFIG_KEY_CHECKPOINTER / CONFIG_KEY_RUNTIME / CONFIG_KEY_CACHE desde config[CONF] y decide qué checkpointer / store / cache usar en esta ejecución —la prioridad es «config inyectado > pasado al constructor».

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)

Límites y fallos

  • get_config fuera de contexto runnable lanza RuntimeError (get_config 报错:29); llamar a get_store() directamente en una prueba unitaria revienta —hay que pasar por graph.invoke o por una función decorada con entrypoint para tener acceso.
  • En Python < 3.11, get_store / get_stream_writer no se pueden usar bajo async (async 警告:53-57), porque la propagación del contextvar depende del comportamiento de asyncio.create_task introducido en 3.11.
  • recursion_limit < 1 lanza un error directo (校验:2563-2564), sin caer silenciosamente al valor por defecto.
  • checkpointer=True no se puede usar en un grafo raíz (True 报错:2583-2584): True significa «heredar del grafo padre», y un grafo raíz no tiene padre, así que hace falta pasar explícitamente un BaseCheckpointSaver o False.
  • Activar checkpointer sin dar coordenadas como thread_id en configurable lanza error (checkpointer 要求坐标:2589-2593), pidiendo alguno de thread_id / checkpoint_ns / checkpoint_id.
  • Un subgrafo que pase thread_id explícitamente resetea el configurable ambiente (explicit 坐标重设:362-367) para evitar que sus checkpoints queden bajo el namespace del padre y no se encuentren —pero, a la inversa, si esperabas que el subgrafo heredara tus claves personalizadas de configurable, ese reset te las quita.

Resumen

RunnableConfig es el contrato de LangGraph con el ecosistema LangChain y, a la vez, el único portador de la localización del checkpoint, la inyección de herramientas de runtime, los callbacks y la trazabilidad. Entender las reglas de fusión de ensure_config y la división entre las claves reservadas __pregel_* equivale a entender básicamente «cómo la configuración viaja desde el usuario hasta el nodo».

Continúa en StateSnapshot para ver cómo este config se ata al checkpoint, y en motor Pregel para ver cómo loop.stop usa recursion_limit para cortar bucles infinitos.

Véase la documentación oficial: documentación de LangGraph · README