En este artículo, aprenderá por qué una ventana de contexto grande no es lo mismo que la memoria del agente y cómo técnicas como la recuperación, la compresión y el resumen encajan en la pila cognitiva de un agente.
Los temas que cubriremos incluyen:
Por qué una ventana de contexto se comporta como un bloc de notas sin estado en lugar de una memoria persistente. Cómo la generación, la compresión y el resumen con recuperación aumentada desempeñan cada uno un papel distinto en la gestión de lo que ingresa a ese bloc de notas. Cómo los agentes pueden lograr una persistencia genuina de la memoria actuando como administradores de bases de datos en lugar de como la base de datos misma.
Introducción
Las ventanas de contexto son un aspecto clave de los modelos modernos de IA, en particular los modelos de lenguaje, mediante los cuales estos modelos pueden atender y utilizar una cantidad limitada de entradas y conversaciones previas (generalmente medidas como una cantidad de tokens) a la vez al producir una respuesta.
Cuando un laboratorio de IA lanza un modelo con una ventana de contexto de 2 millones de tokens, no sorprende que algunos desarrolladores piensen instintivamente así: “¡Introduzcamos todo el código base en el mensaje! ¡Problemas de memoria solucionados!”. Sin embargo, hay una advertencia. Considerar una enorme ventana contextual como “memoria” es, en términos arquitectónicos, similar a comprar un escritorio de oficina de 25 pies de ancho porque uno es reacio a adquirir un archivador. Claro, puedes tener todos tus documentos frente a ti, pero tan pronto como termina la sesión de trabajo, todos los documentos del escritorio son borrados (¡por el personal de limpieza!).
Para aclarar esta distinción y desmitificar otros conceptos relacionados, este artículo ofrece un desglose conceptual de múltiples capas en la pila cognitiva de los agentes de IA. Usaremos varias metáforas, en su mayoría relacionadas con la oficina, para facilitar una mejor comprensión de estos conceptos.
Ventana de contexto
Una ventana de contexto en un modelo de IA, particularmente en los basados en agentes con modelos de lenguaje subyacentes, es como la superficie de un escritorio o un bloc de notas sin estado. Es importante señalar que los modelos son inherentemente totalmente apátridas. Pase lo que pase, cada llamada API a un modelo comienza en el “paso cero”.
Al pasarle a un agente un historial de conversaciones que abarca más de 200 000 tokens (ventana de contexto grande), no recuerda lo que sucedió en un paso anterior en el tiempo. En cambio, está releyendo rápidamente “su universo” desde cero en cuestión de milisegundos. A largo plazo, confiar en esta estrategia en entornos basados en agentes puede introducir varias trampas peligrosas (si no fatales):
Los modelos de IA actúan como un estudiante perezoso, que presta mucha atención a las partes inicial y final de un mensaje masivo (texto), pero pasa por alto por completo ideas y hechos enterrados en las partes intermedias. Se produce un efecto de bola de nieve: a medida que la conversación crece, el agente debe reenviar y releer el historial completo en cada paso, incluidos los primeros giros, a menudo irrelevantes. En términos de latencia, se produce un efecto de “congelación cerebral”, de modo que, frente a una enorme pared de texto, el modelo tardará algún tiempo en empezar a generar la primera palabra de su respuesta.
Para concretar esto, considere cómo se ve realmente una sola llamada API bajo el capó. Debido a que el modelo no guarda memoria entre llamadas, cada turno anterior debe reenviarse en su totalidad solo para hacer una nueva pregunta:
modelo.generar (mensajes =[
{“role”: “user”, “content”: “Step 1: Let’s call this variable `session_id`.”},
{“role”: “assistant”, “content”: “Got it, I’ll use `session_id` going forward.”},
# … every intervening turn must be resent, every single time …
{“role”: “user”, “content”: “Step 47: What variable name did we agree on back in step 1?”}
])
modelo.generar(
mensajes=[
{“role”: “user”, “content”: “Step 1: Let’s call this variable `session_id`.”},
{“role”: “assistant”, “content”: “Got it, I’ll use `session_id` going forward.”},
# … every intervening turn must be resent, every single time …
{“role”: “user”, “content”: “Step 47: What variable name did we agree on back in step 1?”}
]
)
El paso 47 por sí solo obliga a todo el escritorio (los 46 turnos anteriores) a volver a la mesa, solo para responder una pregunta sobre el paso 1. Ese es el efecto de bola de nieve descrito anteriormente, hecho concreto.
Recuperación
Los sistemas de generación aumentada de recuperación (RAG) son como una gran estantería situada en la sala de la oficina, que ayuda a recuperar datos estáticos existentes y relevantes para el paso actual de forma “justo a tiempo”. Los sistemas RAG colocan los K fragmentos de documentos más relevantes en el bloc de notas (la ventana contextual) cuando el usuario hace una determinada pregunta: los documentos recuperados son, por supuesto, los que se determinan como más semánticamente relevantes para la pregunta o sugerencia del usuario.
Sin embargo, cuando los agentes están en el circuito, las cosas no son tan fáciles, ya que la similitud vectorial (el tipo de medida de similitud y representación de datos utilizada en los sistemas RAG) no es necesariamente equivalente a la verdad semántica en ciertos casos. Por ejemplo, supongamos que un usuario le dice a su agente de programación que traslade una reunión al viernes y luego dice “cancelar el jueves, Alice está enferma”. Un motor de búsqueda vectorial puede recuperar ambas declaraciones de una base de documentos, aunque se contradigan entre sí. El agente y su modelo de lenguaje asociado deben poder actuar como contadores capaces de determinar qué declaración refleja mejor la realidad actual.
Una canalización RAG ingenua simplemente concatena todo lo que recupera y deja que el modelo adivine qué instrucción aún se cumple. Un patrón más confiable resuelve el conflicto antes de que ocurra la generación, por ejemplo, favoreciendo la declaración registrada más recientemente:
fragmentos_recuperados = [
{“text”: “Move meeting to Friday”, “timestamp”: “2025-01-10T09:00:00”},
{“text”: “Cancel Thursday, Alice is sick”, “timestamp”: “2025-01-12T14:30:00”}
]# Conciliar fragmentos contradictorios antes de que lleguen al mensaje last_relevant = max(retried_chunks, key=lambda fragmento: fragmento[“timestamp”])
fragmentos_recuperados = [
{“text”: “Move meeting to Friday”, “timestamp”: “2025-01-10T09:00:00”},
{“text”: “Cancel Thursday, Alice is sick”, “timestamp”: “2025-01-12T14:30:00”}
]
# Conciliar fragmentos contradictorios antes de que lleguen al mensaje
último_relevante = máximo(fragmentos_recuperados, llave=lambda pedazo: pedazo[“timestamp”])
Esa línea de lógica de reconciliación es la diferencia entre un agente que reafirma con confianza una instrucción obsoleta y uno que sabe correctamente que la reunión fue cancelada.
Compresión
Esto es fácil de entender si está familiarizado con la compresión en archivos ZIP. En el contexto de los agentes y los modelos de lenguaje, esto implica cierta reducción de tokens algorítmicos: mantener intactos los datos clave subyacentes, mientras se reduce su huella física dentro de un mensaje en un determinado paso. Existen técnicas como eliminar palabras vacías y pasar texto sin formato a un modelo de compresión específico como LLMLingua o Prompt Caching, para hacer esto. Esto es, en esencia, una jugada de optimización del ancho de banda que se utilizará en situaciones como reducir una carga útil JSON de 15 000 tokens a 5 K, dejando así suficiente espacio en el bloc de notas en el modelo para realizar su trabajo principal.
En la práctica, esto podría parecer tan simple como enrutar una carga útil grande a través de un modelo de compresión antes de que llegue al mensaje principal:
raw_payload = json.dumps(large_api_response) # aproximadamente 15,000 tokens comprimido_payload = compress_with_llmlingua( raw_payload, target_token_count=5000 ) Prompt = f”Teniendo en cuenta estos datos: {compressed_payload}\n\nResponda la pregunta del usuario.”
carga_pay_raw = json.deshecho(respuesta_api_grande) # aproximadamente 15.000 tokens
carga útil comprimida = comprimir_con_llmlingua(
carga_pay_raw,
cuenta_token_objetivo=5000
)
inmediato = F“Dados estos datos: {compressed_payload}\n\nResponde la pregunta del usuario.”
Los hechos subyacentes sobreviven intactos al viaje; sólo su huella en el escritorio se reduce.
Resumen
A diferencia de la compresión, el resumen elimina los datos originales y los reemplaza con una abstracción. Debe tratarse como lo que es: un viaje de ida que es inherentemente irreversible. Por lo tanto, una buena práctica, casi imperativa, al aplicar el resumen de contexto es utilizar almacenamiento bifurcado: volcar transcripciones sin procesar en almacenamiento barato como depósitos S3 o tablas SQL básicas y luego pasar solo el resumen sintetizado al mensaje activo.
Ese patrón de almacenamiento bifurcado se puede expresar simplemente como una escritura de dos pasos, uno en el almacenamiento en frío y otro en el mensaje activo:
def resume_turn(raw_transcript, session_id, turn_id): # 1. Conservar la transcripción sin procesar y íntegra en el almacenamiento en frío s3_client.put_object( Bucket=”agent-transcripts”, Key=f”{session_id}/turn_{turn_id}.json”, Body=raw_transcript ) # 2. Generar un resumen compacto para el resumen del mensaje activo = resumer_model.generate(raw_transcript) # 3. Solo el resumen vuelve a ingresar a la ventana contextual y devuelve el resumen
definición resumir_turno(transcripción_cruda, id_sesión, turn_id):
# 1. Conservar la transcripción íntegra y sin procesar en el almacenamiento en frío
s3_cliente.poner_objeto(
Balde=“transcripciones-de-agente”,
Llave=F“{session_id}/turn_{turn_id}.json”,
Cuerpo=crudo_transcripción
)
# 2. Genere un resumen compacto para el mensaje activo
resumen = modelo_resumidor.generar(transcripción_cruda)
# 3. Solo el resumen vuelve a ingresar a la ventana contextual
devolver resumen
Si un paso posterior necesita los detalles originales, siempre se pueden recuperar desde S3. El resumen, a diferencia de la compresión, nunca necesita reconstruirse desde el interior del mensaje activo.
La persistencia de la memoria como máquina de estados.
La persistencia de la memoria en los agentes se da por sentada la mayoría de las veces, especialmente por parte de los desarrolladores junior. Pero para darle a un agente una memoria genuina, no debe actuar como base de datos, sino como administrador de la base de datos. Supongamos que un usuario dice: “El nombre de mi perro es Goofy, pero podríamos cambiarle el nombre a Plutón”. Entonces el agente debería poder activar explícitamente una llamada a una herramienta como esta:
{ “tool”: “update_entity_graph”, “params”: { “subject”: “User_Dog”, “attribute”: “Name”, “value”: “Goofy”, “notes”: “Considerando a Plutón” } }
{
“herramienta”: “update_entity_graph”,
“parametros”: {
“sujeto”: “Usuario_Perro”,
“atributo”: “Nombre”,
“valor”: “Mentecato”,
“notas”: “Considerando a Plutón”
}
}
Es irrelevante si está respaldado por una tabla SQL estándar, un gráfico de conocimiento o Redis: de cualquier manera, se debe enseñar al agente a consultar la máquina de estado al comienzo de cada turno y comprometerse con ella al final de ese turno. Como bucle, esta disciplina de consulta y confirmación se ve así:
def agent_turn(user_message, entidad_graph): # Consulta el estado existente al INICIO de cada turno current_state = entidad_graph.query(subject=”User_Dog”) respuesta = model.generate( mensajes =[{“role”: “user”, “content”: user_message}]context=current_state ) # Confirma cualquier actualización al FINAL de cada turno para la llamada en respuesta.tool_calls: entidad_graph.update(**call.params) devuelve respuesta
definición turno_agente(mensaje_usuario, gráfico_entidad):
# Consultar el estado existente al INICIO de cada turno
estado_actual = gráfico_entidad.consulta(sujeto=“Usuario_Perro”)
respuesta = modelo.generar(
mensajes=[{“role”: “user”, “content”: user_message}],
contexto=actual_estado
)
# Confirmar cualquier actualización al FINAL de cada turno
para llamar en respuesta.llamadas_herramientas:
gráfico_entidad.actualizar(**llamar.parámetros)
devolver respuesta
Concluyendo
A través de estos conceptos, ahora debería tener una idea más clara de los elementos que desempeñan un papel en la gestión del contexto para agentes creados sobre modelos de lenguaje. La lección es simple: deje de intentar comprar un escritorio enorme de 10 millones de tokens. En su lugar, consiga un escritorio normal, déle a su agente un lápiz afilado y enséñele cómo abrir el archivador y aprovechar de manera óptima su contenido para hacer su trabajo.