Inicio / Artículos / Cómo sobreviven los agentes de LangGraph a los reinicios: puntos de control, gestores de puntos de control, hilos

Cómo sobreviven los agentes de LangGraph a los reinicios: puntos de control, gestores de puntos de control, hilos

Aprende cómo funciona la persistencia en LangGraph: por qué los agentes pierden todo sin ella, y cómo el estado, los puntos de control, los gestores de puntos de control y los IDs de hilo permiten reanudar una ejecución después de una caída.

4200 palabras

Un agente que mantiene todo en la memoria de proceso lo olvida en el momento en que el proceso se detiene: una implementación, un cierre inesperado, un tiempo de espera no manejado o incluso el final de una solicitud son suficientes para borrar la conversación y todos los resultados intermedios. LangGraph aborda este problema mediante la persistencia, un mecanismo que registra el estado del grafo después de cada paso para que una ejecución pueda ser pausada, recuperada y continuada en lugar de repetida. Esta guía construye el modelo mental desde cero: qué falla sin persistencia, cómo se relaciona el estado de LangGraph con los puntos de control, qué función tienen estos puntos de control, cómo los identificadores de hilos mantienen separadas las conversaciones y cuál es el lugar que ocupa el punto de control en memoria. Al final, podrás asignar la persistencia a un grafo, entender con precisión qué se guarda y cuándo, y saber por qué la opción en memoria es solo una herramienta de desarrollo.

La persistencia también es la base de varias funcionalidades que suelen discutirse por separado: los pasos de aprobación humana, la llamada a herramientas a lo largo de múltiples interacciones y los agentes de ejecución prolongada dependen todos de la capacidad de detener un proceso y reanudarlo más tarde. Comprenderla primero hace que esas funcionalidades resulten mucho menos misteriosas.

Por qué los agentes necesitan un estado que sobreviva al proceso

Imagínese escribiendo un documento largo durante dos horas sin guardarlo, y luego perder la electricidad. El trabajo simplemente desaparece y debe comenzar de nuevo desde la primera página. Un agente sin persistencia se encuentra exactamente en esa situación cada vez que se reinicia. No tiene registro de lo que ocurrió unos segundos antes, y mucho menos de lo que sucedió días atrás.

En una frase, la persistencia significa almacenar el estado de una aplicación en un lugar duradero para que pueda recuperarse y continuarse después de que el programa se detenga. Funciona como un botón de guardar automático que se activa casi después de cada paso, sin que nadie tenga que presionarlo.

Esto es más importante para los agentes que para el código clásico de solicitud/respuesta, porque los agentes modernos rara vez se limitan a una sola pregunta y una sola respuesta. Por lo general:

  • mantienen conversaciones durante horas o días;
  • se detienen y esperan a que una persona apruebe una acción;
  • realizan varias llamadas a herramientas para completar una tarea;
  • piensan paso a paso durante un largo período de tiempo;
  • tienen que seguir funcionando tras reinicios y caídas sin perder el progreso.

La RAM es rápida, pero es volátil: se vacía cuando el proceso termina. Todo lo que un agente necesite más allá de la vida útil de un proceso debe escribirse en almacenamiento duradero y leerse posteriormente. En LangGraph, ese almacenamiento y la infraestructura que lo gestiona son a lo que se refiere la “persistencia”.

Qué falla cuando un agente no tiene persistencia

El problema es más fácil de comprender a través de modos concretos de fallo. Cada uno de los siguientes casos es habitual en entornos de producción.

Pérdida de energía a mitad de una tarea larga

Un agente está resumiendo un informe de 200 páginas y ha llegado a la página 100 cuando la máquina pierde energía. Sin estado guardado, no hay registro de que se haya completado la mitad del trabajo, por lo que la siguiente ejecución comienza en la página 1.

Reinicio rutinario del servidor

El agente funciona en un servidor en la nube, y un despliegue normal lo reinicia. Cada usuario que estaba en medio de una conversación pierde su historial. El chatbot ya ni siquiera recuerda el nombre de un usuario que se le proporcionó hace dos minutos.

Una caída en medio de una tarea de varios pasos

