RAG no es suficiente: construí la capa de contexto faltante que hace que los sistemas LLM funcionen

TL;DR

una implementación funcional completa en Python puro, con números de referencia reales.

Los sistemas RAG se rompen cuando el contexto crece más allá de unos pocos giros.

El verdadero problema no es la recuperación, sino lo que realmente entra en la ventana contextual.

Un motor de contexto controla explícitamente la memoria, la compresión, la reclasificación y los límites de tokens.

Este no es un concepto. Este es un sistema de trabajo con comportamiento medible.

El punto de ruptura de los sistemas RAG

Construí un sistema RAG que funcionó perfectamente, hasta que dejó de funcionar.

En el momento en que agregué el historial de conversaciones, todo empezó a fallar. Se estaban eliminando documentos relevantes. El mensaje se desbordó. La modelo empezó a olvidar cosas que había dicho hace dos turnos. No porque la recuperación haya fallado. No porque el mensaje estuviera mal escrito. Sino porque no tenía control sobre lo que realmente entraba en la ventana de contexto.

Ese es el problema del que nadie habla. La mayoría de los tutoriales de RAG se detienen en: recuperar algunos documentos, incluirlos en un mensaje, llamar al modelo. ¿Qué sucede cuando el contexto recuperado tiene 6000 caracteres pero el presupuesto restante es 1800? ¿Qué sucede cuando tres de los cinco documentos recuperados están casi duplicados, desplazando al único útil? ¿Qué sucede cuando el turno uno de una conversación de veinte turnos todavía permanece en el mensaje, ocupando espacio, mucho después de que dejó de ser relevante?

Estos no son casos extremos raros. Esto es lo que sucede de forma predeterminada y comienza a fallar en los primeros giros.

Todos los resultados a continuación provienen de ejecuciones reales del sistema (Python 3.12, solo CPU, sin GPU), excepto donde se indique que están calculados.

La respuesta es una capa que la mayoría de los tutoriales omiten por completo. Entre la recuperación en bruto y la construcción inmediata, hay un paso arquitectónico deliberado: decidir qué ve realmente el modelo, cuánto y en qué orden. En 2025, Andrej Karpathy le dio a esto un nombre: ingeniería de contexto.[2]. Lo había estado construyendo durante meses sin llamarlo así.

Este es el sistema que construí desde la recuperación hasta la memoria y la compresión con números reales y código que puedes ejecutar.

Código completo: https://github.com/Emmimal/context-engine/

¿Qué es realmente la ingeniería de contexto?

Vale la pena ser preciso, porque los términos se confunden.

La ingeniería de avisos es el arte de lo que le dice al modelo: el aviso de su sistema, sus ejemplos de pocas tomas, sus instrucciones de formato de salida. Da forma a cómo razona el modelo.

RAG es una técnica para recuperar documentos externos relevantes e incluirlos antes de su generación. Basa el modelo en hechos en los que no fue entrenado.[1].

La ingeniería de contexto es la capa intermedia: las decisiones arquitectónicas sobre qué información fluye hacia la ventana de contexto, cuánta y de qué forma. Responde: dado todo lo que podría incluirse en este mensaje, ¿qué debería incluirse realmente?

Los tres son complementarios. En un sistema bien diseñado, cada uno tiene un trabajo distinto.

¿Para quién es esto?

Vale la pena construir esta arquitectura si está trabajando en chatbots de múltiples turnos donde el contexto se acumula a lo largo de los turnos, sistemas RAG con grandes bases de conocimiento donde el ruido de recuperación es un problema real, o copilotos y agentes de IA que necesitan memoria para mantenerse coherentes.

Omítalo para consultas de un solo turno con una base de conocimientos pequeña: la sobrecarga del proceso no justifica una ganancia marginal de calidad. Omítalo para servicios con latencia crítica de menos de 50 ms: la generación integrada por sí sola agrega ~85 ms en la CPU. Omítalo para dominios totalmente deterministas como el análisis de contratos legales, donde la recuperación de palabras clave suele ser suficiente y más auditable.

Si tiene ventanas de contexto ilimitadas y latencia ilimitada, RAG simple funciona bien. En la producción, esas limitaciones no existen.

