Compresión de Contexto en RAG: Optimizando Tokens sin Perder Calidad
Exploramos tres estrategias de compresión de contexto para sistemas RAG, comparando eficiencia, calidad y costo en tokens. Implementación práctica con Python, Qdrant y Jan AI.
Introducción: El Problema del Contexto en RAG
Los sistemas de RAG (Retrieval Augmented Generation) han revolucionado la forma en que los modelos de lenguaje acceden a información externa. Sin embargo, un desafío crítico surge cuando recuperamos demasiada información de la base de datos vectorial: el consumo excesivo de tokens.
En este artículo, exploramos la compresión de contexto como solución a este problema, implementando y comparando tres estrategias diferentes: Summarize (Abstractive), Extractive Top-K, y Rerank + Truncate.
¿Por Qué Necesitamos Comprimir el Contexto?
El Problema Fundamental
En un sistema RAG típico, el flujo es el siguiente:
graph LR
A[Consulta del Usuario] --> B[Embedding de Consulta]
B --> C[Búsqueda Vectorial<br/>en Base de Datos]
C --> D[Recuperación de Chunks<br/>Relevantes]
D --> E[Contexto Completo<br/>2000-5000 tokens]
E --> F[LLM Genera Respuesta]
style E fill:#ffcccc
style F fill:#ccffcc
El problema: Cuando recuperamos múltiples chunks de documentos (por ejemplo, 5 chunks de 500 palabras cada uno), podemos fácilmente acumular 2,000-5,000 tokens de contexto. Esto tiene varios problemas:
- Costo Económico: Los modelos de lenguaje cobran por token. Más tokens = mayor costo.
- Límites de Contexto: Muchos modelos tienen límites de ventana de contexto (4K, 8K, 32K tokens).
- Ruido Semántico: Información irrelevante puede confundir al modelo.
- Latencia: Procesar más tokens requiere más tiempo de inferencia.
La Solución: Compresión de Contexto
La compresión de contexto es el proceso de reducir la cantidad de tokens del contexto recuperado manteniendo la información más relevante para responder la consulta. No es simplemente truncar texto, sino inteligentemente seleccionar o resumir lo esencial.
graph LR
A[Contexto Original<br/>5000 tokens] --> B[Compresor]
B --> C1[Summarize<br/>800 tokens]
B --> C2[Extractive Top-K<br/>500 tokens]
B --> C3[Rerank + Truncate<br/>800 tokens]
C1 --> D[LLM Genera Respuesta]
C2 --> D
C3 --> D
style A fill:#ffcccc
style C1 fill:#ccffcc
style C2 fill:#ccffcc
style C3 fill:#ccffcc
Arquitectura del Sistema Implementado
Antes de profundizar en las estrategias, veamos la arquitectura completa del sistema que implementamos:
graph TB
subgraph "Ingesta de Documentos"
PDF[PDFs] --> LOADER[PDF Loader<br/>Extracción de Texto]
LOADER --> CHUNKER[Chunker<br/>División en Chunks]
CHUNKER --> EMB1[Embedder<br/>BAAI/bge-m3]
EMB1 --> QDRANT[(Qdrant<br/>Vector DB)]
end
subgraph "Sistema RAG con Compresión"
QUERY[Consulta Usuario] --> EMB2[Embedder<br/>Genera Query Vector]
EMB2 --> QDRANT
QDRANT --> RETRIEVE[Recuperación<br/>Top-K Chunks]
RETRIEVE --> COMP[Context Compressor<br/>3 Estrategias]
COMP --> S1[Summarize]
COMP --> S2[Extractive Top-K]
COMP --> S3[Rerank + Truncate]
S1 --> LLM[Jan AI<br/>Genera Respuestas]
S2 --> LLM
S3 --> LLM
end
LLM --> OUTPUT[Respuestas Comparadas<br/>+ Métricas de Tokens]
style QDRANT fill:#e1f5ff
style COMP fill:#fff9c4
style LLM fill:#fff4e1
Componentes clave:
- Qdrant: Base de datos vectorial para almacenar embeddings
- BAAI/bge-m3: Modelo de embeddings de alta calidad (1024 dimensiones)
- Jan AI: Servidor local de modelos de lenguaje
- Compresor de Contexto: Módulo que implementa las 3 estrategias
Estrategia 1: Summarize (Compresión Abstractiva)
Fundamentación Teórica
La compresión abstractiva utiliza un modelo de lenguaje para generar un resumen nuevo del contexto, reescribiendo y condensando la información. Esta es una estrategia generativa que crea texto nuevo, no solo selecciona texto existente.
¿Por Qué Funciona?
- Compresión Alta: Puede reducir contextos de 5000 tokens a 800 tokens manteniendo información clave.
- Coherencia: El resumen es coherente y fluido, no fragmentos desconectados.
- Enfoque en la Consulta: El prompt guía al modelo a enfocarse en información relevante para responder.
Implementación
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
def compress_context(self, query: str, context: str) -> tuple[str, int]:
"""Comprime el contexto usando Jan AI (resumen guiado por la consulta)"""
system_prompt = (
"Eres un compresor de contexto. Dado el contexto y la consulta, "
"devuelve un resumen conciso con la información más relevante "
"para responder la consulta. "
f"Limita el resumen objetivo a ~{self.compressor_target_tokens} tokens. "
"Mantén citas de fuente (archivo y página) si se incluyen entre corchetes."
)
user_prompt = (
f"Consulta: {query}\n\n"
f"Contexto:\n{context}\n\n"
"Resumen (conciso y específico a la consulta):"
)
# Llamada a Jan AI para generar resumen
response = requests.post(
f"{self.jan_ai_url}/v1/chat/completions",
json={
"model": self.jan_ai_chat_model,
"messages": [
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt},
],
"temperature": 0.2, # Baja temperatura para resúmenes consistentes
}
)
summary = response.json()["choices"][0]["message"]["content"]
return summary, self.count_tokens(summary)
Flujo del Proceso
sequenceDiagram
participant RAG as Sistema RAG
participant QD as Qdrant
participant COMP as Compresor Summarize
participant LLM as Jan AI
participant USER as Usuario
USER->>RAG: Pregunta
RAG->>QD: Búsqueda vectorial
QD-->>RAG: Contexto (5000 tokens)
RAG->>COMP: Contexto + Consulta
COMP->>LLM: Generar resumen guiado
LLM-->>COMP: Resumen (800 tokens)
COMP-->>RAG: Contexto comprimido
RAG->>LLM: Prompt con contexto comprimido
LLM-->>RAG: Respuesta final
RAG-->>USER: Respuesta + Métricas
Ventajas y Desventajas
Ventajas:
- ✅ Alta compresión: Reducción de 70-85% de tokens
- ✅ Coherencia: Texto fluido y coherente
- ✅ Adaptabilidad: El modelo decide qué información es más relevante
- ✅ Preserva relaciones: Mantiene conexiones semánticas entre conceptos
Desventajas:
- ❌ Costo adicional: Requiere una llamada extra al LLM
- ❌ Latencia: Más lento que estrategias extractivas
- ❌ Pérdida de detalles: Puede omitir información específica importante
- ❌ Alucinaciones: Riesgo de introducir información no presente en el original
Cuándo Usar
Ideal para:
- Contextos muy largos (>3000 tokens)
- Cuando la coherencia es más importante que preservar texto exacto
- Aplicaciones donde el costo de tokens es crítico
- Consultas que requieren síntesis de múltiples conceptos
Estrategia 2: Extractive Top-K
Fundamentación Teórica
La compresión extractiva selecciona las oraciones más relevantes del contexto original sin modificarlas. Utiliza similitud semántica (embeddings) para rankear oraciones según su relevancia a la consulta.
¿Por Qué Funciona?
- Velocidad: No requiere llamadas a LLM, solo cálculos de similitud coseno.
- Preservación: Mantiene el texto original sin modificaciones.
- Determinismo: Siempre produce el mismo resultado para la misma entrada.
- Transparencia: El usuario puede ver exactamente qué texto se usó.
Implementación
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
def compress_extractive_topk(self, query: str, context: str) -> tuple[str, int]:
"""Selecciona las K oraciones más relevantes usando similitud coseno"""
# 1. Dividir contexto en oraciones
sentences = self._split_sentences(context)
# 2. Generar embeddings de consulta y oraciones
query_vec = self.embedder.embed_documents([query])[0]
sent_vecs = self.embedder.embed_documents(sentences)
# 3. Calcular similitud coseno (embeddings ya están normalizados)
import numpy as np
q = np.array(query_vec)
similarities = [
float(np.dot(q, np.array(v)))
for v in sent_vecs
]
# 4. Rankear y seleccionar Top-K
ranked = sorted(
zip(sentences, similarities),
key=lambda x: x[1],
reverse=True
)
top_sentences = [sentence for sentence, _ in ranked[:self.compressor_topk]]
# 5. Concatenar oraciones seleccionadas
summary = "\n".join(top_sentences)
return summary, self.count_tokens(summary)
Flujo del Proceso
graph TD
A[Contexto Original<br/>50 oraciones] --> B[Split en Oraciones]
B --> C[Generar Embeddings<br/>Query + Oraciones]
C --> D[Calcular Similitud Coseno]
D --> E[Ranking por Similitud]
E --> F[Top-K Oraciones<br/>K=5]
F --> G[Contexto Comprimido<br/>5 oraciones]
style D fill:#e1bee7
style F fill:#c8e6c9
Análisis Matemático
La similitud coseno entre dos vectores normalizados se calcula como:
\[\text{similitud}(q, s) = \frac{q \cdot s}{||q|| \cdot ||s||} = q \cdot s\]Donde:
- $q$: embedding de la consulta (normalizado)
- $s$: embedding de la oración (normalizado)
- Como ambos están normalizados, el producto punto es igual a la similitud coseno
Complejidad: O(n × d) donde n es el número de oraciones y d es la dimensión del embedding (1024 para bge-m3).
Ventajas y Desventajas
Ventajas:
- ✅ Muy rápido: Solo requiere cálculos de similitud vectorial
- ✅ Preserva texto original: No hay riesgo de alucinaciones
- ✅ Determinístico: Resultados reproducibles
- ✅ Bajo costo: No requiere llamadas adicionales a LLM
Desventajas:
- ❌ Pérdida de contexto: Puede romper el flujo entre oraciones
- ❌ Fragmentación: Las oraciones pueden estar desconectadas
- ❌ Sin reescritura: No puede condensar información
- ❌ Dependencia de embeddings: La calidad depende de la calidad del modelo de embeddings
Cuándo Usar
Ideal para:
- Aplicaciones donde la velocidad es crítica
- Cuando necesitas preservar texto exacto (citas, números, fechas)
- Sistemas con presupuesto limitado de API
- Contextos donde la información está bien distribuida en oraciones individuales
Estrategia 3: Rerank + Truncate
Fundamentación Teórica
Esta estrategia combina re-ranking de chunks por relevancia con truncamiento inteligente hasta un presupuesto de tokens. A diferencia de Extractive Top-K que trabaja a nivel de oraciones, Rerank + Truncate opera a nivel de chunks completos.
¿Por Qué Funciona?
- Preserva chunks completos: Mantiene la integridad de cada chunk
- Control preciso: Respeta exactamente el presupuesto de tokens
- Re-ranking: Los chunks más relevantes se priorizan
- Balance: Combina velocidad (extractiva) con control (presupuesto)
Implementación
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
def compress_rerank_truncate(
self,
query: str,
docs: List[Dict[str, Any]]
) -> tuple[str, int]:
"""Reordena chunks por relevancia y concatena hasta presupuesto"""
# 1. Generar embedding de consulta
query_vec = self.embedder.embed_documents([query])[0]
# 2. Generar embeddings de cada chunk
texts = [doc.get("text", "") for doc in docs]
chunk_vecs = self.embedder.embed_documents(texts)
# 3. Calcular similitud y rankear
import numpy as np
q = np.array(query_vec)
similarities = [
float(np.dot(q, np.array(v)))
for v in chunk_vecs
]
ranked_indices = sorted(
range(len(texts)),
key=lambda i: similarities[i],
reverse=True
)
# 4. Concatena chunks hasta presupuesto de tokens
budget = self.rerank_budget_tokens
selected_chunks = []
used_tokens = 0
for idx in ranked_indices:
chunk = texts[idx]
chunk_tokens = self.count_tokens(chunk)
if used_tokens + chunk_tokens > budget:
break # No cabe más
selected_chunks.append(chunk)
used_tokens += chunk_tokens
summary = "\n\n".join(selected_chunks)
return summary, self.count_tokens(summary)
Flujo del Proceso
graph TD
A[Chunks de Qdrant<br/>5 chunks, 500 tokens c/u] --> B[Generar Embeddings<br/>Query + Chunks]
B --> C[Calcular Similitud<br/>Coseno]
C --> D[Ordenar por Relevancia]
D --> E[Concatena hasta Budget<br/>800 tokens]
E --> F[Chunk 1: 300 tokens]
E --> G[Chunk 3: 250 tokens]
E --> H[Chunk 2: 250 tokens]
F --> I[Contexto Final<br/>800 tokens]
G --> I
H --> I
style D fill:#ffccbc
style E fill:#c8e6c9
Ventajas y Desventajas
Ventajas:
- ✅ Preserva integridad: Mantiene chunks completos
- ✅ Control preciso: Respeta exactamente el presupuesto
- ✅ Balance: Velocidad razonable con buena compresión
- ✅ Flexibilidad: Fácil ajustar el presupuesto según necesidades
Desventajas:
- ❌ Cortes abruptos: Puede cortar chunks importantes
- ❌ Sin reordenamiento interno: No reordena dentro de chunks
- ❌ Dependencia de chunking: La calidad depende de cómo se dividieron los documentos
Cuándo Usar
Ideal para:
- Cuando necesitas control preciso de tokens
- Aplicaciones donde los chunks son unidades semánticas coherentes
- Sistemas que requieren balance entre velocidad y calidad
- Cuando el presupuesto de tokens es un hard constraint
Comparación de Estrategias
Métricas de Comparación
| Estrategia | Reducción Tokens | Velocidad | Coherencia | Preserva Texto | Costo LLM |
|---|---|---|---|---|---|
| Directo BD | 0% (baseline) | ⚡⚡⚡ | ✅✅✅ | ✅✅✅ | Bajo |
| Summarize | 70-85% | ⚡ | ✅✅ | ❌ | Alto (2 llamadas) |
| Extractive Top-K | 60-80% | ⚡⚡⚡ | ✅ | ✅✅✅ | Bajo |
| Rerank + Truncate | 50-70% | ⚡⚡ | ✅✅ | ✅✅ | Bajo |
Visualización Comparativa
graph LR
subgraph "Contexto Original"
A[5000 tokens<br/>Información Completa]
end
subgraph "Estrategias de Compresión"
B[Summarize<br/>800 tokens<br/>85% reducción]
C[Extractive Top-K<br/>500 tokens<br/>90% reducción]
D[Rerank + Truncate<br/>800 tokens<br/>84% reducción]
end
subgraph "Calidad de Respuesta"
E[Alta Coherencia<br/>Baja Preservación]
F[Media Coherencia<br/>Alta Preservación]
G[Alta Coherencia<br/>Media Preservación]
end
A --> B
A --> C
A --> D
B --> E
C --> F
D --> G
style A fill:#ffcccc
style B fill:#c8e6c9
style C fill:#c8e6c9
style D fill:#c8e6c9
Resultados Empíricos
En nuestro sistema de prueba con documentos de medicina del trabajo, observamos:
Ejemplo Real: Consulta sobre RCP (Reanimación Cardio Pulmonar)
| Métrica | Directo BD | Summarize | Extractive Top-K | Rerank + Truncate |
|---|---|---|---|---|
| Tokens Contexto | 2,788 | 668 | 52 | 637 |
| Reducción | - | 76% | 98% | 77% |
| Tiempo Procesamiento | 2.3s | 8.5s | 0.8s | 1.2s |
| Calidad Respuesta | Excelente | Muy Buena | Buena | Muy Buena |
| Preservación Detalles | 100% | ~70% | ~95% | ~85% |
Observaciones clave:
- Extractive Top-K logró la mayor reducción (98%) pero con menor coherencia
- Summarize mantuvo mejor calidad semántica pero requiere más tiempo
- Rerank + Truncate ofreció el mejor balance costo-beneficio
La Importancia de la Compresión de Contexto en RAG
Impacto en Costos
Consideremos un sistema RAG que procesa 1000 consultas por día:
Sin Compresión:
- Promedio: 3000 tokens por contexto
- Costo: $0.03 por consulta (modelo GPT-4)
- Costo diario: $30
Con Compresión (Summarize - 80% reducción):
- Promedio: 600 tokens por contexto
- Costo: $0.006 por consulta
- Costo diario: $6
Ahorro: $24/día = $8,760/año 🎉
Impacto en Rendimiento
graph TD
A[Consulta Usuario] --> B{Contexto > 4000 tokens?}
B -->|Sí| C[Error: Context Window Exceeded]
B -->|No| D[LLM Procesa]
E[Consulta Usuario] --> F[Compresor]
F --> G[Contexto < 1000 tokens]
G --> H[LLM Procesa Rápido]
style C fill:#ffcccc
style H fill:#ccffcc
Impacto en Calidad
La compresión inteligente puede mejorar la calidad de las respuestas:
- Reduce Ruido: Elimina información irrelevante que puede confundir al modelo
- Enfoque: El modelo se concentra en información más relevante
- Coherencia: Contextos más cortos y coherentes producen respuestas más precisas
Casos de Uso Reales
1. Sistemas de Soporte Técnico
Problema: Base de conocimiento con 10,000+ documentos, cada consulta recupera múltiples artículos.
Solución: Usar Rerank + Truncate para mantener chunks completos de documentación técnica mientras respeta límites de tokens.
2. Asistentes de Investigación
Problema: Recuperación de múltiples papers científicos (contextos de 10,000+ tokens).
Solución: Usar Summarize para crear resúmenes coherentes que sinteticen información de múltiples fuentes.
3. Chatbots Empresariales
Problema: Políticas y procedimientos extensos, necesidad de velocidad y bajo costo.
Solución: Usar Extractive Top-K para selección rápida de secciones relevantes sin llamadas adicionales a LLM.
Implementación Práctica: Arquitectura del Sistema
Arquitectura Completa
graph TB
subgraph "Capa de Datos"
PDF[Documentos PDF]
QD[(Qdrant<br/>Vector Store)]
end
subgraph "Capa de Procesamiento"
ING[Ingester<br/>PDF → Chunks → Embeddings]
EMB[Embedder<br/>BAAI/bge-m3]
end
subgraph "Capa de RAG"
RET[Retriever<br/>Vector Search]
COMP[Context Compressor]
LLM[Jan AI<br/>Chat Model]
end
subgraph "Estrategias de Compresión"
S1[Summarize]
S2[Extractive Top-K]
S3[Rerank + Truncate]
end
PDF --> ING
ING --> EMB
EMB --> QD
RET --> QD
RET --> COMP
COMP --> S1
COMP --> S2
COMP --> S3
S1 --> LLM
S2 --> LLM
S3 --> LLM
LLM --> OUTPUT[Respuestas Comparadas]
style COMP fill:#fff9c4
style LLM fill:#fff4e1
Tecnologías Utilizadas
- Qdrant: Base de datos vectorial de alto rendimiento
- BAAI/bge-m3: Modelo de embeddings multilingüe (1024 dimensiones)
- Jan AI: Servidor local de modelos de lenguaje
- Python: FastAPI para API REST, Rich para UI terminal
- Docker: Contenedorización completa del sistema
Conclusiones y Recomendaciones
Conclusiones Principales
La compresión de contexto es esencial en sistemas RAG para controlar costos y mantener calidad.
No hay una estrategia universal: Cada estrategia tiene sus fortalezas según el caso de uso.
El balance es clave: Encontrar el equilibrio entre reducción de tokens, velocidad y calidad.
La comparación es valiosa: Implementar múltiples estrategias permite elegir la mejor según contexto.
Recomendaciones de Uso
Para aplicaciones de alta velocidad y bajo costo: → Usa Extractive Top-K
Para aplicaciones que requieren máxima compresión: → Usa Summarize
Para aplicaciones que necesitan balance y control preciso: → Usa Rerank + Truncate
Para comparación y optimización: → Implementa las tres y mide métricas en tu dominio específico
Próximos Pasos
- Implementar métricas de calidad: BLEU, ROUGE, o evaluación humana
- A/B Testing: Comparar respuestas comprimidas vs. directas
- Estrategias híbridas: Combinar múltiples estrategias
- Compresión adaptativa: Seleccionar estrategia según características del contexto
Referencias y Recursos
Código del proyecto: Disponible en GitHub Repository
¿Has implementado compresión de contexto en tus sistemas RAG? Comparte tus experiencias en los comentarios.