LangGraph — Módulo 11: a producción
Undécimo módulo del curso de LangGraph: ejecución durable y modos de durability (exit, async, sync), nodos deterministas e idempotentes, RetryPolicy, CachePolicy y time travel para depurar.
☰ Índice del contenido
Undécimo módulo del Curso completo de LangGraph. Tu agente funciona en el notebook. Ahora la pregunta distinta: ¿qué pasa cuando el servidor se cae a mitad de una ejecución? ¿Cuando la API del proveedor devuelve un 429? ¿Cuando un usuario reporta un problema de ayer y necesitas reconstruirlo?
La tesis del módulo
Producción no es “el mismo código con más tráfico”: es asumir que todo falla. LangGraph ya te dio la pieza clave en el Módulo 6 —el estado persistido paso a paso—. Este módulo es sobre usarla bien: qué tan seguido guardar, cómo escribir nodos que sobrevivan a re-ejecutarse, y cómo volver al pasado cuando algo salió mal.
Ejecución durable: lo que ya tienes
Que el checkpointer guarde el estado tras cada paso significa algo más grande que “memoria”: significa que una ejecución interrumpida puede continuar desde donde quedó. Eso es ejecución durable.
Un proceso se cae en el paso 7 de 12. Al reanudar el thread, los pasos 1 a 6 no se repiten: LangGraph carga el último checkpoint y sigue. Es la misma mecánica que hace posible el interrupt() del Módulo 7 — solo que ahí la interrupción la pediste tú y aquí la causó un crash.
El compromiso: modos de durabilidad
Guardar tras cada paso cuesta. LangGraph te deja elegir cuánto pagar con el parámetro durability:
graph.invoke(entrada, config, durability="sync")
| Modo | Cuándo escribe | Trade-off |
|---|---|---|
"exit" | Solo al terminar la ejecución | El más rápido. Si el proceso muere a medio camino, no hay nada que recuperar. |
"async" | En paralelo con el siguiente paso | Buen equilibrio. Riesgo pequeño de perder el último checkpoint en un crash. |
"sync" | Antes de continuar al siguiente paso | Máxima garantía, algo más lento. |
CUÁNDO SE ESCRIBE EL CHECKPOINT (durability)
pasos: 1 2 3 4 END
"sync" x x x x x antes de seguir
"async" x x x x x en paralelo
"exit" - - - - x solo al final
x = checkpoint escrito. Un crash en el paso 3:
sync y async retoman desde el 3; exit vuelve a empezar.
Cómo elegir, en la práctica:
"sync"cuando los pasos son caros o tienen efectos externos (cobrar, enviar, escribir en otro sistema). Repetir un paso te sale más caro que el overhead de escribir."async"para el chat típico. Es el equilibrio razonable."exit"para lotes cortos que puedes simplemente reintentar completos. Si el trabajo entero cuesta dos segundos, no vale la pena.
Una consecuencia que conviene tener clara: con "exit" un interrupt() no puede sobrevivir a la caída del proceso — no hay checkpoints intermedios donde apoyarse.
La regla que hace posible todo: nodos re-ejecutables
Todo lo anterior descansa en un supuesto: un nodo puede ejecutarse más de una vez. Pasa al reanudar tras un crash, al retomar un interrupt (lo vimos en el Módulo 7) y al reintentar por un error.
De ahí salen dos reglas no negociables:
1. Los efectos secundarios deben ser idempotentes. Ejecutarlos dos veces tiene que dar el mismo resultado que ejecutarlos una.
# ❌ Reanudar cobra dos veces
def cobrar(state):
api.cobrar(state["monto"])
# ✅ La clave de idempotencia protege
def cobrar(state):
api.cobrar(state["monto"], idempotency_key=state["pedido_id"])
Si la API externa no ofrece clave de idempotencia, guárdalo tú en el estado: revisa si ya lo hiciste antes de hacerlo.
2. Un nodo debe ser lo más determinista posible. El no-determinismo escondido —random, datetime.now(), un uuid4() a mitad del nodo— hace que la re-ejecución produzca algo distinto y los bugs sean irreproducibles. Cuando necesites un valor así, genéralo una vez y guárdalo en el estado.
Corolario práctico: un nodo, un efecto secundario. Si un nodo manda un correo y actualiza la base y llama a un servicio, cualquier reintento repite las tres cosas. Separarlos en nodos distintos hace que un checkpoint caiga entre medio.
Reintentos
Los errores transitorios —un 429, un timeout, un cierre de conexión— no deberían tumbar la ejecución. RetryPolicy se aplica por nodo:
from langgraph.types import RetryPolicy
builder.add_node(
"consultar_api",
consultar_api,
retry_policy=RetryPolicy(
max_attempts=4,
initial_interval=0.5,
backoff_factor=2.0,
jitter=True,
),
)
backoff_factor=2.0 duplica la espera en cada intento (0.5s, 1s, 2s…) y jitter=True le suma algo de aleatoriedad para que, si muchas ejecuciones fallan a la vez, no reintenten todas en el mismo instante y vuelvan a tumbar el servicio.
Ponlo en los nodos que tocan el mundo exterior. No lo pongas en un nodo que falla por un error de lógica: reintentar cuatro veces algo que siempre va a fallar solo retrasa el diagnóstico.
Caché de nodos
Si un nodo es caro y determinista, no lo ejecutes dos veces con la misma entrada:
from langgraph.types import CachePolicy
builder.add_node(
"embeber_documentos",
embeber_documentos,
cache_policy=CachePolicy(ttl=3600),
)
Ideal para embeddings, consultas pesadas o cualquier transformación pura. No lo uses en nodos con efectos secundarios ni donde quieras variedad — cachear la llamada al LLM de un chat significa que el usuario recibe la misma respuesta ante la misma pregunta.
Time travel: volver al pasado
Del Módulo 6 ya sabes leer el historial. Lo que no habíamos usado es que puedes re-ejecutar desde un checkpoint anterior.
# 1. Encontrar el momento en que se torció
historial = list(agente.get_state_history(config))
for snap in historial:
print(snap.metadata["step"], snap.next)
# 2. Elegir ese checkpoint
punto = historial[3]
config_pasado = punto.config # trae el checkpoint_id
# 3. Re-ejecutar desde ahí
agente.invoke(None, config_pasado)
Los nodos anteriores al checkpoint se saltan; los posteriores vuelven a correr de verdad (incluidas las llamadas al modelo).
Lo verdaderamente útil es combinarlo con update_state: cambias algo en ese punto del pasado y ves cómo habría seguido. Es la forma de responder “¿y si el modelo hubiera elegido la otra tool?” sin reproducir la conversación entera a mano.
Cuidado: re-ejecutar repite los efectos secundarios de los nodos posteriores. Si en el paso 5 se manda un correo, hacer time travel al paso 3 lo manda otra vez. La idempotencia de la que hablábamos arriba es lo que hace que esto sea seguro.
Checklist de producción
Antes de exponer un agente a usuarios reales:
- Checkpointer persistente (Postgres), no
InMemorySaver -
durabilityelegido a conciencia, no por defecto - Efectos secundarios idempotentes y aislados en su propio nodo
-
RetryPolicyen los nodos que llaman servicios externos -
recursion_limitajustado a tu grafo real, no el default - Trazas activas (LangSmith u otro) desde el día uno, no después del primer incidente
- Límite de tokens: recorte o resumen del historial largo (Módulo 6)
- Aprobación humana en las acciones destructivas o costosas (Módulo 7)
- Secretos por variables de entorno, nunca en el código
- Un plan para borrar los datos de un usuario que lo pida (Módulo 10)
Errores comunes
- Dejar
InMemorySaveren producción. Funciona en tus pruebas y pierde todo en el primer deploy. - Efectos secundarios no idempotentes. El bug más caro de este módulo: correos duplicados, cobros dobles.
- Reintentar errores de lógica. Cuatro fallos idénticos y un diagnóstico más lento.
- Cachear nodos con efectos secundarios. El efecto no ocurre en el hit de caché y no entiendes por qué.
- Elegir
"exit"sin querer. Y descubrir en el primer crash que no había nada guardado. - Instrumentar después del incidente. Sin trazas del momento, el incidente ya no se puede reconstruir.
La frase para llevarse
“Producción no premia al agente más inteligente, sino al que se recupera mejor. Escribe cada nodo pensando en que va a ejecutarse dos veces — porque va a pasar.”
Qué viene
Cerramos el curso con el Módulo 12: los patrones avanzados que se construyen sobre todo lo anterior. Map-reduce con Send, reflexión, planificación, y cómo evaluar si tu agente realmente mejoró.
Material relacionado
Preguntas sobre este curso
Regístrate gratis para hacer preguntas y guardar tu progreso.
✉ Entérate de lo nuevo
Recibe un aviso cuando publique nuevos cursos, workshops o investigaciones. Sin spam.