Arquitectura de canalización completa

Un proceso completo de ingeniería de contexto para sistemas RAG, que combina recuperación, administración de memoria, compresión y control del presupuesto de tokens para crear aplicaciones LLM eficientes y escalables. Imagen del autor.

Componente 1: El Retriever

La mayoría de las implementaciones de RAG eligen un método de recuperación y lo dan por terminado. El problema es que ningún método domina todos los tipos de consultas. La concordancia de palabras clave es rápida y precisa para términos exactos. TF-IDF maneja la ponderación de términos. Las incrustaciones de vectores densos captan relaciones semánticas que las palabras clave pasan por alto por completo.

Palabra clave frente a TF-IDF: misma consulta, comportamiento diferente

Para la consulta: "¿Cómo funciona la memoria en los agentes de IA?"

Ambos métodos coinciden en que mem-001 es el documento principal. Pero hay una diferencia fundamental: TF-IDF proporciona una puntuación más matizada al ponderar la rareza de los términos, mientras que la recuperación de palabras clave solo cuenta la superposición bruta. En esta cuestión convergen, pero divergen mucho en cuestiones conceptuales con redacción diferente. Precisamente por eso se hace necesaria la recuperación híbrida.

El Retriever admite tres modos: palabra clave, tfidf e híbrido. El modo híbrido ejecuta ambos métodos y combina sus puntuaciones con un único peso ajustable:

puntuación_híbrida = alfa * puntuación_emb + (1 – alfa) * puntuación_tf

El valor predeterminado alfa=0,65 pondera las incorporaciones un poco más que TF-IDF: empírico, sin principios, pero probado en diferentes estilos de consulta. Las consultas con muchas palabras clave funcionan mejor alrededor de alfa=0,4; Las consultas de estilo paráfrasis se benefician de alfa=0,8 o superior.

Qué soluciona la recuperación híbrida que TF-IDF omite

Para la consulta: "¿Cómo se comparan las incorporaciones con TF-IDF para la memoria en agentes de IA?"

ModoDocumentos recuperadosPor quéTF-IDFmem-001, vec-001, ctx-001Solo aparecen documentos con palabras clave superpuestasHybridmem-001, vec-001, tfidf-001, ctx-001Ahora aparece tfidf-001 conceptualmente relevante

tfidf-001 no aparece en los resultados de TF-IDF porque comparte pocos tokens de consulta. El modo híbrido sale a la luz porque la incorporación reconoce su relevancia conceptual. Este es el modo de falla exacto del RAG tradicional a escala.

Una nota de implementación: los transformadores de oraciones son opcionales. Sin él, el sistema recurre a incrustaciones aleatorias con una advertencia. La producción adquiere una semántica real; el desarrollo obtiene un trozo funcional.

Componente 2: El reclasificador

La recuperación le proporciona candidatos. La reclasificación decide el orden final.

El reclasificador aplica una suma ponderada de dos factores que combina la puntuación de recuperación con un valor de importancia basado en etiquetas. Los documentos etiquetados con memoria, contexto, trapo o incrustación reciben una tag_importance de 1,4; todos los demás reciben 1.0. Ambos alimentan la misma fórmula:

puntuación_final = puntuación_base * 0,68 + importancia_etiqueta * 0,32

Un documento etiquetado con tag_importance=1,4 aporta 0,448 solo por ese término, frente a 0,32 de uno sin etiquetar: una bonificación fija de 0,128 independientemente de la puntuación de recuperación. Los pesos reflejan un previo específico: la señal de recuperación es primaria, la relevancia del dominio es una señal secundaria significativa.

Puntuaciones antes y después de la reclasificación

DocumentoAntes de reclasificarDespués de reclasificarCambiarmem-0010.41610.7309+75.7%rag-001fuera del top 40.5280promotedvec-0010.28800.5158+79.1%tfidf-0010.21640.4672+115.9%

rag-001 salta desde fuera de los cuatro primeros a la segunda posición completamente debido a su impulso de etiqueta. Estos reordenamientos cambian qué documentos sobreviven a la compresión; no son cosméticos.

