Python & AI Tutorials Logo
LangChain & LangGraph

8. Estado de conversación y memoria

Hasta ahora, cada interacción con LLM que hemos construido ha sido sin estado (stateless). Sin estado significa que cada solicitud es independiente: el modelo no tiene memoria de conversaciones anteriores. Esto funciona bien para tareas puntuales como resumen de documentos o preguntas y respuestas de una sola pregunta.

Pero construir un agente conversacional es una historia diferente. Necesitas que el agente recuerde lo que se discutió anteriormente, entienda pronombres como "eso" o "aquello", y mantenga el contexto a lo largo del diálogo. Para hacer esto, debes gestionar explícitamente el estado (state).

(Aquí, "estado" se refiere a información que un programa recuerda. Para agentes conversacionales, el historial de conversación anterior es el estado.)

En este capítulo, cubriremos:

  • Por qué los LLM no "recuerdan" conversaciones
  • Cómo implementar memoria conversacional usando las herramientas de historial de mensajes de LangChain
  • Cómo gestionar presupuestos de tokens para prevenir desbordamiento de contexto

8.1) Por qué los LLM olvidan

Los LLM no tienen memoria

Los LLM tienen una característica crítica: no recuerdan nada de conversaciones anteriores.

Cuando llamas a una API de LLM, el modelo procesa tu entrada y genera una respuesta. Pero no almacena ese registro en ningún lugar. No hay memoria dentro del modelo que mantenga estado, no hay historial de conversación. Cada llamada a la API es completamente independiente. Es como empezar de cero cada vez.

Esto es por diseño. Los LLM funcionan como funciones sin estado (stateless): proporcionas entrada, producen salida, y nada se retiene. El modelo ejecutándose en los servidores de OpenAI ahora mismo no tiene registro de lo que acabas de preguntar.

Por qué los LLM parecen recordar

Pero espera: cuando usas ChatGPT o Claude, se siente como si recordaran tu conversación. Puedes decir "Háblame sobre París", luego continuar con "¿Cuál es la población?" y el modelo sabe que todavía estás hablando de París. ¿Cómo funciona eso?

Así es como: la aplicación envía el historial de conversación anterior junto con cada nuevo mensaje.

Esto es lo que realmente sucede:

LLMAppUserLLMAppUserLa app almacena el historial de mensajes"Háblame sobre París"[Mensaje 1: "Háblame sobre París"]"París es la capital de Francia...""París es la capital de Francia...""¿Cuál es la población?"[Mensaje 1: "Háblame sobre París"Mensaje 2: "París es la capital..."Mensaje 3: "¿Cuál es la población?"]"París tiene aproximadamente 2.1 millones...""París tiene aproximadamente 2.1 millones..."

El LLM no "recuerda" que preguntaste sobre París antes: solo lo sabe porque la aplicación envió el historial de conversación anterior junto con el nuevo mensaje. En última instancia, es la aplicación la que gestiona el estado, no el modelo.

Por qué el "estado" debe gestionarse en tu aplicación, no en el modelo

Piensa en un LLM como una función pura: dada una entrada, produce una salida. No hay estado interno que el LLM gestione más allá de los mensajes que proporcionas. Esto es por diseño.

Para agentes conversacionales, esto significa que el estado debe gestionarse en tu aplicación.

El estado (el historial de conversación) vive en el código de tu aplicación, no en el modelo. Esto significa que eres responsable de:

  • Almacenar el historial de conversación
  • Enviar el historial relevante con cada nueva solicitud
  • Gestionar el tamaño del historial (cubierto en la sección 8.3)

En la sección 8.2, implementaremos esto usando las herramientas de historial de mensajes de LangChain.

¿Qué sucede si no gestionas el estado?

Si no gestionas el estado, tu agente (tu aplicación) no puede mantener una conversación coherente. Estos son los fallos más comunes:

1. No puede recordar la conversación anterior

python
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
 
llm = ChatOpenAI(model="gpt-4o-mini")
 
