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-92 — CONF = "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:1701 — self.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