¿La heurística tiene principios? No del todo. Un reclasificador de codificador cruzado: puntúa cada par de consulta y documento con un modelo neuronal[7]- sería más exacto. Pero los codificadores cruzados cuestan una llamada de modelo por documento. En cinco documentos, la heurística se ejecuta en microsegundos. A más de 500, vale la pena el costo de un codificador cruzado.

Componente 3: Memoria con decadencia exponencial

Este es el componente que la mayoría de los tutoriales omiten por completo y en el que los sistemas ingenuos colapsan más rápido.

La memoria conversacional tiene dos modos de falla: olvidar demasiado rápido (perder contexto que aún es relevante) y olvidar demasiado lento (acumular ruido que desplaza información útil). Una ventana corrediza deja caer abruptamente las viejas curvas: la curva 10 está completamente presente, la curva 11 ya no está. No es así como funciona la información útil.

La solución es la caída exponencial, donde los giros se desvanecen continuamente en función de tres factores.

La fórmula de puntuación:

efectivo = importancia * actualidad * frescura + relevancia_boost

Donde cada término es:

recency = e^(−decay_rate × age_segundos) — los turnos más antiguos tienen menos peso frescura = e^(−0.01 × time_since_last_access) — los turnos a los que se hace referencia recientemente obtienen un impulso relevancia_boost = (|query ∩ turn| / |query|) × 0.35 — los turnos con una alta superposición de token de consulta se retienen por más tiempo

Esto refleja cómo la memoria de trabajo realmente prioriza la información.[4]— los turnos de gran importancia duran más; Los giros fuera de tema se desvanecen rápidamente independientemente de cuándo ocurrieron.

Puntuación de importancia automática

La puntuación de importancia automática hace que esto sea práctico sin anotaciones manuales. El sistema califica cada turno según la longitud del contenido, las palabras clave del dominio y la superposición de consultas:

Turn ContentRoleAuto-Scored Importancia“¿Qué es la ingeniería de contexto y por qué es importante?”usuario2.33“Explique cómo el deterioro de la memoria previene la hinchazón del contexto”. usuario2.50“¿Cuál es el clima en Chennai hoy?”usuario1.10

Una pregunta sobre el clima obtiene una puntuación de 1,10, apenas por encima del suelo. Una pregunta de dominio sobre el deterioro de la memoria obtiene una puntuación de 2,50 y sobrevive mucho más tiempo antes de deteriorarse. En una conversación larga, los dominios de gran importancia permanecen en la memoria, mientras que las conversaciones triviales de baja importancia se desvanecen primero: el orden exacto que desea.

Deduplicación

La deduplicación se ejecuta antes de que se almacene cualquier turno, como una verificación de tres niveles: contención exacta (si el nuevo turno es una subcadena de uno existente, rechazar), fuerte superposición de prefijos (si la primera mitad de ambos turnos coinciden, rechazar) y similitud de superposición de tokens >= 0,72 (si la superposición de tokens es lo suficientemente alta, rechazar como paráfrasis).

En 0,72, puedes captar paráfrasis sin rechazar falsamente preguntas relacionadas pero distintas sobre el mismo tema. Un seguimiento como "¿Puedes explicar la ingeniería de contexto y su papel en RAG?" después de "¿Qué es la ingeniería de contexto y cómo ayuda a los sistemas RAG?" Las puntuaciones se superponen en aproximadamente un 72 %: se activa la deduplicación, se guarda una ranura de memoria y se crea espacio para información genuinamente nueva.

Presupuesto de tokens bajo presión

Asignación de presupuesto de tokens entre turnos en un sistema LLM que muestra indicaciones del sistema, historial de conversaciones, documentos recuperados y compresión dinámica en una canalización RAG.
Cómo se distribuye el presupuesto de tokens entre turnos en un sistema RAG consciente del contexto, equilibrando las indicaciones del sistema, el historial de memoria y los documentos recuperados. Imagen del autor.

Componente 4: Compresión de contexto

Tienes 810 caracteres de contexto recuperado. Su presupuesto de token restante permite 800. Esa brecha de 10 caracteres significa que algo se trunca gravemente o todo se desborda.

