C
Contextología
RAG

Cómo mejorar la precisión de un sistema RAG: reranking, hybrid search y query expansion

22 de mayo de 2026· 6 min read

Un sistema RAG básico recupera los K fragmentos más similares semánticamente a la consulta. Funciona bien para preguntas directas, pero falla en casos más complejos: términos técnicos específicos, consultas multi-hop, preguntas ambiguas o documentos con terminología muy especializada.

Estas son las técnicas que se aplican cuando el RAG básico no es suficientemente preciso.

Por qué falla el retrieval básico

Antes de aplicar técnicas avanzadas, entiende por qué falla el tuyo. Los tres problemas más comunes:

1. El embedding no captura el concepto correcto Las consultas en lenguaje natural y los fragmentos técnicos tienen representaciones vectoriales distintas aunque hablen de lo mismo. "¿Cómo reinicio el servidor?" y "Procedimiento de restart del daemon" son semánticamente equivalentes pero los embeddings los colocan lejos.

2. Los términos exactos no se recuperan bien La búsqueda semántica es mala con identificadores exactos: números de modelo, nombres propios, códigos de error, términos de dominio muy específicos. "Error 0x800704CF" con búsqueda semántica pura a menudo falla.

3. La consulta es ambigua o multi-aspecto "Cuéntame sobre la política de privacidad" puede referirse a muchos fragmentos del documento. Los primeros K resultados pueden ser redundantes entre sí en lugar de complementarios.

Técnica 1: Búsqueda híbrida (Hybrid Search)

Combina búsqueda vectorial (semántica) con búsqueda por keywords (BM25 / TF-IDF). Los resultados se fusionan con Reciprocal Rank Fusion (RRF).

# Qdrant tiene soporte nativo para búsqueda híbrida
from qdrant_client import QdrantClient
from qdrant_client.models import Prefetch, FusionQuery, Fusion

client = QdrantClient(...)

results = client.query_points(
    collection_name="documents",
    prefetch=[
        Prefetch(query=dense_embedding, using="dense", limit=20),  # vectorial
        Prefetch(query=sparse_embedding, using="sparse", limit=20),  # BM25
    ],
    query=FusionQuery(fusion=Fusion.RRF),  # fusión por rank
    limit=5,
)

Cuándo aplica: Siempre. La búsqueda híbrida mejora el recall en casi todos los casos y es especialmente crucial cuando los documentos contienen terminología técnica, nombres propios o identificadores específicos.

Impacto típico: +15-30% de mejora en recall para consultas con términos técnicos específicos.

Técnica 2: Reranking

El reranker es un segundo modelo que toma los K resultados del retrieval inicial y los reordena según su relevancia real para la consulta. Es más costoso computacionalmente que la búsqueda vectorial pero mucho más preciso.

import cohere

co = cohere.Client("...")

# Paso 1: retrieval inicial (top 20)
initial_results = vector_store.search(query, top_k=20)

# Paso 2: reranking (quedarse con top 5)
reranked = co.rerank(
    model="rerank-v3.5",
    query=query,
    documents=[r.content for r in initial_results],
    top_n=5,
)

final_results = [initial_results[r.index] for r in reranked.results]

El flujo correcto: Recupera más resultados de los que vas a usar (20-50), luego el reranker selecciona los mejores (3-5). No recuperes directamente 5 y reordenes — pierdes recall.

Opciones de reranker:

  • Cohere Rerank v3 — el estándar. Fácil integración, excelente calidad
  • BGE Reranker Large — open source, rendimiento similar a Cohere, auto-hosteable
  • Jina Reranker — buena calidad para múltiples idiomas incluyendo español

Impacto típico: +20-40% de mejora en precisión. Es la técnica individual con mayor impacto.

Técnica 3: Query expansion

Genera múltiples variantes de la consulta y recupera resultados para cada una. Aumenta el recall capturando fragmentos que son relevantes pero usan terminología diferente.

async def expanded_retrieval(query: str, k: int = 5) -> list:
    # Genera variantes de la consulta
    variants_prompt = f"""Genera 3 variantes de esta consulta para mejorar la búsqueda.
    Usa sinónimos, reformulaciones y términos relacionados.
    
    Consulta: {query}
    
    Devuelve solo las 3 variantes, una por línea."""
    
    variants_response = await llm.generate(variants_prompt)
    variants = [query] + variants_response.strip().split("\n")[:3]
    
    # Recupera resultados para cada variante
    all_results = []
    for variant in variants:
        results = await vector_store.search(variant, k=k)
        all_results.extend(results)
    
    # Deduplica y reordena
    return deduplicate_and_rerank(all_results, query)