# Primera pregunta
response1 = llm.invoke([HumanMessage(content="Mi nombre es Alice")])
print(response1.content)  # Salida: ¡Encantado de conocerte, Alice!
 
# Segunda pregunta (sin historial enviado)
response2 = llm.invoke([HumanMessage(content="¿Cuál es mi nombre?")])
print(response2.content)  # Salida: No sé tu nombre...

El modelo no tiene idea de que dijiste que tu nombre era Alice porque no enviamos esa información en la segunda llamada.

2. No puede saber a qué se refieren los pronombres

python
# El usuario pregunta sobre un tema
response1 = llm.invoke([HumanMessage(content="Háblame sobre Python")])
print(response1.content)  
# Salida: Python es un lenguaje de programación de alto nivel conocido por su legibilidad...
 
# El usuario continúa con un pronombre
response2 = llm.invoke([HumanMessage(content="¿Cuáles son sus características principales?")])
print(response2.content)  
# Salida: ¡Con gusto te ayudo! ¿Podrías especificar sobre qué estás preguntando?

Sin el mensaje anterior, el modelo no puede saber a qué se refiere "sus".

Impacto en el mundo real:

Imagina construir un agente de soporte al cliente sin gestión de estado:

Usuario: "Tengo problemas con mi pedido #12345"
Agente: "Lamento escuchar eso. ¿Cuál parece ser el problema?"
Usuario: "La dirección de envío está mal"
Agente: "Puedo ayudarte con eso. ¿Podrías proporcionar tu número de pedido?"
Usuario: "Acabo de decírtelo..."

Esta experiencia frustra a los usuarios y pierde su confianza. La gestión de estado no es opcional para agentes conversacionales: es esencial para crear interacciones coherentes y útiles.

En la sección 8.2, implementaremos la gestión de estado usando las herramientas de historial de mensajes de LangChain y agregaremos memoria conversacional al chat CLI.

8.2) Gestión del estado de conversación

Ahora que entendemos por qué la gestión de estado es crítica, aprendamos cómo implementarla. Usaremos las herramientas integradas de historial de mensajes de LangChain para gestionar el historial de conversación. Al final, aplicaremos lo que aprendimos para agregar gestión de estado al chat CLI del Capítulo 3.

Entendiendo los tipos de mensajes: HumanMessage, AIMessage y SystemMessage

Antes de aprender cómo gestionar el estado de conversación, necesitas conocer los tipos de mensajes usados en el estado de conversación. LangChain usa tres tipos de mensajes para representar conversaciones: HumanMessage (entrada del usuario), AIMessage (respuesta del modelo) y SystemMessage (instrucciones). Cada mensaje tiene un rol (role) (tipo de mensaje) y contenido (content) (el texto real).

python
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage
 
# SystemMessage: Instrucciones para el comportamiento del modelo
system_msg = SystemMessage(content="Eres un asistente útil especializado en programación Python.")
 
# HumanMessage: Entrada del usuario
user_msg = HumanMessage(content="¿Cómo leo un archivo en Python?")
 
# AIMessage: Respuesta del modelo
# (En la práctica, LangChain envuelve la respuesta del modelo en este objeto - se muestra aquí para ilustración)
ai_msg = AIMessage(content="Puedes usar la función `open()` con un gestor de contexto...")

¿Por qué tipos de mensajes separados?

Para que el modelo entienda el historial de conversación efectivamente, necesita conocer el propósito de cada mensaje y quién lo dijo. Los tres tipos de mensajes sirven propósitos distintos:

  • SystemMessage: Instrucciones que definen cómo debe comportarse el modelo (ej., "Sé conciso", "Eres un tutor de Python")
  • HumanMessage: Lo que dijo el usuario
  • AIMessage: Lo que el modelo respondió previamente

Esta estructura permite al modelo distinguir entre instrucciones, preguntas del usuario y sus propias respuestas pasadas, lo cual es esencial para mantener conversaciones coherentes de múltiples turnos.

Construyendo historial de conversación manualmente

Ahora que entendemos los tres tipos de mensajes, veamos cómo construir manualmente un historial de conversación:

python
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage
 