El compresor implementa tres estrategias. Truncar es el más rápido: corta cada trozo proporcionalmente. La oración utiliza una selección codiciosa de límites de oración. Extractivo tiene en cuenta las consultas: cada oración de todos los documentos recuperados se califica por superposición simbólica con la consulta, se clasifica por relevancia y se selecciona con avidez dentro del presupuesto. Luego, las oraciones seleccionadas se devuelven en el orden del documento original, no en el orden de clasificación de relevancia.[5]. El orden de clasificación de relevancia produce un contexto incoherente. El orden original preserva el flujo lógico del material original.

Compensaciones de la estrategia de compresión: misma entrada de 810 caracteres, presupuesto de 800 caracteres

EstrategiaTamaño de salidaRelación de compresiónQué optimizaTruncar744 caracteres91,9%VelocidadOración684 caracteres84,4%Limpiar límitesExtractivo762 caracteres94,1%Relevancia

La compresión extractiva conserva mejor el significado, pero guarda menos caracteres sin formato. Con presupuestos ajustados, le ofrece el contenido adecuado, no sólo menos contenido.

Componente 5: El ejecutor del presupuesto simbólico

Todo se alimenta de TokenBudget, un asignador basado en espacios que rastrea el uso en regiones de contexto nombradas. La estimación de tokens utiliza la heurística 1 token ≈ 4 caracteres para la prosa en inglés, de acuerdo con la documentación de OpenAI.[6].

El orden de reserva es el diseño completo:

def build(self, consulta: str) -> ContextPacket: presupuesto = TokenBudget(total=self.total_token_budget) presupuesto.reserve_text("system_prompt", self.system_prompt) # 1. Se corrigió scoring_docs = self._rerank(self._retriever.retrieve(query, …), query) memoria_turns = self._memory.get_weighted(query=query) presupuesto.reserve_text("historia", " ".join(t.content para t en memoria_turns)) # 2. Reservado restantes_chars = presupuesto.remaining_chars() compresor = Compresor(max_chars=remaining_chars, estrategia=self.compression_strategy) resultado = compresor.compress([sd.document.content para sd en scoring_docs], consulta=consulta) Budget.reserve_text("retrived_docs", result.text) # 3. Lo que queda return ContextPacket(…)

El mensaje del sistema es fijo y no se puede negociar. La memoria es lo que hace que los múltiples turnos sean coherentes. Los documentos son la variable: útiles, pero lo primero que hay que comprimir cuando se acaba el espacio. Si se reserva en el orden equivocado, los documentos desbordan silenciosamente el presupuesto antes de que se tenga en cuenta la historia. El orquestador impone explícitamente el orden correcto.

Qué sucede bajo la presión real de los tokens

Aquí es donde fallan los sistemas ingenuos, y este motor se adapta.

Configuración: 5 documentos (810 caracteres en total), 200 tokens reservados para el aviso del sistema, presupuesto total de 800 tokens. Consulta: "¿Cómo se comparan las incrustaciones y TF-IDF en cuanto a memoria en los agentes?"

Turno 1: aún no hay historial de conversaciones: Documentos recuperados: 5, reclasificados. Giros de memoria: 0. Compresión aplicada: 48% de reducción. Resultado: se ajusta al presupuesto.

Turno 2: después de que comienza la conversación: Documentos recuperados: 5, reclasificados. Turnos de memoria: 2, ahora compitiendo por el espacio. La compresión se vuelve más agresiva: reducción del 45%. Resultado: todavía se ajusta al presupuesto.

¿Qué cambió? El sistema no falló: se adaptó. La memoria consume parte del presupuesto, por lo que la compresión de los documentos recuperados se ajusta automáticamente. Ése es el objetivo de la ingeniería contextual: el modelo siempre recibe algo coherente, nunca un desbordamiento aleatorio.

Medir lo que realmente te compra

La siguiente tabla compara cuatro enfoques en la misma consulta y un presupuesto total de 800 tokens. Las primeras tres filas se calculan a partir de entradas conocidas utilizando el mismo conjunto de documentos de 810 caracteres; la cuarta fila refleja la potencia real del motor verificada en ejecuciones de demostración.