Cuándo aplica: Cuando los usuarios consultan con vocabulario diferente al de los documentos. Muy útil para bases de conocimiento con jerga técnica consultada en lenguaje natural.

Técnica 4: HyDE (Hypothetical Document Embeddings)

En lugar de buscar por el embedding de la consulta, genera un documento hipotético que respondería a la consulta y busca por su embedding. Los documentos hipotéticos se parecen más a los documentos reales que las consultas de usuario.

async def hyde_retrieval(query: str, k: int = 5) -> list:
    # Genera documento hipotético
    hyde_prompt = f"""Escribe un párrafo que respondería directamente a esta pregunta.
    Usa el estilo y terminología típica de documentación técnica.
    
    Pregunta: {query}"""
    
    hypothetical_doc = await llm.generate(hyde_prompt)
    
    # Busca usando el embedding del documento hipotético
    hypothetical_embedding = embedder.embed(hypothetical_doc)
    results = vector_store.search_by_vector(hypothetical_embedding, k=k)
    
    return results

Cuándo aplica: Cuando las consultas son muy cortas o usan lenguaje muy diferente al de los documentos. HyDE es especialmente útil para documentación técnica consultada por usuarios no técnicos.

Limitación: Añade una llamada extra al LLM por consulta. Evalúa si el coste adicional justifica la mejora.

Técnica 5: Filtrado por metadatos

Antes de la búsqueda semántica, filtra por metadatos estructurados para reducir el espacio de búsqueda. Más rápido y más preciso que buscar en toda la colección.

# Extraer entidades de la consulta
metadata_filter = extract_metadata_filter(query)
# Ejemplo: query="política de devoluciones de electrodomésticos"
# → filter: {category: "electrodomésticos", doc_type: "política"}

results = vector_store.search(
    query=query,
    filter=metadata_filter,
    top_k=10,
)

Claves para que funcione:

  • Los metadatos tienen que estar bien estructurados durante el chunking
  • El extractor de filtros necesita ser fiable (o hacerlo manualmente en la UI)
  • Los filtros deben ser lo suficientemente específicos para reducir el espacio pero no tanto como para excluir resultados relevantes

Técnica 6: Multi-query retrieval

En lugar de una sola búsqueda, descompone la consulta en sub-preguntas y recupera resultados para cada una. Especialmente útil para preguntas multi-aspecto.

async def multi_query_retrieval(query: str) -> list:
    # Descompone la consulta
    decompose_prompt = f"""Descompón esta consulta en 2-3 preguntas más específicas.
    
    Consulta: {query}"""
    
    sub_queries = await llm.generate(decompose_prompt)
    
    # Recupera para cada sub-pregunta
    all_results = []
    for sub_q in sub_queries:
        results = await vector_store.search(sub_q, k=5)
        all_results.extend(results)
    
    return deduplicate(all_results)

Cómo combinar las técnicas

No necesitas todas a la vez. El orden de adopción según impacto/coste:

Nivel 1 (implementar siempre):

  • Búsqueda híbrida — alto impacto, bajo coste adicional
  • Filtrado por metadatos — requiere work up-front en chunking pero mejora mucho la precisión

Nivel 2 (si el nivel 1 no es suficiente):

  • Reranking — gran impacto, coste moderado por llamada extra
  • Parent-child chunking — mejora contexto sin coste en runtime

Nivel 3 (para casos específicos):

  • HyDE — cuando las consultas son muy cortas o el vocabulario diverge mucho
  • Query expansion o Multi-query — cuando el recall es el problema principal

Cómo medir la mejora

Antes de aplicar cualquier técnica, establece una línea base. Las métricas clave:

  • Recall@K: de los fragmentos relevantes que existen, ¿cuántos están en el top-K recuperado?
  • Precision@K: de los K fragmentos recuperados, ¿cuántos son relevantes?
  • MRR (Mean Reciprocal Rank): ¿en qué posición aparece el primer resultado relevante?

Para evaluarlas necesitas un dataset de consultas con respuestas conocidas. Usa RAGAS para automatizarlo.


Recursos relacionados:

Pon en práctica lo que has aprendido

Checklist de RAG

Verifica que tu pipeline RAG aplica las técnicas de retrieval correctas.

Abrir herramienta gratuita →

Recibe lo mejor de Contextología

Diseño de contexto, agentes y workflows de IA directamente en tu correo.