llm = ChatOpenAI(model="gpt-4o-mini")
 
# Construir historial de conversación
# SystemMessage establece instrucciones (una vez al inicio)
# Luego: Entrada del usuario → Respuesta de IA → Entrada del usuario (flujo de conversación natural)
messages = [
    SystemMessage(content="Eres un tutor conciso de Python."),
    HumanMessage(content="¿Qué es una comprensión de lista?"),
    AIMessage(content="Una comprensión de lista es una forma concisa de crear listas: [x*2 for x in range(5)]"),
    HumanMessage(content="¿Puedes mostrarme un ejemplo más complejo?")
]
 
# Enviar historial completo con nueva pregunta
response = llm.invoke(messages)
print(response.content)

Salida:

¡Claro! Aquí hay una comprensión de lista que filtra y transforma:
[x**2 for x in range(10) if x % 2 == 0]
Esto produce:
[0, 4, 16, 36, 64]

El modelo entiende que "un ejemplo más complejo" se refiere a un ejemplo más complejo de comprensión de lista porque enviamos el historial completo de conversación.

Usando InMemoryChatMessageHistory

Construir listas de mensajes manualmente se vuelve engorroso a medida que las conversaciones crecen. InMemoryChatMessageHistory de LangChain simplifica esto proporcionando métodos para agregar mensajes y recuperar el historial completo.

Métodos clave:

  • add_message(message): Agrega un solo mensaje (HumanMessage, AIMessage, SystemMessage)
  • add_messages(messages): Agrega múltiples mensajes a la vez
  • messages: Propiedad que devuelve la lista completa de mensajes
  • clear(): Elimina todos los mensajes (útil para empezar de nuevo)

Ejemplo:

python
from langchain_core.chat_history import InMemoryChatMessageHistory
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage
 
# Crear un almacén de historial de mensajes
history = InMemoryChatMessageHistory()
 
# Agregar múltiples mensajes a la vez
history.add_messages([
    SystemMessage(content="Eres un tutor útil de Python."),
    HumanMessage(content="¿Qué es un decorador en Python?")
])
 
# Agregar mensajes uno por uno
history.add_message(AIMessage(content="Un decorador es una función que modifica el comportamiento de otra función..."))
history.add_message(HumanMessage(content="¿Puedes mostrar un ejemplo?"))
 
# Recuperar todos los mensajes
messages = history.messages

Ejemplo práctico: Gestión de estado con InMemoryChatMessageHistory:

python
from langchain_openai import ChatOpenAI
from langchain_core.chat_history import InMemoryChatMessageHistory
from langchain_core.messages import SystemMessage, HumanMessage
 
llm = ChatOpenAI(model="gpt-4o-mini")
history = InMemoryChatMessageHistory()
 
# Establecer mensaje del sistema y agregar al historial (hecho una vez al inicio)
system_msg = SystemMessage(content="Eres un tutor útil de Python.")
history.add_message(system_msg)
 
# Función de chat
def chat(user_input):
    """Procesa la entrada del usuario, envía al LLM y gestiona automáticamente el historial de conversación"""
    # Agregar entrada del usuario al historial
    history.add_message(HumanMessage(content=user_input))
    
    # Enviar entrada del usuario junto con historial de conversación anterior
    response = llm.invoke(history.messages)
    
    # Agregar respuesta del modelo al historial
    history.add_message(response)
    
    return response.content
 
# Simular conversación
print(chat("¿Qué es una función lambda?"))
print(chat("Muéstrame un ejemplo"))  # El modelo recuerda el contexto
print(chat("¿Cuál es la diferencia con una función regular?"))  # Todavía recuerda

Salida:

Una función lambda es una función anónima definida con la palabra clave lambda...
 
Aquí hay un ejemplo: square = lambda x: x**2
Puedes usarla así: square(5) # Devuelve 25
 
Las funciones lambda están limitadas a una sola expresión, mientras que las funciones regulares...

