𝟯 𝗥𝗲𝗲𝘀𝗰𝗿𝗶𝘁𝘂𝗿𝗮𝘀 𝗱𝗲 𝗟𝗮𝗻𝗴𝗚𝗿𝗮𝗽𝗵: 𝗟𝗼 𝗾𝘂𝗲 𝗲𝗹 𝗰𝗵𝗲𝗰𝗸𝗽𝗼𝗶𝗻𝘁𝗶𝗻𝗴 𝗲𝗻 𝗽𝗿𝗼𝗱𝘂𝗰𝗰𝗶𝗼́𝗻 𝗺𝗲 𝗲𝗻𝘀𝗲𝗻̃𝗼́

Durante tres semanas, nuestro agente de onboarding perdió cada tarea que debía guardar. Los logs no mostraban nada. Ni errores. Ni advertencias. El checkpointer reportaba éxito, pero el estado desaparecía al reanudar.

Reescribí el mismo pipeline de LangGraph tres veces antes de entender la verdad. El framework hizo exactamente lo que le pedí. Simplemente le estaba dando las instrucciones incorrectas.

Si construyes agentes con estado (stateful) para producción, no asumas que los valores por defecto te salvarán. Esto es lo que falló y cómo lo solucioné.

𝗘𝗹 𝗽𝗿𝗶𝗺𝗲𝗿 𝗳𝗮𝗹𝗹𝗼: 𝗣𝗲𝗿𝗱𝗶𝗱𝗮 𝘀𝗶𝗹𝗲𝗻𝗰𝗶𝗼𝘀𝗮 𝗱𝗲𝗹 𝗲𝘀𝘁𝗮𝗱𝗼

Mi agente gestionaba un flujo de onboarding de cinco pasos. Utilicé Postgres para guardar el progreso y que los usuarios pudieran reanudarlo más tarde. Pero cada reanudación empezaba desde el primer paso.

La causa fue mi esquema de estado (state schema). En LangGraph, cada nodo devuelve una actualización que se fusiona con el estado. Si no especificas cómo fusionarla, el comportamiento por defecto es sobrescribir.

Pensé que mi lista de mensajes se añadiría (append). En su lugar, cada nuevo nodo reemplazaba todo el historial con un solo mensaje. El checkpoint guardaba los datos incorrectos a la perfección.

La solución: Usar campos Annotated con reductores (reducers) explícitos.

• Usa el operador add para las listas de mensajes. • Usa una función de fusión (merge) personalizada para diccionarios. • Usa la sobrescritura por defecto solo para valores únicos como "step".

𝗘𝗹 𝘀𝗲𝗴𝘂𝗻𝗱𝗼 𝗳𝗮𝗹𝗹𝗼: 𝗗𝗲𝘀𝗲𝗿𝗶𝗮𝗹𝗶𝘇𝗮𝗰𝗶𝗼́𝗻 𝘆 𝗰𝗼𝗻𝗰𝘂𝗿𝗿𝗲𝗻𝗰𝗶𝗮

La segunda reescritura enfrentó dos problemas nuevos:

  1. Filas corruptas: Almacené objetos personalizados en el estado. El serializador no podía manejarlos. Esto creó filas que existían pero eran inutilizables.
  2. Claves duplicadas: WhatsApp reintenta los webhooks si no respondes rápido. Si llegaban dos mensajes a la vez, dos ejecuciones del grafo intentaban escribir en el mismo hilo (thread). Esto causaba colisiones en la base de datos.

Las soluciones: • Elimina los objetos personalizados. Usa solo diccionarios simples y tipos estándar de LangChain. • Gestiona los webhooks fuera del grafo. Usa una cola y una clave de idempotencia para descartar duplicados. • Añade un bloqueo de base de datos (database lock). Asegúrate de que solo ocurra una ejecución por hilo a la vez.

𝗟𝗮 𝘁𝗲𝗿𝗰𝗲𝗿𝗮 𝗿𝗲𝗲𝘀𝗰𝗿𝗶𝘁𝘂𝗿𝗮: 𝗘𝗹 𝗽𝗮𝘁𝗿𝗼́𝗻 𝗲𝘀𝘁𝗮́𝗯𝗹𝗲

La versión final se centró en tres principios:

• Small graphs: I broke one massive graph into three smaller subgraphs. This reduced the blast radius of bugs. • Explicit checkpoints: I stopped checkpointing after every single node. I only checkpoint at meaningful resume points. This cut database writes by 60%. • Idempotent nodes: This is vital. Every node must produce the same result if it runs twice. If a node sees that a task is already done in the state, it should return immediately. This prevents double-charging for expensive model calls.

𝗟𝗲𝘀𝘀𝗼𝗻𝘀 𝗳𝗼𝗿 𝘆𝗼𝘂:

  • Read reducer semantics before you write code.
  • Do not store custom objects in state.
  • Move concurrency control outside the graph.
  • Make every node idempotent.

The framework did not fail. My assumptions did.

Source: https://dev.to/elenarevicheva/three-langgraph-rewrites-what-production-checkpointing-actually-taught-me-ok9

Optional learning community: https://t.me/GyaanSetuAi