Dentro de una tarea más amplia, el agente llama a una API externa que se agota en tiempo, y el proceso de Python muere debido a una excepción no manejada. El paso fallido se pierde, al igual que todo lo que el agente había completado antes.

Flujos de trabajo que duran horas o días

Algunos agentes están diseñados para ser deliberadamente lentos. Imagínese uno que monitorea los precios de las acciones y solo actúa cuando se supera un umbral. Si todo su estado está en memoria, no puede ser pausado, redesplegado ni trasladado a otra máquina sin tener que comenzar de nuevo.

Aprobación humana que lleva horas

Un agente redacta un documento legal que debe ser aprobado por un abogado antes de ser enviado. El abogado no puede consultar la cola durante seis horas. Mantener un proceso congelado y ocupar recursos tanto tiempo es una pérdida de recursos, y si el servidor se reinicia mientras se espera, la tarea desaparece.

Razonamiento multietapa que falla tarde

Los agentes complejos suelen dividir el trabajo en un ciclo de planificación, búsqueda, verificación y resumen. Si falla el paso 7 de 10, volver a ejecutar los pasos del 1 al 6 es una pérdida de tiempo, llamadas a API y dinero.

Por qué “simplemente ejecutarlo de nuevo” no es una estrategia

Reiniciar desde cero parece aceptable hasta que se calculan los costos:

  • Dinero. Cada llamada al LLM consume tokens, por lo que volver a ejecutar seis pasos exitosos porque el séptimo falló significa pagar por ellos dos veces.
  • Tiempo. Los usuarios tienen que esperar mientras se repite el trabajo por el cual ya habían esperado.
  • Confianza. Un asistente bancario que olvida una solicitud de préstamo cada vez que se actualiza la página no parece ser un producto real.
  • Efectos secundarios. Si un paso anterior ya envió un correo electrónico o cargó una tarjeta, repetirlo causa daños reales. Algunos pasos no son seguros para reproducirse.
  • Ese último punto es el más importante. El objetivo de la persistencia no es solo ahorrar esfuerzo; también consiste en permitir que una aplicación se pause, falle, reinicie o espere y luego continúe desde donde realmente se detuvo, de modo que el trabajo completado permanezca así.

    Una definición precisa de persistencia

    Teniendo en cuenta el problema, es útil una definición más precisa: la persistencia es la capacidad de un sistema para escribir sus datos internos, su estado, en un almacenamiento duradero, de modo que dichos datos sobrevivan a la ejecución actual y puedan cargarse nuevamente para continuar su ejecución exactamente desde donde se interrumpió.

    Esa definición incluye tres capacidades distintas:

    1. Guardado del estado: registrar lo que el agente conoce actualmente y qué ha hecho.
    2. Recuperación de una ejecución anterior: leer ese registro nuevamente, incluso después de un reinicio.
    3. Reanudación del flujo de trabajo: continuar desde el punto recuperado en lugar de desde el principio.

    Memoria temporal versus almacenamiento duradero

    Una fuente frecuente de confusión es la diferencia entre almacenar datos temporalmente y hacer que perduren. Un diccionario regular de Python que contiene la conversación representa memoria temporal: cuando finaliza el proceso, el diccionario desaparece. La persistencia implica tomar una instantánea de esos datos y guardarla en un lugar que sobreviva al proceso, generalmente una base de datos. Esa es la idea principal; el resto de esta guía explica cómo LangGraph la implementa.

    Qué te aporta la persistencia en producción

    Más allá de evitar la pérdida de trabajo, la persistencia cambia los tipos de sistemas que puedes crear.

    • Agentes de ejecución prolongada. Los agentes que recorren muchas páginas, procesan grandes conjuntos de datos o esperan eventos externos pueden ser pausados y reanudados en cualquier momento, en cualquier máquina que pueda acceder al almacenamiento.
    • Tolerancia a fallos. Un sistema tolerante a fallos sigue funcionando correctamente cuando ocurren caídas, fallas de red o tiempos de espera. Con persistencia, un fallo solo afecta al paso que se estaba ejecutando, no a toda la tarea.
    • Flujos de trabajo de aprobación. Cuando alguien debe revisar algo, el agente puede detenerse durante el tiempo necesario sin perder nada. Construir esto se vuelve sencillo una vez que el estado es duradero.
  • Recuperación sin código personalizado. No es necesario escribir rutinas especiales de recuperación. Basta con cargar el punto de control más reciente guardado y continuar; LangGraph se encarga de esto una vez que está habilitada la persistencia.
  • Productos fiables. Los usuarios reales no aceptarán tener que empezar de nuevo después de cada despliegue rutinario. La persistencia marca la gran diferencia entre una demostración frágil y un producto real.
  • Menor costo. Los trabajos costosos que ya han tenido éxito, como generaciones prolongadas o llamadas a herramientas lentas, no se vuelven a pagar.
  • Mejor experiencia. La gente espera que un asistente de chat recuerde la conversación incluso después de cerrar la pestaña y volver. Esa capacidad de recordar es precisamente la persistencia en acción.
  • Una prueba rápida para determinar si lo necesitas: si el proceso se reiniciara ahora mismo, ¿el usuario estaría insatisfecho? Si la respuesta es sí, el grafo necesita persistencia.

    Estado: lo que realmente se guarda

    La persistencia en LangGraph solo tiene sentido una vez que se entiende el estado, porque persistir un grafo realmente significa guardar su estado en momentos adecuadamente elegidos.

    Una aplicación de LangGraph es un grafo: un conjunto de pasos, llamados nodos, conectados por aristas, con datos que fluyen a través de ellos. A medida que la ejecución avanza entre los nodos, estos necesitan una estructura compartida para leer y escribir en ella. Esa estructura compartida es el estado. Una imagen útil es una pizarra en una sala de reuniones: cada nodo se acerca, lee lo que hay allí, agrega su propia contribución y la pasa al siguiente nodo.

    El estado suele declararse como un diccionario tipado. El primer paso es la importación:

    from typing import TypedDict
    

    El esquema enumera luego las claves con las que trabaja el grafo y sus tipos. Aquí, el estado registra una lista de mensajes, el nombre del usuario y un contador de pasos:

    class State(TypedDict):
        messages: list
        user_name: str
        step_count: int
    

    Dado que State es un TypedDict, se trata simplemente de un diccionario con un conjunto fijo de claves y tipos declarados. Cada nodo recibe el estado actual y devuelve una actualización parcial, y LangGraph fusiona esa actualización en el estado compartido.

    Por qué todo gira en torno al estado

    El estado es el centro de una aplicación LangGraph:

    • los nodos lo leen para decidir qué hacer;
    • los nodos escriben sus resultados de vuelta en él;
    • las aristas pueden dirigirse a diferentes nodos según los valores que contiene;
    • la persistencia lo guarda y lo restaura.

    Una vez que se haga clic en ello, la persistencia se reduce a una sola frase: después de cada paso, tome una foto del estado y guárdela en un lugar seguro.

    Instantáneas después de cada paso

    Mantenga este modelo durante el resto de la guía. Cada vez que un nodo finaliza, LangGraph captura el estado actual y lo guarda en almacenamiento. Si el proceso se detiene justo después de que termine el segundo nodo, la instantánea de ese momento sigue existiendo, por lo que la ejecución puede continuar desde allí en lugar de desde el primer nodo. Esa instantánea tiene un nombre: un punto de control.

    Puntos de control: instantáneas del estado en un momento determinado

    punto de control es una instantánea del estado del grafo en un momento específico. El término proviene del mismo ámbito que en los videojuegos: es un lugar seguro al que se puede regresar en lugar de volver a jugar todo el nivel.

    Qué permiten los puntos de control

    Un punto de control responde de manera fiable a una pregunta: ¿cómo era el estado inmediatamente después de que finalizara un paso determinado? Sin puntos de control, solo se conoce el estado actual, y eso únicamente mientras el programa está en ejecución. Con ellos, se puede inspeccionar cualquier punto anterior del proceso, y el sistema puede recuperarse al más reciente en caso de fallo.

    Los puntos de control se crean automáticamente

    Un detalle que sorprende a muchos principiantes es que nunca se guardan los puntos de control manualmente. Una vez que se asigna un generador de puntos de control al grafo, LangGraph escribe un nuevo punto de control después de cada superpaso. Un superpaso corresponde a una iteración del bucle de ejecución del grafo; en un grafo lineal simple, equivale a la finalización de un nodo, mientras que en un grafo con ramas paralelas, todos los nodos programados para la misma iteración pertenecen a un mismo superpaso. Usted escribe código de grafo normal, y el guardado se realiza al mismo tiempo.

    Qué contiene un punto de control

    Un punto de control suele registrar:

    • id: un identificador único, ordenado de modo que los puntos de control posteriores aparezcan después de los anteriores;
    • ts: la fecha en que se creó el punto de control;
    • channel_values: los datos del estado en sí, como mensajes y otras variables, en ese momento;
    • channel_versions: contadores de versión internos que LangGraph utiliza para rastrear qué partes del estado han cambiado;
    • metadatos: información de registro, como el nodo que generó el punto de control y el número de paso.

    No es necesario memorizar esta estructura. La definición práctica es suficiente: un punto de control es una instantánea del estado junto con algunos registros contables, grabados en un momento específico. Si desea conocer cómo se almacenan internamente estas partes, incluidas las escrituras pendientes y los bloques de datos, existe una explicación más detallada en Inside LangGraph's InMemorySaver.

    Un contador, paso a paso

    Considere un grafo con un nodo que incrementa un número, ejecutado tres veces seguidas. Después de la primera ejecución, el estado guardado contiene {count: 1}; después de la segunda, {count: 2}; y después de la tercera, {count: 3}, cada uno como su propio punto de control. Si el programa se cae inmediatamente después de escribirse el segundo punto de control, se puede reiniciar desde {count: 2} sin tener que repetir los dos primeros incrementos.

    Checkpointers: el componente que guarda y carga

    Si un punto de control es la instantánea, un checkpointer es el componente que toma esas instantáneas, las almacena y las recupera. Es la capa de persistencia de LangGraph.

    Una analogía adecuada es una cámara con un archivador integrado. Cada vez que se completa un nodo, la cámara toma una foto del estado y lo guarda. El archivador puede ser la memoria de proceso, un archivo local o una base de datos, dependiendo del checkpointer que se elija.

    Tres funciones de un checkpointer

    Un checkpointer es responsable de:

    1. Guardar el estado: escribir la nueva captura de pantalla en el almacenamiento después de cada paso.
    2. Cargar el estado: leer la captura de pantalla más reciente cuando se vuelve a invocar el grafo para el mismo hilo (los hilos se tratan en la siguiente sección).
    3. Restaurar la ejecución: entregar esa captura de pantalla al sistema en tiempo de ejecución para que el grafo continúe desde donde se detuvo en lugar de comenzar de nuevo.

    Asociar un checkpointer en tiempo de compilación

    La persistencia se activa al compilar el grafo. Se importa una clase checkpointer y el constructor del grafo; las etiquetas de origen indican que este fragmento es en JavaScript, pero en realidad es en Python:

    from langgraph.checkpoint.memory import InMemorySaver
    from langgraph.graph import StateGraph
    

    Luego se crea el checkpointer y se pasa a compile(). Cabe señalar que en el fragmento mostrado, el comentario y la asignación checkpointer = InMemorySaver() están combinados en una sola línea, lo que haría que la asignación formara parte del comentario; en el código real deben estar en líneas separadas:

    # ... assume `builder` is a StateGraph you've already defined ...checkpointer = InMemorySaver()
    graph = builder.compile(checkpointer=checkpointer)
    

    Ese argumento checkpointer=checkpointer habilita la persistencia para todo el grafo. Sin él, LangGraph no almacena nada, y cada invoke() comienza con un estado vacío.

    El ciclo resultante es cargar, ejecutar, guardar, y se repite después de cada paso. Por eso la persistencia parece automática: nunca se llama a las funciones guardar o cargar manualmente, ya que el grafo compilado lo hace como parte de la ejecución normal.

    Hay una advertencia fácil de pasar por alto. Un checkpointer solo mantiene persistentes los grafos a los que se le ha pasado. Si el mismo archivo compila un segundo grafo sin un checkpointer, ese segundo grafo no tiene ninguna persistencia.

    Hilos: mantener separadas las conversaciones

    El valor thread_id aparece en todo el código de LangGraph, y merece una explicación precisa. Un hilo representa una conversación o tarea continua. Cada hilo tiene un ID único, y cada punto de control generado para esa conversación se agrupa bajo él.

    Por qué cada conversación necesita su propio hilo

    Imagínese un chatbot de soporte que atiende a miles de clientes al mismo tiempo. Un cliente pregunta por un reembolso mientras otro consulta sobre una entrega tardía. Se trata de conversaciones separadas que transcurren en paralelo, y mezclarlas, por ejemplo al informarle al primer cliente sobre el paquete del segundo cliente, sería un fallo grave.

    Los IDs de hilo evitan eso al funcionar como etiquetas en carpetas. Cada punto de control se archiva bajo un único ID de hilo, de modo que el estado de las diferentes conversaciones nunca se mezcla.

    Transmitir un ID de hilo en el código

    El hilo se selecciona a través de un diccionario de configuración. El thread_id se encuentra bajo la clave configurable (esto y el siguiente fragmento son en Python, no texto plano):

    config = {"configurable": {"thread_id": "customer-a-session-101"}}
    

    Esa configuración se transmite junto con la entrada en cada llamada:

    result = graph.invoke({"messages": [{"role": "user", "content": "Where's my refund?"}]}, config)
    

    Cada llamada a graph.invoke() o graph.stream() recibe un parámetro config que contiene el thread_id. LangGraph lo utiliza para:

    • buscar puntos de control existentes para ese hilo, si hay historial que cargar;
    • guardar nuevos puntos de control con el mismo ID a medida que continúa la ejecución.

    Si se utiliza un thread_id diferente, se obtiene una conversación nueva y vacía, como si el estado hubiera sido reiniciado, aunque el grafo compilado y los puntos de control sean exactamente los mismos objetos.

    Cómo se relacionan los hilos con los productos reales

    • Una interfaz de chat: cada chat que se abre es, en efecto, su propio hilo, y cambiar de chat equivale a cambiar de ID de hilo. Lo que se dice en una conversación no se transfiere a otra.
  • Un servicio de soporte: cada ticket puede asociarse a un ID de hilo, lo que mantiene el historial de ese cliente aislado y fácil de recuperar posteriormente.
  • Un asistente personal: un asistente que gestiona el calendario y la lista de tareas de una persona podría utilizar un único hilo de larga duración vinculado a la cuenta de usuario, de modo que las preferencias se mantengan a lo largo de varios días.
  • Una consecuencia práctica es que los IDs de hilo deben generarse y almacenarse de forma intencionada, por ejemplo, a partir del número de ticket o de un ID de sesión en su propia base de datos. Si se pierde el ID, los puntos de control siguen existiendo, pero no hay nada que los indique.

    Un gráfico compilado, muchos hilos

    No es necesario tener un gráfico por usuario. Un gráfico compilado puede servir a cualquier cantidad de hilos simultáneamente; lo que se necesita es un ID de hilo único por usuario o por conversación. El gráfico define el comportamiento, y el hilo define en cuyo estado opera ese comportamiento.

    Siguiendo una ejecución desde el inicio hasta la caída y su reanudación

    Al combinar el estado, los puntos de control, el checkpointer y los hilos se obtiene una imagen completa de lo que ocurre durante la ejecución. El diagrama de flujo a continuación (sintaxis Mermaid, mostrado como texto) sigue una ejecución, incluyendo una caída y la ruta de retorno posterior:

    flowchart TD
        A[1. Graph starts with invoke] --> B[2. Checkpointer checks thread_id for existing State]
        B --> C{State exists for this thread?}
        C -->|Yes| D[3a. Load last saved State]
        C -->|No| E[3b. Start with fresh empty State]
        D --> F[4. Node executes]
        E --> F
        F --> G[5. State updates in memory]
        G --> H[6. Checkpoint saved to storage]
        H --> I{More nodes to run?}
        I -->|Yes| F
        I -->|No| J[7. Return final result to caller]
        H -.->|💥 Crash happens here| K[Process restarts]
        K --> B
    

    En prosa, la secuencia es:

    1. Se inicia el gráfico. Se llama a graph.invoke(input, config) con un determinado thread_id.
  • Se crea o carga el estado. El checkpointer verifica si ese hilo ya cuenta con puntos de control. Si los tiene, el más reciente se convierte en el estado inicial; de lo contrario, la ejecución comienza desde un estado vacío.
  • Un nodo se ejecuta utilizando el estado que le fue asignado.
  • Se actualiza el estado. Todo lo que devuelve el nodo se integra en el estado actual.
  • Se escribe un punto de control. El checkpointer almacena el nuevo estado bajo la ID del hilo.
  • Se ejecuta el siguiente nodo, y se repiten los pasos del 3 al 5.
  • Ocurre una falla, digamos justo después de que se escribió el punto de control del nodo 2 pero antes de que comenzara a ejecutarse el nodo 3.
  • Se reanuda la ejecución. Al volver a invocar el grafo en el mismo hilo, se carga el último punto de control escrito con éxito, el que está después del nodo 2, y la ejecución continúa con el nodo 3, no con el nodo 1.
  • No existe un modo de recuperación separado que implementar. Basta con volver a llamar al grafo en el mismo hilo para que LangGraph determine dónde continuar.

    Hay un detalle sobre el que vale la pena ser preciso. Para continuar una ejecución que fue interrumpida o falló, se invoca el grafo con None como entrada y la misma configuración, lo que indica a LangGraph que continúe desde el punto de control guardado en lugar de iniciar una nueva ejecución. Al pasar una entrada nueva en un hilo existente, se inicia una nueva ejecución que se basa en el estado guardado: las claves con un reductor, como una lista de mensajes, se acumulan, mientras que las claves simples son sobrescritas por el nuevo valor. Puedes inspeccionar lo que se guardó con graph.get_state(config) para obtener la última captura y con graph.get_state_history(config) para ver toda la secuencia.

    Este mismo mecanismo es el que permite a un agente detenerse intencionadamente, por ejemplo para esperar una decisión humana, y reanudar su funcionamiento horas o días después en otra máquina, siempre y cuando esa máquina pueda acceder al mismo almacenamiento persistente. Para ver un ejemplo práctico de este patrón, consulte Pausar y reanudar agentes LangGraph con interrupto y comando.

    El backend más sencillo: InMemorySaver

    LangGraph no lo obliga a utilizar un único sistema de almacenamiento. Soporta varios backends, es decir, lugares donde se guardan físicamente los puntos de control, y difieren principalmente en su durabilidad y en la cantidad de procesos que pueden compartirlos. El más sencillo es InMemorySaver.

    Qué es y cómo almacena los puntos de control

    InMemorySaver es un punto de control que almacena cada checkpoint en la RAM del proceso Python actual, dentro de un diccionario ordinario en memoria indexado por el ID del hilo. No existe archivo ni base de datos, solo un objeto Python.

    El primer ejemplo utiliza un estado tipado con una única clave count y un nodo que le suma uno. El nodo está conectado desde START hasta END; el grafo se compila con InMemorySaver y se ejecuta en thread-1 con un conteo inicial de 1, lo que produce 2. La fuente lo indica como JavaScript, pero en realidad es Python; también hay que tener en cuenta que asume que TypedDict, StateGraph, START, END y InMemorySaver ya fueron importados anteriormente:

    class StateInt(TypedDict):
        count: int
    
    def add_one(state: StateInt) -> dict:
        return {"count": state["count"] + 1}
    
    builder = StateGraph(StateInt)
    builder.add_node("add_one", add_one)
    builder.add_edge(START, "add_one")
    builder.add_edge("add_one", END)
    
    memory = InMemorySaver()
    graph = builder.compile(checkpointer=memory)
    
    config = {"configurable": {"thread_id": "thread-1"}}
    result = graph.invoke({"count": 1}, config)
    print(result)  # {'count': 2}
    

    El siguiente fragmento es simplemente una leyenda que contrasta la versión mínima del grafo con la más realista mostrada arriba:

    Two ways to write the same graph — minimal vs. real-world.
    

    La variante mínima necesita asyncio además del checkpointer y del generador de grafo:

    import asyncio
    from langgraph.checkpoint.memory import InMemorySaver
    from langgraph.graph import StateGraph
    

    Luego utiliza un int simple como estado completo, registra una lambda como nodo, marca ese nodo tanto como punto de entrada como de finalización, y ejecuta el grafo de forma asíncrona con ainvoke. Como se muestra, varias instrucciones se han ejecutado juntas en líneas separadas (por ejemplo, la llamada a set_finish_point y la asignación en InMemorySaver()), por lo que hay que separarlas antes de ejecutarlo; el fragmento también es en Python a pesar de su etiqueta:

    builder = StateGraph(int)
    builder.add_node("add_one", lambda x: x + 1)
    builder.set_entry_point("add_one")
    builder.set_finish_point("add_one")memory = InMemorySaver()
    graph = builder.compile(checkpointer=memory)config = {"configurable": {"thread_id": "thread-1"}}
    result = asyncio.run(graph.ainvoke(1, config))
    print(result)  # Output: 2
    

    Analizando las líneas importantes:

    • InMemorySaver() crea un almacén de puntos de control vacío en memoria.
  • builder.compile(checkpointer=memory) adjunta ese almacenamiento al grafo, lo que activa la persistencia.
  • config = {"configurable": {"thread_id": "thread-1"}} vincula la llamada a una conversación específica.
  • graph.ainvoke(1, config) es el punto de entrada asíncrono; aquí se inicia con el número entero 1 como estado en el hilo "thread-1".
  • Al volver a llamar con el mismo thread_id, se trabaja con los puntos de control ya guardados para ese hilo en lugar de con un historial vacío. Tenga en cuenta la diferencia con la sección anterior: en este pequeño grafo la ejecución ya ha finalizado y el estado es un único valor sobrescrito, por lo que una nueva entrada simplemente lo reemplaza, mientras que None como entrada continuaría una ejecución inconclusa.

    Fortalezas

    • Sin configuración: no se necesita base de datos ni servicio externo.
    • Muy rápido, ya que no hay latencia en el disco ni en la red.
    • Perfecto para pruebas unitarias.
    • Útil para el aprendizaje y para experimentos en cuadernos de notas.

    Límites

    • Todo desaparece cuando se detiene el proceso. Se trata de RAM pura, lo cual es exactamente el problema descrito al inicio de esta guía.
    • No puede compartirse entre procesos o servidores, ya que cada proceso tiene su propia memoria.
    • No es seguro para entornos de producción, ya que un simple reinicio borra todas las conversaciones.

    Cuándo utilizarlo

    Los usos adecuados son:

    • desarrollo local y depuración;
    • pruebas automatizadas, tanto unitarias como de pipelines CI;
    • prototipos rápidos y cuadernos de notas donde no importa que sobrevivan a un reinicio.

    Cualquier cosa de la que dependan los usuarios reales necesita un mecanismo de verificación respaldado por una base de datos. La documentación de LangGraph indica claramente que InMemorySaver está destinado a fines de depuración y pruebas, y recomienda una implementación duradera como PostgresSaver para entornos de producción. Configurarlo, junto con otros backends como SQLite y Redis, así como verificadores personalizados y flujos de aprobación basados en interrupt() y Command, es el siguiente paso lógico; un ejemplo orientado a la producción de ejecución de LangGraph en Postgres y Redis se aborda en Autohospedaje de un servidor de agentes LangGraph.

    Puntos clave

    • La persistencia escribe el estado del grafo en un almacenamiento duradero para que la ejecución sobreviva a caídas, reinicios y largas esperas en lugar de tener que comenzar desde cero.
  • Reiniciar no solo es lento: repite las llamadas pagadas al LLM y puede reproducir efectos secundarios como correos electrónicos o pagos.
  • El estado es los datos compartidos que fluyen a través del grafo, y es precisamente lo que se guarda.
  • Un punto de control es una instantánea de ese estado después de un super-paso; un checkpointer crea, almacena y vuelve a cargar automáticamente los puntos de control una vez se pasa a compile().
  • Un thread_id aísla una conversación o tarea, de modo que un mismo grafo compilado puede servir a muchos usuarios de forma segura.
  • Reanudar una ejecución interrumpida implica invocar el mismo hilo con None; una nueva entrada en un hilo existente inicia una nueva ejecución sobre el estado guardado.
  • InMemorySaver es ideal para pruebas y prototipos, pero como reside en la memoria del proceso, los sistemas de producción necesitan un checkpointer respaldado por una base de datos.
  • Lecturas relacionadas