La función chat() maneja la gestión de estado automáticamente: envía cada solicitud del usuario junto con el historial de conversación anterior al LLM, y agrega tanto la solicitud como la respuesta de vuelta al historial. Simplemente llamar a chat() mantiene el estado de conversación sin gestión adicional.

Refactorización del Capítulo 3: Agregando memoria a tu chat CLI

Tomemos el chat CLI con streaming del Capítulo 3 y agreguemos memoria conversacional. Aquí está la versión original sin estado:

python
# chapter3_cli.py (original - sin estado)
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
 
llm = ChatOpenAI(model="gpt-4o-mini")
 
def chat_loop():
    print("Chat iniciado. Escribe 'quit' para salir.\n")
    while True:
        user_input = input("Tú: ")
        if user_input.lower() == "quit":
            break
        
        # Sin estado - sin historial
        response = llm.stream([HumanMessage(content=user_input)])
        print("IA: ", end="", flush=True)
        for chunk in response:
            print(chunk.content, end="", flush=True)
        print("\n")
 
if __name__ == "__main__":
    chat_loop()

Versión refactorizada con memoria:

python
# chapter8_cli.py (con memoria)
from langchain_openai import ChatOpenAI
from langchain_core.chat_history import InMemoryChatMessageHistory
from langchain_core.messages import SystemMessage, HumanMessage, AIMessage
 
llm = ChatOpenAI(model="gpt-4o-mini")
 
def chat_loop():
    print("Chat iniciado. Escribe 'quit' para salir.\n")
    
    # Crear historial y agregar mensaje del sistema
    history = InMemoryChatMessageHistory()
    history.add_message(SystemMessage(content="Eres un asistente útil."))
    
    while True:
        user_input = input("Tú: ")
        if user_input.lower() == "quit":
            break
        
        # Agregar mensaje del usuario al historial
        history.add_message(HumanMessage(content=user_input))
        
        # Transmitir respuesta
        print("IA: ", end="", flush=True)
        full_response = ""
        for chunk in llm.stream(history.messages):
            print(chunk.content, end="", flush=True)
            full_response += chunk.content
        print("\n")
        
        # Agregar respuesta de IA al historial
        history.add_message(AIMessage(content=full_response))
 
if __name__ == "__main__":
    chat_loop()

Lo que cambió:

  1. Agregado almacenamiento de historial: Creada instancia de InMemoryChatMessageHistory() dentro de chat_loop()
  2. Mensaje del sistema: Agregado al historial una vez al inicio
  3. Enviar entrada del usuario con historial: llm.stream(history.messages) incluye conversación anterior
  4. Rastrear conversación: Agregar tanto entrada del usuario como respuesta del LLM al historial

Probando el chat refactorizado:

Chat iniciado. Escribe 'quit' para salir.
 
Tú: Mi nombre es Alice
IA: ¡Encantado de conocerte, Alice! ¿Cómo puedo ayudarte hoy?
 
Tú: ¿Cuál es mi nombre?
IA: Tu nombre es Alice.
 
Tú: ¿Qué acabo de preguntarte?
IA: Me preguntaste cuál es tu nombre.
 
Tú: quit

El modelo ahora mantiene contexto a lo largo de toda la conversación. Recuerda tu nombre, preguntas anteriores y puede hacer referencia a partes anteriores del diálogo.

Almacenamiento persistente: Más allá de las opciones en memoria

InMemoryChatMessageHistory es conveniente para desarrollo local, pero pasar a producción requiere reemplazarlo con una solución de almacenamiento que garantice persistencia.

Limitaciones técnicas de InMemory:

  • RAM volátil: Cuando el proceso del servidor termina o se reinicia, todo el historial de conversación almacenado en memoria se elimina inmediatamente. Las actualizaciones o recuperación de errores resultan en pérdida completa del contexto del usuario.
  • Sin escalado horizontal: A medida que tu servicio escala a múltiples instancias de servidor, cada servidor mantiene su propia memoria aislada. Los usuarios que se conectan a diferentes servidores no pueden compartir historial de conversación.
  • Ineficiencia de recursos: Almacenar todo el historial de conversación en RAM consume mucha memoria y amenaza la estabilidad del sistema a medida que aumentan los usuarios concurrentes.