ApproachDocs recuperados después de la compresión ¿La memoria se ajusta al presupuesto? Naive RAG5 (completo) 810 caracteres, ninguno Ninguno No – 10 caracteres sobreRAG + Truncar 5360 caracteres (43 %) Ninguno Sí – pero se perdió el contenido finalRAG + Memoria (sin deterioro) 5 (completo) 810 caracteres 3 vueltas, sin filtrar No – el historial lo supera Motor de contexto completo 5, reclasificado 400 caracteres (50%) 2 turnos, filtrado de desintegración Sí, se cumplieron todas las restricciones

El ingenuo RAG se desborda inmediatamente. El truncamiento encaja pero corta a ciegas la cola. La memoria sin decadencia añade ruido en lugar de señal: los giros más antiguos nunca se desvanecen y el historial de conversaciones se vuelve inflado. El sistema completo reclasifica, comprime de forma inteligente e incluye solo giros que aún contienen información.

Deterioro de la memoria por puntuación de importancia

Gráfico de deterioro de la memoria que muestra la puntuación efectiva a lo largo del tiempo con decay_rate 0,001 y umbral min_importance 0,1. Tres curvas de decaimiento trazadas a lo largo de 24 horas: importancia de la curva verde 2,50 explicación de inflación del contexto, importancia de la curva azul 2,33 consulta de ingeniería de contexto, importancia de la curva ámbar 1,10 consulta meteorológica disminuyó a las 12 horas. Anotación de aumento de relevancia en la curva azul a las 6 horas.
La puntuación efectiva disminuye a lo largo de 24 horas: los turnos de ingeniería de contexto de alta importancia sobreviven a la ventana de sesión completa, mientras que los turnos de baja importancia, como las consultas meteorológicas, caen por debajo del umbral de 0,1 a las 12 horas aproximadamente y se eliminan. El aumento de relevancia debido a la superposición de tokens de consulta puede revivir temporalmente los turnos antiguos.

Características de rendimiento

Medido en Python 3.12.6, solo CPU, sin GPU, base de conocimientos de 5 documentos:

OperaciónLatenciaNotasRecuperación de palabras clave~0,8msCoincidencia de token simpleRecuperación TF-IDF~2,1msVectorización + similitud de cosenoRecuperación híbrida~85msLa generación de incrustación dominaReclasificación (5 documentos)~0,3msPuntuación ponderada por etiquetasDecaimiento de memoria + filtrado~0,6msCálculo de decaimiento exponencialCompresión (extractivo)~4.2msPuntuación de oraciones + selecciónMotor completo.build()~92msDomina el modo híbrido

La recuperación híbrida es el cuello de botella. Si necesita un tiempo de respuesta inferior a 50 ms, utilice TF-IDF o el modo de palabra clave. A 100 solicitudes/segundo en modo híbrido, necesita aproximadamente 9 trabajadores simultáneos; Con el almacenamiento en caché integrado, las consultas posteriores se reducen a ~2 ms por solicitud después de la primera.

Decisiones de diseño honestas

alfa=0,65 es empírico, no basado en principios. Probé con un pequeño conjunto de consultas de mi base de conocimientos. Para un dominio diferente (documentos legales, literatura médica, código denso) el alfa correcto será diferente. Las consultas con muchas palabras clave obtienen mejores resultados alrededor de 0,4; Las consultas conceptuales o parafraseadas se benefician de 0,8 o superior.

Los pesos de reclasificación (0,68/0,32) son una heurística. Un reclasificador de codificador cruzado tendría más principios[7]pero cuesta una llamada modelo por documento. Para 5 documentos, la heurística se ejecuta en microsegundos. Para más de 500 documentos, vale la pena el costo de un codificador cruzado.

La estimación del token (1 token ≈ 4 caracteres) es una aproximación. Dentro de ~15% del recuento real de tokens para la prosa en inglés[6], pero falla en códigos y escrituras no latinas. Para producción, canje en tiktoken.[8]- es un cambio de una línea en compresor.py.

