Persistencia y Puntos de Control
Aprende a persistir el estado del grafo con MemorySaver, guardar y cargar puntos de control, y usar gestión de estado basada en hilos.
Una de las funciones más potentes de LangGraph es la persistencia: la capacidad de guardar y restaurar el estado del grafo en cualquier momento de la ejecución. Esto permite agentes de larga duración, flujos de trabajo con humano en el bucle y sistemas tolerantes a fallos.
Por qué Importa la Persistencia
Sin persistencia, cada invocación de grafo empieza desde cero. Con persistencia:
- El estado sobrevive a las invocaciones: Reanuda donde lo dejaste
- Humano en el bucle: Pausa la ejecución, inspecciona el estado, aporta entrada y reanuda
- Tolerancia a fallos: Recupérate de los fallos sin perder el progreso
- Conversaciones con varios turnos: Mantén el contexto entre múltiples interacciones
MemorySaver: Persistencia en Memoria
MemorySaver guarda checkpoints en memoria. Es simple y rápido, pero el estado se pierde cuando el proceso termina.
from langgraph.checkpoint.memory import MemorySaver
from langgraph.graph import StateGraph, START, END
from typing_extensions import TypedDict
class State(TypedDict):
messages: list
turn_count: int
def node_a(state: State) -> dict:
return {
"messages": state["messages"] + ["Processed by A"],
"turn_count": state["turn_count"] + 1
}
# Add persistence
checkpointer = MemorySaver()
builder = StateGraph(State)
builder.add_node("a", node_a)
builder.add_edge(START, "a")
builder.add_edge("a", END)
# Compile with checkpointer
app = builder.compile(checkpointer=checkpointer)El parámetro checkpointer se pasa a .compile(). Una vez definido, cada ejecución de nodo crea un checkpoint que puede recuperarse después.
IDs de Hilo
Los checkpoints se organizan por thread ID. Un hilo representa una única conversa o sesión de flujo de trabajo:
# First invocation — creates a new thread
config1 = {"configurable": {"thread_id": "thread-1"}}
result1 = app.invoke(
{"messages": [], "turn_count": 0},
config1
)
# Second invocation on the same thread — continues from where we left off
config2 = {"configurable": {"thread_id": "thread-1"}}
result2 = app.invoke(
{"messages": ["New message"], "turn_count": 0}, # Initial state is merged
config2
)
# Different thread — starts fresh
config3 = {"configurable": {"thread_id": "thread-2"}}
result3 = app.invoke(
{"messages": [], "turn_count": 0},
config3
)Al usar un checkpointer, el estado inicial que pasas a invoke() se fusiona con el estado existente del hilo. Las claves que ya existen en el hilo conservan sus valores; las claves nuevas se añaden.
Recuperando Puntos de Control
# Get the latest state for a thread
latest = app.get_state(config1)
print(latest.values) # Current state dict
print(latest.next) # Nodes to execute next (empty if finished)
# List all checkpoints for a thread
for checkpoint in app.get_state_history(config1):
print(f"Checkpoint: {checkpoint.config}")
print(f" State: {checkpoint.values}")
print(f" Next: {checkpoint.next}")
print(f" Time: {checkpoint.created_at}")Actualizando Estado Durante la Ejecución
Puedes actualizar manualmente el estado de un hilo:
config = {"configurable": {"thread_id": "thread-1"}}
# Inject new state values
app.update_state(
config,
{"messages": ["Injected message"], "turn_count": 99}
)
# Now when you invoke, it continues from the updated state
result = app.invoke({"messages": ["New message"]}, config)update_state() te permite intervenir en el estado de un agente; es útil para comentarios humanos, corrección de errores o pruebas.
Reanudando desde un Punto de Control Específico
Puedes reanudar la ejecución desde un checkpoint específico:
# Get a specific checkpoint from history
history = list(app.get_state_history(config1))
if len(history) > 1:
previous_checkpoint = history[1] # Second-to-last checkpoint
# Resume from that checkpoint
result = app.invoke(
None, # No new input — use the checkpoint's state
previous_checkpoint.config
)Checkpointers SQLite y PostgreSQL
Para producción, usa checkpointers basados en base de datos que persistan entre reinicios:
SQLite (desarrollo local)
from langgraph.checkpoint.sqlite import SqliteSaver
# File-based SQLite
checkpointer = SqliteSaver.from_conn_string("checkpoints.db")
# Or in-memory SQLite
checkpointer = SqliteSaver.from_conn_string(":memory:")PostgreSQL (producción)
from langgraph.checkpoint.postgres import PostgresSaver
# Requires a PostgreSQL connection string
checkpointer = PostgresSaver.from_conn_string(
"postgresql://user:pass@localhost:5432/langgraph"
)
# Initialize tables (run once)
checkpointer.setup()Llama siempre a checkpointer.setup() en los checkpointers SQLite/PostgreSQL para asegurar que las tablas de la base de datos existan. Es una inicialización única.
Contenido del Punto de Control
Cada checkpoint guarda:
checkpoint = app.get_state(config)
# The full state dict
state_values = checkpoint.values
# Which nodes are pending execution
next_nodes = checkpoint.next
# Checkpoint metadata
metadata = checkpoint.config
# Includes: thread_id, checkpoint_id, checkpoint_ns
# Parent checkpoint (for branching)
parent = checkpoint.parent_configRamificación desde Puntos de Control
Crea rutas de ejecución alternativas desde cualquier checkpoint:
# Get the state at a specific checkpoint
checkpoint = list(app.get_state_history(config))[2]
# Create a new thread that continues from this checkpoint
branch_config = {"configurable": {"thread_id": "branch-thread"}}
# Copy the checkpoint's state to the new thread
app.update_state(branch_config, checkpoint.values)
# Now branch-thread continues from where the checkpoint was made
result = app.invoke({"messages": ["Alternative path"]}, branch_config)Configurando Frecuencia de Puntos de Control
De forma predeterminada, se guarda un checkpoint después de cada ejecución de nodo. Puedes controlar esto:
from langgraph.checkpoint.memory import MemorySaver
# Configure checkpoint interval
checkpointer = MemorySaver()
# Option 1: Always checkpoint (default)
app = builder.compile(checkpointer=checkpointer)
# Option 2: Pass checkpoint config at invocation
config = {
"configurable": {
"thread_id": "my-thread",
"checkpoint_ns": "custom"
}
}Para grafos de alto rendimiento, hacer checkpoint después de cada nodo puede ser costoso. Considera agrupar o reducir la frecuencia de checkpointing en producción.
Ejemplo Completo: Chatbot Persistente
from langgraph.checkpoint.memory import MemorySaver
from langgraph.graph import StateGraph, START, END
from langchain_openai import ChatOpenAI
from typing_extensions import TypedDict
from typing import Annotated, List, Any
from operator import add
llm = ChatOpenAI(model="gpt-4o-mini")
class ChatState(TypedDict):
messages: Annotated[List[Any], add]
def chat_node(state: ChatState) -> dict:
response = llm.invoke(state["messages"])
return {"messages": [response]}
checkpointer = MemorySaver()
builder = StateGraph(ChatState)
builder.add_node("chat", chat_node)
builder.add_edge(START, "chat")
builder.add_edge("chat", END)
app = builder.compile(checkpointer=checkpointer)
# Multi-turn conversation
session = {"configurable": {"thread_id": "alice-session"}}
app.invoke({"messages": [("human", "Hi!")]}, session)
app.invoke({"messages": [("human", "What's my name?")]}, session)
# The bot remembers the context because state persists
# Different session — starts fresh
session2 = {"configurable": {"thread_id": "bob-session"}}
app.invoke({"messages": [("human", "What's my name?")]}, session2)
# Bob's session doesn't know about Alice's conversationPreguntas de Práctica (Practice Questions)
Cómo responder
Pregunta de opción múltiple con 4 opciones. Selecciona una opción y elige Comprobar respuesta.
What parameter is passed to compile() to enable persistence?
Cómo responder
Pregunta de opción múltiple con 4 opciones. Selecciona una opción y elige Comprobar respuesta.
What organizes checkpoints in LangGraph?
Cómo responder
Pregunta de opción múltiple con 4 opciones. Selecciona una opción y elige Comprobar respuesta.
Which checkpointer is suitable for production deployment?
Cómo responder
Pregunta de opción múltiple con 4 opciones. Selecciona una opción y elige Comprobar respuesta.
How often does LangGraph save checkpoints by default?
Cómo responder
Pregunta de opción múltiple con 4 opciones. Selecciona una opción y elige Comprobar respuesta.
What does app.get_state(config) return?
Cómo responder
Pregunta de opción múltiple con 4 opciones. Selecciona una opción y elige Comprobar respuesta.
How do you manually inject state into an existing thread?
Cómo responder
Pregunta de opción múltiple con 4 opciones. Selecciona una opción y elige Comprobar respuesta.
When resuming a thread with invoke(), how is the new input combined with existing state?
Cómo responder
Pregunta de opción múltiple con 4 opciones. Selecciona una opción y elige Comprobar respuesta.
What method lists all checkpoints for a thread?
Cómo responder
Pregunta de opción múltiple con 4 opciones. Selecciona una opción y elige Comprobar respuesta.
What is the purpose of checkpointing in LangGraph?
Cómo responder
Pregunta de opción múltiple con 4 opciones. Selecciona una opción y elige Comprobar respuesta.
How do you initialize database tables for SqliteSaver or PostgresSaver?
Conclusiones Clave
- MemorySaver ofrece persistencia en memoria; SqliteSaver y PostgresSaver para producción
- Los IDs de hilo organizan los checkpoints en sesiones
- Los checkpoints se guardan después de cada ejecución de nodo de forma predeterminada
- get_state() recupera el estado actual; get_state_history() enumera todos los checkpoints
- update_state() inyecta estado manualmente en un hilo
- Los hilos distintos tienen estados aislados: no comparten contexto
- Los checkpointers basados en base de datos requieren checkpointer.setup() antes del primer uso
- El checkpointing permite conversaciones con varios turnos, humano en el bucle y tolerancia a fallos