Alternativas profesionales:

  • PostgresChatMessageHistory (Recomendado): La opción más robusta y ampliamente adoptada. Usa PostgreSQL para almacenamiento permanente y sobresale en consultas complejas y análisis de datos.
  • SQLChatMessageHistory: Aprovecha bases de datos SQL existentes como MySQL. Te permite usar tu infraestructura actual sin cambios.
  • RedisChatMessageHistory: Ideal para servicios donde la velocidad de respuesta es crítica. Basado en memoria con opciones de persistencia, especializado en manejo de alto tráfico.

"El almacenamiento cambia, el código permanece igual"

LangChain proporciona una interfaz unificada en todos los backends de almacenamiento. Los mismos métodos que usaste con InMemoryChatMessageHistory—como add_message() y add_messages()—funcionan idénticamente con otras opciones de almacenamiento. Esto significa que tu lógica de negocio (código de manejo de conversación) no requiere cambios al cambiar de almacenamiento.

python
# [Desarrollo] En memoria local
# from langchain_core.chat_history import InMemoryChatMessageHistory
# history = InMemoryChatMessageHistory()
 
# [Producción] Almacenamiento persistente PostgreSQL
import psycopg
from langchain_postgres import PostgresChatMessageHistory
 
# Crear conexión PostgreSQL
sync_connection = psycopg.connect(
    "postgresql://user:password@10.1.1.100:5432/agent_db",
    autocommit=True,
)
 
# Especificar conexión DB y session ID
# session_id: Clave única para identificar conversaciones
#   - Gestión por usuario: session_id = user_id (una conversación por usuario)
#   - Gestión por sesión: session_id = uuid (nuevo ID para cada conversación)
history = PostgresChatMessageHistory(
    table_name="chat_history",
    session_id="user_123",
    sync_connection=sync_connection
)
 
# --- Misma interfaz independientemente del tipo de almacenamiento ---
history.add_message(HumanMessage(content="Muéstrame mi pedido anterior."))
print(history.messages)

Importante: Esta guía usa InMemory para progresión rápida, pero los despliegues de producción deben cambiar a almacenamiento persistente como PostgresChatMessageHistory.

8.3) Gestión de la longitud de conversación

La función de memoria conversacional que implementamos en la sección anterior tiene un problema importante: solo agrega mensajes al historial. Esto significa que el historial sigue creciendo, lo que crea dos problemas principales:

  1. Costo: El historial completo se envía con cada solicitud, por lo que el costo por solicitud continúa aumentando a medida que crece el historial
  2. Límites de ventana de contexto: Los modelos tienen un tamaño máximo de entrada que pueden procesar en una sola solicitud (ej., 400K tokens para GPT-5). Cuando el historial de conversación excede este límite, el modelo no puede procesar la solicitud correctamente.

Una de las soluciones más simples es el patrón de ventana deslizante (sliding window).

Patrón de ventana deslizante (mantener solo los últimos N mensajes)

El patrón de ventana deslizante resuelve los dos problemas anteriores manteniendo solo los N mensajes más recientes en el historial de conversación. Gestiona el tamaño del historial descartando mensajes antiguos, proporcionando los siguientes beneficios:

  1. Control de costos: Al mantener el historial por debajo de cierto tamaño independientemente de la longitud de la conversación, previene que los costos por solicitud crezcan infinitamente
  2. Sin desbordamiento: Mantiene el tamaño de entrada dentro del límite máximo del modelo

Diagrama conceptual:

Mensaje 1

Mensaje 2

Mensaje 3

Mensaje 4

Mensaje 5

Mensaje 6

Tamaño de ventana = 4

Con un tamaño de ventana de 4, mantenemos solo los 4 mensajes más recientes (3, 4, 5, 6) y descartamos los mensajes más antiguos (1, 2). Cuando llega un nuevo mensaje (7), la ventana se mueve hacia el mensaje más reciente, descartando el mensaje más antiguo (3) y manteniendo los mensajes 4, 5, 6, 7.