El compresor extractivo puntúa según la superposición de recuperación de tokens de consulta: cuántos tokens de consulta aparecen en la oración, como una fracción de la longitud de la consulta. Esto es rápido y libre de dependencias, pero pierde la similitud semántica: una oración que parafrasea la consulta sin compartir ningún token obtiene una puntuación de cero. La puntuación de oraciones basada en incrustación solucionaría este problema a costa de una llamada de modelo adicional por paso de compresión.

Compensaciones y lo que falta

Reclasificación del codificador cruzado. La interfaz _rerank() ya está diseñada para ser intercambiada. Introduzca un codificador cruzado basado en BERT para obtener clasificaciones por pares significativamente mejores.

Compresión basada en incrustación. Reemplace el marcador de oraciones de superposición de tokens en _extractive() con un pequeño modelo de incrustación. Capta la relevancia semántica que la superposición de palabras clave pasa por alto. Probablemente valga la pena para más de 100 sistemas de documentos.

Alfa adaptativo. Clasifique el tipo de consulta dinámicamente y ajuste alfa en lugar de utilizar un 0,65 fijo. Una consulta breve con términos de dominio poco comunes probablemente requiera más peso TF-IDF; una pregunta larga en lenguaje natural necesita más peso.

Memoria persistente. La clase de memoria actual solo está en proceso. Un backend SQLite liviano con la misma interfaz add() / get_weighted() sobreviviría a los reinicios y permitiría la continuidad entre sesiones.

Cierre

RAG le consigue los documentos correctos. La ingeniería rápida le brinda las instrucciones correctas. La ingeniería de contexto le proporciona el contexto correcto.

La ingeniería inmediata decide cómo piensa el modelo. La ingeniería de contexto decide en qué debe pensar.

La mayoría de los sistemas optimizan lo primero e ignoran lo segundo. Por eso se rompen.

El código fuente completo con las siete demostraciones se encuentra en: https://github.com/Emmimal/context-engine/

Referencias

[1]Lewis, P., Pérez, E., et al. (2020). Generación de recuperación aumentada para tareas de PNL intensivas en conocimiento. NeurIPS 33, 9459–9474. https://arxiv.org/abs/2005.11401

[2]Karpatía, A. (2025). Ingeniería de Contexto. https://x.com/karpathy/status/1937902205765607626

[3]Pedregosa, F., et al. (2011). Scikit-learn: aprendizaje automático en Python. JMLR 12, 2825–2830. https://jmlr.org/papers/v12/pedregosa11a.html

[4]Baddeley, A. (2000). El buffer episódico: ¿un nuevo componente de la memoria de trabajo? Tendencias en ciencias cognitivas, 4(11), 417–423.

[5]Mihalcea, R. y Tarau, P. (2004). TextRank: poner orden en los textos. EMNLP 2004. https://aclanthology.org/W04-3252/

[6]OpenAI. (2023). Contando fichas con tiktoken. https://github.com/openai/tiktoken

[7]Nogueira, R. y Cho, K. (2019). Pasaje Reclasificación con BERT. arXiv:1901.04085. https://arxiv.org/abs/1901.04085

[8]OpenAI. (2023). tiktoken: tokenizador BPE rápido para usar con modelos de OpenAI. https://github.com/openai/tiktoken

[9]Reimers, N. y Gurevych, I. (2019). Sentence-BERT: incrustaciones de oraciones utilizando redes BERT siamesas. EMNLP 2019. https://arxiv.org/abs/1908.10084

Divulgación

Todo el código de este artículo fue escrito por mí y es un trabajo original, desarrollado y probado en Python 3.12.6. Los números de referencia provienen de ejecuciones de demostración reales en mi máquina local (Windows 11, solo CPU) y se pueden reproducir clonando el repositorio y ejecutando demo.py, excepto cuando el artículo indique explícitamente que los números se calculan a partir de entradas conocidas. La biblioteca de transformadores de oraciones se utiliza como una dependencia opcional para la generación de incrustaciones en modo de recuperación híbrido. Todas las demás funciones se ejecutan en la biblioteca estándar de Python y solo en numpy. No tengo ninguna relación financiera con ninguna herramienta, biblioteca o empresa mencionada en este artículo.