Arquitectura de memoria para Agentes de IA: Estrategias para mantener contexto persistente sin costos exponenciales

El problema no es que tu agente olvide lo que dijo hace cinco minutos, es que recordarlo todo le cuesta 40 dólares por hora a tu empresa. La arquitectura de memoria para Agentes de IA se ha convertido en el diferenciador técnico entre un prototipo de juguete y un sistema escalable en producción. Mientras los modelos multiplican sus ventanas de contexto hasta 128k o incluso 200k tokens, enviar la conversación completa en cada llamada es económicamente suicida. Aquí no vamos a reinventar LangChain: te explico cómo funciona la memoria bajo el capó para que optimices lo que ya usas o construyas tu propia solución sin caer en trampas de diseño que duplican tus costos de inferencia.

El problema real: obesidad del contexto y «lost in the middle»

Los grandes modelos de lenguaje sufren de dos dolencias crónicas cuando les envías demasiado texto. La primera es económica: los precios de API escalan linealmente con los tokens de entrada. Si mantienes un historial de 20 mensajes de 500 tokens cada uno, estás pagando por 10k tokens de «contexto muerto» antes de que el modelo escriba una sola palabra.

La segunda es de atención: el fenómeno «lost in the middle» demuestra que la precisión del modelo decae dramáticamente cuando la información relevante está en el medio de un contexto masivo, incluso con ventanas de 128k tokens. Más contexto no es mejor contexto; es solo contexto más caro y más ruidoso.

Jerarquías de memoria: diseño en capas

Un agente productivo no tiene una sola «memoria», sino tres sistemas especializados que operan a diferentes escalas temporales y económicas.

Working Memory: ventana deslizante por tokens, no por mensajes

El error clásico es usar deque(maxlen=10) y luego recortar por tokens. Es contradictorio: si limitas físicamente a 10 mensajes, ¿por qué validar tokens? Si cada mensaje tiene 2k tokens (aproximadamente 1,500 palabras en español), esos «solo 10 mensajes» explotan tu presupuesto.

La solución es un límite exclusivo por tokens, sin restricción de cantidad de mensajes. En español, un token aproxima a una palabra (no uses la regla inglesa de 0.75 palabras/token ni la obsoleta de 4 caracteres/token). Para precisión quirúrgica, usa tiktoken o el tokenizer específico de tu modelo.

import tiktoken
from typing import List, Dict

class WorkingMemory:
    def __init__(self, max_tokens: int = 4000, model: str = "gpt-4"):
        self.messages: List[Dict] = []  # Lista simple, sin maxlen arbitrario
        self.max_tokens = max_tokens
        self.encoding = tiktoken.encoding_for_model(model)
    
    def add_message(self, role: str, content: str):
        self.messages.append({"role": role, "content": content})
        self._enforce_token_limit()
    
    def _enforce_token_limit(self):
        """Recorta desde el inicio manteniendo los mensajes más recientes."""
        total = 0
        for i, msg in enumerate(reversed(self.messages)):
            # Conteo real de tokens, no aproximación por caracteres
            tokens = len(self.encoding.encode(msg["content"]))
            total += tokens
            if total > self.max_tokens:
                cutoff = len(self.messages) - i - 1
                self.messages = self.messages[cutoff:]
                break
    
    def get_context(self) -> List[Dict]:
        return self.messages

Este diseño garantiza que nunca excedas los 4k tokens de ventana reciente, sin importar si fueron 3 mensajes largos o 50 mensajes cortos.

Memoria Semántica: retrieval cuando el pasado importa

No todo lo antiguo merece estar en el prompt activo. Para conocimiento factual persistente (preferencias del usuario, documentos subidos, historial de compras), usa una base vectorial. Convierte interacciones pasadas a embeddings y recupera solo los top-k relevantes vía búsqueda por similitud cuando el agente lo solicite explícitamente.

Esta estrategia transfiere el costo de O(n) tokens a O(1) tokens + costo fijo de embedding (barato) y retrieval (casi gratuito).

Memoria Episódica: compresión progresiva

Cuando la conversación es larga pero no puedes descartarla (ej. negociaciones complejas), implementa summarization condensado. Cada N interacciones, genera un resumen de los puntos clave y descarta el raw text. Tu Working Memory mantiene solo el resumen + los últimos mensajes crudos.

class EpisodicMemory:
    def __init__(self):
        self.summaries: List[str] = []
        self.recent_raw: WorkingMemory = WorkingMemory(max_tokens=2000)
    
    def compress_if_needed(self, llm_client):
        if self.recent_raw.token_count > 3000:
            summary = llm_client.summarize(self.recent_raw.get_context())
            self.summaries.append(summary)
            self.recent_raw = WorkingMemory(max_tokens=2000)
    
    def get_full_context(self):
        return {
            "historical_summaries": self.summaries,
            "recent_messages": self.recent_raw.get_context()
        }

Estimación de tokens: precisión vs velocidad

Nunca uses len(text) // 4 para estimar tokens en español. Un texto técnico en español genera aproximadamente 1 token por palabra (ligeramente más si hay muchos signos de puntuación o código). Para producción, integra siempre el tokenizer oficial:

  • OpenAI: tiktoken
  • Anthropic: API de conteo propia o heurística de 1.2 tokens/palabra
  • Open source (Llama, Mistral): transformers.AutoTokenizer
  • Un error común es asumir que «40k tokens por interacción» es normal. En escenarios conversacionales reales, un mensaje de usuario rara vez supera los 200-500 tokens. Si estás procesando documentos masivos, hazlo por chunks, no cargues PDFs completos en el contexto de chat.

    En producción: ¿Construir o adoptar?

    No reinventes la rueda a menos que tengas restricciones de latencia extremas o lógica de negocio muy específica.

  • LangChain ofrece ConversationBufferWindowMemory (limita por mensajes) y ConversationSummaryMemory (limita por tokens con resumen). Úsalos como punto de partida.
  • LlamaIndex es superior para arquitecturas RAG complejas donde la memoria semántica es el componente dominante.
  • Custom (como el código anterior) solo cuando necesites control granular sobre el recorte (ej. priorizar mensajes del sistema sobre mensajes del usuario) o cuando estés optimizando costos en escala masiva donde cada token cuenta.

Conclusión

La arquitectura de memoria eficiente se reduce a tres principios: recorta por tokens reales, no por cantidad de mensajes; mueve el conocimiento histórico a sistemas de retrieval, no al prompt; y comprime progresivamente lo que no cabe en tu ventana de trabajo. Los modelos con 200k tokens de contexto son una trampa seductora: te permiten ser perezoso, pero te cobran por esa pereza en cada inferencia.

Antes de optimizar código, mide tu consumo real de tokens por sesión de usuario. Si estás por encima de los 8k tokens de entrada promedio, tienes un problema de arquitectura, no de modelo.

¿Dónde está el cuello de botella en tu agente? Revisa tu última factura de API y dime cuántos tokens estás enviando de contexto histórico vs. cuántos generas de respuesta. Si la proporción está desbalanceada, es hora de rediseñar tu memoria.

LinkedIn
Share
Instagram
WhatsApp