Compensaciones de la ventana deslizante:

  • Pros: Limita el tamaño del historial para mantener costos constantes y previene desbordamiento de ventana de contexto
  • Contras: Los mensajes más allá del tamaño de ventana se descartan, por lo que el modelo no puede hacer referencia a ellos

Esta compensación puede ser problemática. La solución es usar una ventana deslizante para conversación reciente mientras se recupera información pasada necesaria de un almacenamiento separado cuando sea necesario. Esto puede implementarse usando RAG (Retrieval-Augmented Generation), que cubriremos en el Capítulo 9.

Unidades de tamaño de ventana: Conteo de mensajes vs Conteo de tokens

El diagrama anterior muestra un ejemplo de establecer el tamaño de ventana por conteo de mensajes. Sin embargo, en entornos de producción, el dimensionamiento de ventana basado en tokens es más comúnmente usado porque los tamaños de mensajes varían:

Recorte basado en conteo de mensajes:

Limita el tamaño de ventana por conteo de mensajes (ej., mantener solo los últimos 20 mensajes).

  • Características: Conteo fijo de mensajes, pero el conteo total de tokens aún puede variar
  • Usar cuando: Los tamaños de mensajes están controlados (SMS, chats con límite de caracteres)
  • Riesgo: Un mensaje largo aún puede exceder la ventana de contexto

Recorte basado en conteo de tokens (Recomendado para producción):

Limita el tamaño de ventana por conteo de tokens, la unidad de entrada procesada por LLMs (ej., mantener solo los últimos 5,000 tokens).

  • Características: Nunca excede la ventana de contexto independientemente de la longitud del mensaje
  • Usar cuando: Los tamaños de mensajes varían

Implementando recorte basado en tokens: trim_messages()

LangChain proporciona una utilidad trim_messages() que implementa el patrón de ventana deslizante basado en tokens.

Cómo funciona trim_messages():

Esta función toma la lista completa de mensajes y el conteo máximo de tokens, devolviendo solo los mensajes más recientes que caben dentro del límite de tokens.

Parámetros clave:

  • messages: Lista de mensajes a recortar
  • max_tokens: Conteo máximo de tokens a mantener
  • token_counter: Función para calcular el conteo de tokens para cada mensaje (usa el tokenizador del modelo para devolver el conteo de tokens del mensaje)
  • include_system: Si siempre mantener SystemMessage (usualmente True)

Configurando el contador de tokens:

python
import tiktoken
from langchain_core.messages import BaseMessage, HumanMessage, AIMessage, SystemMessage
from langchain_core.messages.utils import trim_messages
 
# Obtener tokenizador para tu modelo (diferentes familias de modelos usan diferentes tokenizadores)
# Proporciona el nombre del modelo para obtener el tokenizador apropiado
# Modelos Claude: Usa el tokenizador de Anthropic (tiktoken es específico de OpenAI)
enc = tiktoken.encoding_for_model("gpt-4o")
 
def token_counter(msg: BaseMessage) -> int:
    """Cuenta tokens en un solo mensaje."""
    return len(enc.encode(msg.content or ""))
 
# Crear historial de conversación
messages = [
    SystemMessage(content="Eres un asistente útil."),
    HumanMessage(content="¡Hola!"),
    AIMessage(content="¡Hola! ¿Cómo puedo ayudarte?"),
    HumanMessage(content="¿Cuánto es 2+2?"),
    AIMessage(content="2+2 es igual a 4."),
    HumanMessage(content="¿Cuánto es 3+3?"),
    AIMessage(content="3+3 es igual a 6."),
    HumanMessage(content="¿Cuánto es 4+4?"),
]
 
# Mantener solo mensajes dentro del conteo máximo de tokens
trimmed = trim_messages(
    messages,
    max_tokens=30,
    token_counter=token_counter,
    include_system=True,
)
 
print(f"Original: {len(messages)} mensajes")
print(f"Recortado: {len(trimmed)} mensajes")
for m in trimmed:
    print(f"{type(m).__name__}: {m.content}")

