RunnableConfig: cómo la configuración fluye por todo el grafo
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 detags/metadata/callbacksde LangChain. - Herencia con override: cuando un subgrafo corre dentro de un nodo del grafo padre, el
configurabledel padre se propaga automáticamente; pero si el subgrafo da explícitamente su propiothread_id, se considera «reinicio del linaje de checkpoints» —véase la lógica de «explicit config» enensure_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) sonCONFIG_KEYS; los campos de runtime reservados por LangGraph se colocan enconfigurablecon el prefijo__pregel_para no colisionar con claves definidas por el usuario —véaseCONFIG_KEY_* 常量:33-77. - Defaults configurables por variable de entorno:
recursion_limitpor defecto se toma de la variableLANGGRAPH_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 elRunnableConfigactual desde el contextvarvar_child_runnable_config; si no hay contexto runnable, lanzaRuntimeError.get_store:32-123— obtiene el store desdeconfig[CONF][CONFIG_KEY_RUNTIME].store; la docstring muestra los dos usos conStateGraphyentrypoint.get_stream_writer:126-196— devuelveruntime.stream_writer, con el que un nodo emite eventos de stream personalizados (stream_mode="custom").ensure_config:322-420— fusiona varios config, aplica elrecursion_limit=DEFAULT_RECURSION_LIMITpor defecto y gestiona el reinicio de coordenadas de checkpoint.merge_configs:147-180— implementación del merge profundo de varios config; el dictconfigurablese fusiona en shallow.patch_configurable:52-62— parchea sólo la claveconfigurable, conservando los demás campos; el loop lo usa para inyectar__pregel_checkpointerdiná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 deconfig[CONF].recursion_limit 校验:2563-2564—_setup_streamleeconfig["recursion_limit"]y lanza un error si es menor que 1.loop.stop:1701—self.stop = self.step + self.config["recursion_limit"] + 1traduce el límite de recursión a «hasta qué paso se puede correr».GraphRecursionError:3005-3011— al llegar aout_of_stepsse lanzaGraphRecursionError, sugiriendo subirrecursion_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.
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)},
)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».
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.checkpointerLímites y fallos
get_configfuera de contexto runnable lanzaRuntimeError(get_config 报错:29); llamar aget_store()directamente en una prueba unitaria revienta —hay que pasar porgraph.invokeo por una función decorada conentrypointpara tener acceso.- En Python < 3.11,
get_store/get_stream_writerno se pueden usar bajo async (async 警告:53-57), porque la propagación del contextvar depende del comportamiento deasyncio.create_taskintroducido en 3.11. recursion_limit < 1lanza un error directo (校验:2563-2564), sin caer silenciosamente al valor por defecto.checkpointer=Trueno se puede usar en un grafo raíz (True 报错:2583-2584):Truesignifica «heredar del grafo padre», y un grafo raíz no tiene padre, así que hace falta pasar explícitamente unBaseCheckpointSaveroFalse.- Activar checkpointer sin dar coordenadas como
thread_idenconfigurablelanza error (checkpointer 要求坐标:2589-2593), pidiendo alguno dethread_id/checkpoint_ns/checkpoint_id. - Un subgrafo que pase
thread_idexplícitamente resetea elconfigurableambiente (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 deconfigurable, 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