Notas prácticas: Memoria de LangGraph: La guía práctica completa para gestionarla
Guía paso a paso práctica: Memoria LangGraph: La guía práctica completa para gestionar contratos, verificaciones y espacios de código reutilizable para los equipos que utilizan este patrón.
Fuentes: LangGraph Memory: The Complete Practical Guide to Managing What Your Agent Remembers
Las notas siguientes reconstruyen un camino práctico basado en “LangGraph Memory: La guía práctica completa para gestionar lo que recuerda tu agente”. Se da énfasis en los contratos, las verificaciones y los marcadores de posición para código, en lugar de en un enfoque motivacional. Al trabajar en la sección de Resumen, anote primero el contrato: las entradas requeridas, la señal de éxito y qué ocurre en caso de un fallo parcial. Esa lista de verificación ayuda a mantener honestas las futuras modificaciones del código. Prefiera unidades pequeñas y verificables en lugar de scripts extensos. Cuando un paso falla, el fallo debe apuntar a una sola responsabilidad y no a un proceso complicado.
El problema de la memoria del que nadie habla en las tutoriales
El problema de memoria del que nadie habla en las tutoriales funciona mejor cuando se trata como una superficie medible. Capture una transcripción ejemplar, un caso de fallo y la nota de reversión antes de ampliar el alcance. Considere esta etapa como un contrato entre las entradas y las salidas validadas. Asigne nombres a los artefactos, defina verificaciones de éxito y rechace las completaciones parciales silenciosas. Mantenga el estado del grafo plano y tipado. Los bloques anidados ocultan qué nodo escribió qué campo y provocan interrupciones en la continuación posterior.
El paisaje de la memoria: cuatro cosas que debe entender primero
“El paisaje de la memoria: Cuatro cosas que hay que entender primero” funciona mejor cuando se trata como una superficie medible. Capture un registro exitoso, un caso de fallo y la nota de reversión antes de ampliar el alcance. Registre los tiempos y el costo de tokens o consultas junto con los resultados funcionales. Tener visibilidad del costo desde el principio evita facturas inesperadas cuando el proceso pasa de la versión de demostración a entornos compartidos. Mantenga el estado del gráfico en formato plano y con tipos definidos. Los bloques anidados ocultan qué nodo escribió qué campo y provocan interrupciones en la continuación del proceso.
User sends message
↓
Graph loads State from Checkpointer (using thread_id)
↓
Memory Management runs (trim / filter / summarize)
↓
LLM receives cleaned messages
↓
LLM response added to State
↓
Checkpointer saves updated State (to memory / SQLite / Postgres)
Módulo 2 ampliado: El estado consciente de la memoria
Modulo 2 Ampliado: El Estado Consciente de la Memoria funciona mejor cuando se trata como una superficie medible. Capture un registro ideal, un caso de fallo y la nota de reversión antes de ampliar el alcance. Mantenga la configuración fuera del código de la aplicación. Los archivos de entorno, los almacenes de secretos y las banderas de funcionalidad deben estar en un lugar donde los operadores puedan auditarlos sin tener que leer todo el grafo. Mantenga el estado del grafo plano y tipado. Los bloques anidados ocultan qué nodo escribió qué campo y causan interrupciones en la continuación del proceso. Modulo 2 Ampliado: El Estado Consciente de la Memoria funciona mejor cuando se trata como una superficie medible. Capture un registro ideal, un caso de fallo y la nota de reversión antes de ampliar el alcance. Prefiera unidades pequeñas y probables sobre scripts extensos. Cuando falla un paso, el fallo debe apuntar a una única responsabilidad en lugar de a un proceso complicado.
class AgentState(TypedDict):
messages: Annotated[list[BaseMessage], add_messages]
El Estado Estándar Consciente de la Memoria
Para el Estado Consciente de la Memoria Estándar, defina las entradas, el responsable del paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido sin tener que adivinar el estado oculto. Trate esta etapa como un contrato entre las entradas y los resultados validados. Asigne nombres a los artefactos, defina verificaciones de éxito y rechace las completaciones parciales silenciosas. Incluya la aprobación humana en aquellos casos que impliquen gastos o cambios en los datos de producción. La conexión en tiempo de compilación no equivale a la completitud del proceso empresarial.
# ── MODULE 2: STATE (Memory-Aware Version) ──────────────────
from langgraph.graph import MessagesState
# Option A: The shorthand (recommended for most agents)
# MessagesState is a pre-built TypedDict with just the messages field.
# Use it when you don't need custom fields.
class State(MessagesState):
summary: str # The only addition needed for memory management
Las Tres Estrategias para Gestionar la Longitud de las Conversaciones
En las Tres Estrategias para Gestionar la Longitud de las Conversaciones, se deben definir los insumos, el responsable de cada paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido, sin tener que adivinar el estado oculto. Se deben registrar los tiempos de ejecución y el costo en tokens o consultas junto con los resultados funcionales. La visibilidad temprana de los costos evita facturas inesperadas cuando el proceso pasa de entornos de demostración a entornos compartidos. Se debe incluir la aprobación humana en aquellos casos en que se gastan fondos o se modifican datos de producción. La conexión durante la compilación no equivale a una solución completa desde el punto de vista empresarial.
Estrategia 1: Filtrado de mensajes
Para la Estrategia 1: Filtrado de mensajes, defina las entradas, el responsable del paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido sin tener que adivinar el estado oculto. Mantenga la configuración fuera del código de la aplicación. Los archivos de entorno, los almacenes de datos secretos y las banderas de funcionalidad deben estar en un lugar donde los operadores puedan auditarlos sin necesidad de leer todo el sistema. Coloque la aprobación humana en aquellos pasos que implican gastos o modifican datos de producción. La conexión realizada en tiempo de compilación no equivale a una solución completa desde el punto de vista empresarial. Para la Estrategia 1: Filtrado de mensajes, defina las entradas, el responsable del paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido sin tener que adivinar el estado oculto. Prefiera unidades pequeñas y verificables en lugar de scripts extensos. Cuando un paso falla, el error debe indicar una única responsabilidad y no un proceso complicado.
Patrón A: Filtrar solo lo que ve el LLM (estado sin cambios)
Al trabajar con el Patrón A: Filtrar solo lo que ve el LLM (estado sin cambios), anote primero el contrato: entradas requeridas, señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Trate esta etapa como un contrato entre las entradas y las salidas validadas. Asigne nombres a los artefactos, defina comprobaciones de éxito y rechace las completaciones parciales silenciosas. Almacene en caché las instrucciones del sistema estables y los esquemas de las herramientas. Reenviar un preámbulo idéntico es una causa común de agotamiento.
# ── MODULE 4: NODES ──────────────────────────────────────────
def chat_model_node(state: State) -> dict:
"""Calls the LLM with only the last 2 messages, but state keeps the full history."""
# Filter: only send last 2 messages to the LLM
# State is NOT modified - full history stays in the checkpoint
recent_messages = state["messages"][-2:]
response = llm.invoke(recent_messages)
return {"messages": [response]}
Patrón B: RemoveMessage — Eliminar permanentemente del estado
Al trabajar con el Patrón B: RemoveMessage — Eliminar permanentemente del estado, anote primero el contrato: entradas requeridas, señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Registre los tiempos y el costo de tokens o consultas junto con los resultados funcionales. La visibilidad temprana del costo evita facturas inesperadas cuando el proceso pasa de la versión de demostración a entornos compartidos. Haga una verificación después de los pasos costosos. La función de reanudación no debe volver a facturar la misma llamada al LLM cuando un operador intenta nuevamente un nodo posterior.
# ── IMPORTS needed ──────────────────────────────────────────
from langchain_core.messages import RemoveMessage
# ── MODULE 4: NODES ──────────────────────────────────────────
def filter_messages_node(state: State) -> dict:
"""A dedicated node that removes all but the last 2 messages from state.
RemoveMessage works by ID: you pass it the ID of a message you want deleted,
and the add_messages reducer handles the actual deletion. This is NOT the same
as just slicing the list - it permanently removes from the checkpoint.
"""
# Build a list of RemoveMessage objects for every message we want to delete
# state["messages"][:-2] = all messages EXCEPT the last 2
delete_messages = [RemoveMessage(id=m.id) for m in state["messages"][:-2]]
return {"messages": delete_messages} # Reducer sees these and deletes them
def chat_model_node(state: State) -> dict:
"""After filtering, the state already has a short message list."""
response = llm.invoke(state["messages"])
return {"messages": [response]}
# ── MODULE 6: GRAPH ASSEMBLY ─────────────────────────────────
graph_builder = StateGraph(State)
graph_builder.add_node("filter", filter_messages_node)
graph_builder.add_node("chat_model", chat_model_node)
# Filter runs FIRST, then chat_model sees the cleaned state
graph_builder.add_edge(START, "filter")
graph_builder.add_edge("filter", "chat_model")
graph_builder.add_edge("chat_model", END)
graph = graph_builder.compile(checkpointer=MemorySaver())
Estrategia 2: Recorte basado en tokens con trim_messages
Al trabajar con la Estrategia 2: Recorte basado en tokens mediante trim_messages, anote primero el contrato: entradas requeridas, señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código.
Guarde la configuración fuera del código de la aplicación. Los archivos de entorno, los almacenes de secretos y las banderas de funcionalidad deben estar en un lugar donde los operadores puedan auditarlos sin tener que leer todo el sistema.
Almacene en caché las instrucciones del sistema estables y los esquemas de las herramientas. Reenviar un preámbulo idéntico es una causa común de desperdicio.
Al trabajar con la Estrategia 2: Recorte basado en tokens mediante trim_messages, anote primero el contrato: entradas requeridas, señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código.
Prefiera unidades pequeñas y probables sobre scripts extensos. Cuando un paso falla, el fallo debe apuntar a una única responsabilidad en lugar de a algo complicado.
# ── IMPORTS needed ──────────────────────────────────────────
from langchain_core.messages import trim_messages
# ── MODULE 4: NODES ──────────────────────────────────────────
def chat_model_node(state: State) -> dict:
"""Uses token-based trimming to control exactly how much history the LLM sees."""
trimmed = trim_messages(
state["messages"],
# Maximum number of tokens to allow (tune this to your model and budget)
max_tokens=1000,
# "last" = keep the MOST RECENT messages (the ones that fit within max_tokens)
# This is almost always what you want
strategy="last",
# The LLM used to count tokens. Use the same model you're chatting with
# for accurate counting. This does NOT make an API call - just counts.
token_counter=ChatOpenAI(model="gpt-4o"),
# False = never cut a message in half. Either include it fully or drop it.
# Always set this to False in production to avoid feeding partial tool results.
allow_partial=False,
# True = always include the most recent HumanMessage even if it alone
# exceeds max_tokens. Prevents the agent from receiving an empty context.
include_system=True,
)
response = llm.invoke(trimmed)
return {"messages": [response]}
Referencia rápida de parámetros clave
La referencia rápida de parámetros clave funciona mejor cuando se trata como una superficie medible. Capture una transcripción ejemplar, un caso de fallo y la nota de reversión antes de ampliar el alcance. Considere esta etapa como un contrato entre las entradas y las salidas validadas. Asigne nombres a los artefactos, defina verificaciones de éxito y rechace completaciones parciales silenciosas. Mantenga el estado del grafo plano y tipado. Los bloques anidados ocultan qué nodo escribió qué campo y provocan interrupciones en la continuación posterior.
Estrategia 3: Resumen basado en LLM (El estándar de producción)
Estrategia 3: Resumen basado en LLM (el estándar de producción) funciona mejor cuando se trata como una superficie medible. Capture una transcripción ideal, un caso de fallo y la nota de reversión antes de ampliar el alcance. Registre los tiempos y el costo en tokens o consultas junto con los resultados funcionales. Tener visibilidad del costo desde el principio evita facturas inesperadas cuando se pasa de la versión de demostración a entornos compartidos. Asigne un presupuesto de tokens por turno y por sesión. Las herramientas agenciales amplían el contexto de forma intensiva; los límites estrictos impiden que las demostraciones se conviertan en facturas inesperadas.
Paso 1: Estado (Módulo 2)
Paso 1: State (Módulo 2) funciona mejor cuando se trata como una superficie medible. Capture una transcripción exitosa, un caso de fallo y la nota de reversión antes de ampliar el alcance. Mantenga la configuración fuera del código de la aplicación. Los archivos de entorno, los almacenes de secretos y las banderas de funcionalidad deben estar en un lugar donde los operadores puedan auditarlos sin tener que leer todo el grafo. Mantenga el estado del grafo plano y tipado. Los bloques anidados ocultan qué nodo escribió qué campo y provocan interrupciones en la continuación del proceso.
class State(MessagesState):
summary: str # Stores the running summary. Empty string by default.
Paso 2: Los dos nodos (Módulo 4)
Paso 2: Los dos nodos (Módulo 4) funcionan mejor cuando se tratan como una superficie medible. Capture una transcripción exitosa, un caso de fallo y la nota de reversión antes de ampliar el alcance. Documente tanto el camino óptimo como el camino de recuperación juntos. Las reintentos, los controles humanos y el manejo de mensajes no entregados forman parte del producto, no son mejoras posteriores. Mantenga el estado del grafo simple y tipado; los bloques anidados ocultan qué nodo escribió qué campo y provocan interrupciones en la continuación del proceso.
def call_model(state: State) -> dict:
"""The main agent node. Uses summary as context if it exists."""
summary = state.get("summary", "")
if summary:
# If we have a summary, prepend it as a system message
# This gives the LLM the compressed history without the raw messages
system_message = SystemMessage(
content=f"Summary of the conversation so far:\n\n{summary}"
)
messages = [system_message] + state["messages"]
else:
# No summary yet — just use the raw messages
messages = state["messages"]
response = llm.invoke(messages)
return {"messages": [response]}
def summarize_conversation(state: State) -> dict:
"""Triggered when conversation gets long. Compresses history into a summary,
then deletes the old raw messages to keep state lean."""
existing_summary = state.get("summary", "")
# Build the summarization prompt.
# If a summary already exists, extend it with new messages.
# This is the "rolling summary" pattern - it never reads all messages from scratch.
if existing_summary:
summary_message = (
f"This is the summary of our conversation so far:\n{existing_summary}\n\n"
f"Extend this summary with the new messages above. "
f"Keep it concise - focus on facts and decisions that matter for future turns."
)
else:
summary_message = (
"Create a concise summary of the conversation above. "
"Focus on key facts, user intent, and any decisions made. "
"This summary will replace the full message history."
)
# Call the LLM with all current messages + the summarization instruction
messages = state["messages"] + [HumanMessage(content=summary_message)]
response = llm.invoke(messages)
# After summarizing, delete all messages except the last 2.
# Why keep 2? So the LLM has immediate conversational context
# (it can see what was just said) even after summarization runs.
delete_messages = [RemoveMessage(id=m.id) for m in state["messages"][:-2]]
return {
"summary": response.content, # Updated summary stored in state
"messages": delete_messages # Old messages removed from state
}
Paso 3: La lógica de enrutamiento (Módulo 5)
Paso 3: La lógica de enrutamiento (Módulo 5) funciona mejor cuando se trata como una superficie medible. Capture una transcripción ejemplar, un caso de fallo y la nota de reversión antes de ampliar el alcance. Prefiera unidades pequeñas y verificables en lugar de scripts extensos. Cuando un paso falla, el fallo debe apuntar a una única responsabilidad y no a un proceso complicado. Mantenga el estado del grafo simple y tipado. Los bloques anidados ocultan qué nodo escribió qué campo y provocan interrupciones en la continuación del proceso.
from typing import Literal
def should_continue(state: State) -> Literal["summarize_conversation", "__end__"]:
"""After the main agent responds, decide: keep going or trigger summarization?
The threshold (6 messages here) is your tuning knob.
Too low = you summarize frequently, losing some nuance.
Too high = you let the context grow before compressing.
For most production agents, 6–10 messages is a good starting range.
"""
if len(state["messages"]) > 6:
return "summarize_conversation"
return "__end__"
Paso 4: Ensamblaje del grafo (Módulo 6)
Paso 4: El ensamblaje del grafo (Módulo 6) funciona mejor cuando se trata como una superficie medible. Capture una transcripción exitosa, un caso de fallo y la nota de reversión antes de ampliar el alcance. Considere esta etapa como un contrato entre las entradas y las salidas validadas. Asigne nombres a los artefactos, defina verificaciones de éxito y rechace completaciones parciales silenciosas. Mantenga el estado del grafo plano y tipado. Los bloques anidados ocultan qué nodo escribió qué campo y provocan interrupciones en la continuación posterior.
graph_builder = StateGraph(State)
graph_builder.add_node("call_model", call_model)
graph_builder.add_node("summarize_conversation", summarize_conversation)
graph_builder.add_edge(START, "call_model")
graph_builder.add_conditional_edges(
"call_model",
should_continue,
{
"summarize_conversation": "summarize_conversation",
"__end__": END
}
)
# After summarization, the conversation ends for this turn
graph_builder.add_edge("summarize_conversation", END)
memory = MemorySaver()
graph = graph_builder.compile(checkpointer=memory)
Cómo se aplica en la práctica
“Cómo se aplica en la práctica” funciona mejor cuando se trata como una superficie medible. Capture un registro exitoso, un caso de fallo y la nota de reversión antes de ampliar el alcance. Registre los tiempos y el costo de tokens o consultas junto con los resultados funcionales. Tener visibilidad del costo desde el principio evita facturas inesperadas cuando el proceso pasa de la versión de demostración a entornos compartidos. Mantenga el estado del gráfico simple y con tipos definidos. Los bloques anidados ocultan qué nodo escribió qué campo y provocan interrupciones en la continuación del proceso.
Turn 1–3: Messages accumulate. should_continue → "__end__" each time.
Turn 4: call_model runs. len(messages) > 6 → should_continue → "summarize_conversation"
summarize_conversation:
- LLM reads all 7 messages, generates a 2-sentence summary
- state["summary"] = "User is troubleshooting a Python import error..."
- Deletes messages[:-2] (keeps only last 2 raw messages)
Turn 5: call_model prepends summary as SystemMessage + 2 recent messages
LLM sees: [SystemMessage(summary), HumanMessage, AIMessage, HumanMessage(latest)]
Token cost: 4 messages instead of potentially 20+
Hacer que la memoria sea verdaderamente persistente: Puntos de control externos
Hacer que la memoria sea verdaderamente persistente: Los punteros de punto de control externos funcionan mejor cuando se tratan como una superficie medible. Capture una transcripción de referencia, un caso de fallo y la nota de reversión antes de ampliar el alcance. Guarde la configuración fuera del código de la aplicación. Los archivos de entorno, los almacenes de secretos y las banderas de funcionalidad deben estar en un lugar donde los operadores puedan auditarlos sin tener que leer todo el grafo. Mantenga el estado del grafo plano y tipado. Los bloques anidados ocultan qué nodo escribió qué campo y provocan interrupciones en la continuación del proceso. Hacer que la memoria sea verdaderamente persistente: Los punteros de punto de control externos funcionan mejor cuando se tratan como una superficie medible. Capture una transcripción de referencia, un caso de fallo y la nota de reversión antes de ampliar el alcance. Prefiera unidades pequeñas y probables a scripts extensos. Cuando un paso falla, el fallo debe apuntar a una única responsabilidad en lugar de a un proceso complicado.
La interfaz del puntero de punto de control
Para la interfaz Checkpointer, defina las entradas, el responsable de la etapa y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar la etapa a partir de un punto de control conocido sin tener que adivinar el estado oculto. Considere esta fase como un contrato entre las entradas y los resultados validados. Asigne nombres a los artefactos, defina verificaciones de éxito y rechace las completaciones parciales silenciosas. Incluya la aprobación humana en aquellos casos que impliquen gastos o cambios en los datos de producción. La conexión en tiempo de compilación no equivale a la completitud del proceso empresarial.
# Development: everything in RAM (lost on restart)
from langgraph.checkpoint.memory import MemorySaver
checkpointer = MemorySaver()
# Local / Single-machine production: persists to a .db file on disk
from langgraph_checkpoint_sqlite import SqliteSaver
import sqlite3
conn = sqlite3.connect("chat_memory.db", check_same_thread=False)
checkpointer = SqliteSaver(conn)
# Cloud / Distributed production: persists to a Postgres database
from langgraph_checkpoint_postgres import PostgresSaver
checkpointer = PostgresSaver.from_conn_string(
"postgresql://user:password@host:5432/dbname"
)
# This one line is the ONLY thing that changes between environments:
graph = graph_builder.compile(checkpointer=checkpointer)
SqliteSaver en profundidad
Para un análisis en profundidad de SqliteSaver, defina las entradas, el responsable de la etapa y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar la etapa a partir de un punto de control conocido sin tener que adivinar el estado oculto. Registre los tiempos de ejecución y el costo en tokens o consultas junto con los resultados funcionales. La visibilidad temprana del costo evita facturas inesperadas cuando el proceso pasa de entornos de demostración a entornos compartidos. Implemente la aprobación humana en aquellas acciones que generen gastos o modifiquen datos de producción. La configuración en tiempo de compilación no equivale a una solución completa desde el punto de vista empresarial.
# ── Full SqliteSaver Setup ───────────────────────────────────
import sqlite3
from langgraph_checkpoint_sqlite import SqliteSaver
# File-based: state persists across restarts
conn = sqlite3.connect(
"agent_memory.db",
check_same_thread=False # Required for LangGraph's multi-threaded execution
)
checkpointer = SqliteSaver(conn)
graph = graph_builder.compile(checkpointer=checkpointer)
# Usage is IDENTICAL to MemorySaver:
config = {"configurable": {"thread_id": "user-123"}}
response = graph.invoke({"messages": [HumanMessage(content="Hello")]}, config)
# Stop and restart your app. Run this again with the SAME thread_id:
# The agent picks up exactly where it left off.
response = graph.invoke({"messages": [HumanMessage(content="What did I just say?")]}, config)
# Agent: "You said 'Hello'."
Cuándo usar SqliteSaver frente a PostgresSaver
Para determinar cuándo usar SqliteSaver en lugar de PostgresSaver, defina las entradas, el responsable del paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido sin tener que adivinar el estado oculto. Guarde la configuración fuera del código de la aplicación. Los archivos de entorno, los almacenes de datos confidenciales y las banderas de funcionalidad deben encontrarse en un lugar que los operadores puedan auditar sin necesidad de leer todo el sistema. Coloque la aprobación humana en aquellos pasos que implican gastos o modifican datos de producción. La conexión en tiempo de compilación no equivale a la completitud del proceso empresarial. Para determinar cuándo usar SqliteSaver en lugar de PostgresSaver, defina las entradas, el responsable del paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido sin tener que adivinar el estado oculto. Prefiera unidades pequeñas y verificables en lugar de scripts extensos. Cuando un paso falla, el error debe indicar una única responsabilidad en lugar de...
un pipeline enredado.Inspeccionando la memoria: get_state y get_state_history
Al trabajar con Inspeccionando la memoria: get_state y get_state_history, anote primero el contrato: entradas requeridas, señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código.
Trate esta etapa como un contrato entre las entradas y las salidas validadas. Asigne nombres a los artefactos, defina comprobaciones de éxito y rechace las completaciones parciales silenciosas.
Haga un punto de control después de los pasos costosos. La reanudación no debe volver a facturar la misma llamada al LLM cuando un operador intenta nuevamente un nodo posterior.
# ── Inspecting Current State ─────────────────────────────────
config = {"configurable": {"thread_id": "user-123"}}
# Get the current state snapshot for this thread
state_snapshot = graph.get_state(config)
# What's in the snapshot?
print(state_snapshot.values["messages"]) # Current message list
print(state_snapshot.values.get("summary", "")) # Summary, if any
print(state_snapshot.next) # Which nodes would run next (empty list = graph is at END)
# ── Inspecting State History ─────────────────────────────────
# get_state_history gives you every checkpoint ever saved for this thread.
# Each checkpoint is a moment in time after a node ran.
for state in graph.get_state_history(config):
print(f"Step: {state.metadata['step']}")
print(f"Messages: {len(state.values['messages'])}")
print(f"Next: {state.next}")
print("---")
Poniéndolo todo junto: El agente completamente gestionado por memoria
Al trabajar en “Putting It All Together: The Complete Memory-Managed Agent”, anote primero el contrato: las entradas requeridas, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Registre los tiempos de ejecución y el costo de tokens o consultas junto con los resultados funcionales. Tener visibilidad del costo desde el principio evita facturas inesperadas cuando se pasa de entornos de demostración a entornos compartidos. Haga un punto de control después de los pasos costosos. La reanudación no debe volver a facturar la misma llamada al LLM cuando un operador vuelve a intentar un nodo posterior.
# ============================================================
# LANGGRAPH MEMORY-MANAGED AGENT — COMPLETE TEMPLATE
# Combines: Summarization + External Persistence (SqliteSaver)
# ============================================================
# ── MODULE 1: IMPORTS & CONFIGURATION ───────────────────────
import os
import sqlite3
from typing import Literal
from langchain_openai import ChatOpenAI
from langchain_core.messages import (
HumanMessage, AIMessage, SystemMessage,
BaseMessage, RemoveMessage
)
from langgraph.graph import StateGraph, START, END
from langgraph.graph import MessagesState
from langgraph.checkpoint.memory import MemorySaver
# For production, swap MemorySaver for one of these:
# from langgraph_checkpoint_sqlite import SqliteSaver
# from langgraph_checkpoint_postgres import PostgresSaver
llm = ChatOpenAI(model="gpt-4o", temperature=0)
# ── MODULE 2: STATE ─────────────────────────────────────────
class State(MessagesState):
# summary stores the rolling compressed history.
# It starts empty and gets written by summarize_conversation.
summary: str
# ── MODULE 4: NODES ─────────────────────────────────────────
def call_model(state: State) -> dict:
"""Main reasoning node. Uses rolling summary for long-context efficiency."""
summary = state.get("summary", "")
if summary:
system_message = SystemMessage(
content=f"Summary of the conversation so far:\n\n{summary}"
)
messages = [system_message] + state["messages"]
else:
messages = state["messages"]
response = llm.invoke(messages)
return {"messages": [response]}
def summarize_conversation(state: State) -> dict:
"""Compression node. Summarizes history and prunes old messages from state."""
existing_summary = state.get("summary", "")
if existing_summary:
summary_prompt = (
f"Current conversation summary:\n{existing_summary}\n\n"
f"Extend this summary with the new messages. "
f"Be concise. Preserve facts, user intent, and key decisions."
)
else:
summary_prompt = (
"Summarize this conversation concisely. "
"Focus on facts, user intent, and decisions made."
)
messages = state["messages"] + [HumanMessage(content=summary_prompt)]
response = llm.invoke(messages)
# Keep only the 2 most recent raw messages in state.
# Everything older is now captured in the summary.
delete_messages = [RemoveMessage(id=m.id) for m in state["messages"][:-2]]
return {
"summary": response.content,
"messages": delete_messages
}
# ── MODULE 5: ROUTING ────────────────────────────────────────
def should_continue(state: State) -> Literal["summarize_conversation", "__end__"]:
"""Trigger summarization when conversation exceeds 6 messages.
Tune this threshold based on your average message length and token budget.
For GPT-4o at ~$0.005/1K tokens, 6 messages is a practical starting point.
"""
if len(state["messages"]) > 6:
return "summarize_conversation"
return "__end__"
# ── MODULE 6: GRAPH ASSEMBLY ─────────────────────────────────
graph_builder = StateGraph(State)
graph_builder.add_node("call_model", call_model)
graph_builder.add_node("summarize_conversation", summarize_conversation)
graph_builder.add_edge(START, "call_model")
graph_builder.add_conditional_edges(
"call_model",
should_continue,
{
"summarize_conversation": "summarize_conversation",
"__end__": END
}
)
graph_builder.add_edge("summarize_conversation", END)
# ── CHECKPOINTER SELECTION ───────────────────────────────────
# Development (in-memory, lost on restart):
checkpointer = MemorySaver()
# Uncomment for local persistence (survives restarts):
# conn = sqlite3.connect("agent_memory.db", check_same_thread=False)
# checkpointer = SqliteSaver(conn)
# Uncomment for cloud/production (requires PostgreSQL):
# checkpointer = PostgresSaver.from_conn_string(os.environ["DATABASE_URL"])
graph = graph_builder.compile(checkpointer=checkpointer)
# ── MODULE 7: ENTRYPOINT ─────────────────────────────────────
if __name__ == "__main__":
config = {"configurable": {"thread_id": "user-001"}}
print("Memory-managed agent ready. Type 'exit' to quit, 'state' to inspect memory.\n")
while True:
user_text = input("You: ").strip()
if not user_text or user_text.lower() == "exit":
break
# Debug command: inspect what the agent currently remembers
if user_text.lower() == "state":
snapshot = graph.get_state(config)
print(f"\n[DEBUG] Messages in state: {len(snapshot.values['messages'])}")
print(f"[DEBUG] Summary: {snapshot.values.get('summary', '(none)')}\n")
continue
response = graph.invoke(
{"messages": [HumanMessage(content=user_text)]},
config=config
)
print(f"Agent: {response['messages'][-1].content}\n")
La guía de decisión para la estrategia de memoria
Al trabajar con la Guía de Decisión para Estrategias de Memoria, anote primero el contrato: los datos de entrada necesarios, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación garantiza que los cambios posteriores en el código sean transparentes. Guarde la configuración fuera del código de la aplicación. Los archivos de entorno, los almacenes de datos secretos y las banderas de funcionalidad deben encontrarse en un lugar donde los operadores puedan auditarlos sin tener que leer todo el sistema. Haga un punto de control después de los pasos costosos. La función de reanudación no debe volver a facturar la misma llamada al LLM cuando un operador intente nuevamente un nodo posterior. Al trabajar con la Guía de Decisión para Estrategias de Memoria, anote primero el contrato: los datos de entrada necesarios, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación garantiza que los cambios posteriores en el código sean transparentes. Prefiera unidades pequeñas y verificables sobre scripts extensos. Cuando un paso falla, el fallo debe indicar una única responsabilidad y no un proceso complicado.
La tarjeta de referencia actualizada de palabras clave
La tarjeta de referencia actualizada de palabras clave funciona mejor cuando se trata como una superficie medible. Capture una transcripción ejemplar, un caso de fallo y la nota de reversión antes de ampliar el alcance. Considere esta etapa como un contrato entre las entradas y los resultados validados. Asigne nombres a los artefactos, defina verificaciones de éxito y rechace completaciones parciales silenciosas. Mantenga el estado del gráfico plano y tipado. Los bloques anidados ocultan qué nodo escribió qué campo y provocan interrupciones en la continuación posterior.
Conclusión: La memoria es arquitectura, no algo secundario
Conclusión: “La memoria es arquitectura, no algo secundario” funciona mejor cuando se trata como una superficie medible. Capture una transcripción ejemplar, un caso de fallo y la nota de reversión antes de ampliar el alcance. Registre los tiempos y el costo de tokens o consultas junto con los resultados funcionales. Tener visibilidad del costo desde el principio evita facturas inesperadas cuando el proceso pasa de la fase de demostración a entornos compartidos. Mantenga el estado del grafo simple y tipado; los bloques anidados ocultan qué nodo escribió qué campo y causan interrupciones en la continuación del proceso.
Lista de verificación operativa
Para la lista de verificación operativa, defina las entradas, el responsable de cada paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido sin tener que adivinar el estado oculto.
Documente tanto la ruta óptima como la ruta de recuperación. Las reintentos, los controles humanos y el manejo de mensajes no entregados forman parte del producto, no son ajustes realizados posteriormente.
Se debe obtener la aprobación humana para las operaciones que generan gastos o modifican los datos de producción. La conexión en tiempo de compilación no equivale a la completitud del proceso empresarial.
Escriba un manual breve: cómo rotar claves, cómo vaciar la cola de tareas y cómo revertir la última operación de inserción.
Prefiera unidades pequeñas y verificables en lugar de scripts extensos. Cuando falla un paso, el problema debe atribuirse a una sola responsabilidad y no a un proceso complejo e entrelazado.
Se debe obtener la aprobación humana para las operaciones que generan gastos o modifican los datos de producción. La conexión en tiempo de compilación no equivale a la completitud del proceso empresarial.
Antes de promocionar la solución, congele las versiones, guarde una transcripción de referencia para el camino crítico y confirme los pasos de reversión. Los entornos compartidos requieren límites de velocidad, verificaciones de asignación y un responsable claro para la rotación de credenciales secretas. Prefiera una fiabilidad sencilla a demostraciones ingeniosas pero puntuales.
Nota por lotes para d865d505a59e: mantener las claves del proveedor fuera del repositorio, establecer un límite para los tokens por sesión y almacenar las transcripciones junto a los fixtures de evaluación para que los cambios posteriores en el modelo sigan siendo comparables.