Nota: El número de mensajes retenidos depende del valor de max_tokens y el conteo real de tokens de cada mensaje. En el ejemplo anterior, max_tokens=30 es un valor muy pequeño elegido para propósitos de prueba. En producción, debes establecer un valor apropiado considerando el tamaño promedio de mensaje y el rango de conversación deseado.

Ejemplo: Aplicando recorte a la función de chat

python
import tiktoken
from langchain_openai import ChatOpenAI
from langchain_core.chat_history import InMemoryChatMessageHistory
from langchain_core.messages import SystemMessage, HumanMessage, BaseMessage
from langchain_core.messages.utils import trim_messages
 
llm = ChatOpenAI(model="gpt-4o-mini")
history = InMemoryChatMessageHistory()
enc = tiktoken.encoding_for_model("gpt-4o-mini")
 
def token_counter(msg: BaseMessage) -> int:
    """Cuenta tokens en un solo mensaje."""
    return len(enc.encode(msg.content or ""))
 
def chat_with_trimming(user_input: str, max_tokens: int = 1000) -> str:
    """Chat con recorte automático de historial."""
    history.add_message(HumanMessage(content=user_input))
    
    system_msg = SystemMessage(content="Eres un asistente útil.")
    all_messages = [system_msg] + history.messages
    
    # Recortar al conteo máximo de tokens
    trimmed_messages = trim_messages(
        all_messages,
        max_tokens=max_tokens,
        token_counter=token_counter,
        include_system=True,
    )
    
    response = llm.invoke(trimmed_messages)
    history.add_message(response)
    
    return response.content
 
# Ejemplo de uso
print(chat_with_trimming("Estoy planeando un viaje a Japón"))
print(chat_with_trimming("¿Qué debería visitar en Tokio?"))
print(chat_with_trimming("¿Cuántos días debería pasar allí?"))
# Incluso a medida que crece el historial, solo la conversación reciente dentro del conteo máximo de tokens se envía al LLM

Siguiente paso: Limitaciones de la gestión de estado de conversación y soluciones (Capítulo 9: RAG)

Proporcionar historial de conversación al modelo ayuda a mantener el contexto de conversación. Sin embargo, esto solo no es suficiente en algunos casos. Por ejemplo:

  • Cuando necesitas encontrar información en documentos de empresa o manuales
  • Cuando necesitas hacer referencia a historial de conversación antiguo que ha sido empujado fuera de la ventana deslizante

Aquí es donde se necesita RAG (Retrieval-Augmented Generation). RAG funciona de la siguiente manera:

  1. Almacenamiento: Almacenar información en una base de datos vectorial para búsqueda semántica
  2. Recuperación: Consultar información con significado similar a lo que estás buscando

Si usas RAG para complementar las limitaciones del patrón de ventana deslizante:

  • Ventana deslizante: Mantener últimos 20 mensajes (contexto reciente)
  • RAG: Buscar y recuperar contenido relevante de mensajes empujados fuera de la ventana

En lugar de solo recordar conversación reciente, RAG te permite crear un sistema de memoria a largo plazo.

RAG permite a los agentes aprovechar conocimiento más amplio y contexto más largo a través de utilización de conocimiento externo y recuperación de conversación pasada.

El Capítulo 9 cubrirá cómo implementar RAG en detalle.


Resumen del capítulo:

En este capítulo, aprendiste:

  1. Por qué los LLM olvidan: Los modelos son sin estado: la memoria es una ilusión creada al reenviar el historial de conversación
  2. Tipos de mensajes: SystemMessage (instrucciones), HumanMessage (entrada del usuario), AIMessage (respuestas del modelo)
  3. Gestión de estado: Gestionar historial de conversación con InMemoryChatMessageHistory
  4. Gestión de longitud de conversación: Por qué el historial ilimitado causa problemas de costo y ventana de contexto
  5. Ventanas deslizantes: Un patrón que mantiene solo mensajes recientes para prevenir que el historial crezca infinitamente
  6. Recorte basado en tokens: Implementar ventanas deslizantes usando trim_messages() y conteo de tokens