Inicio / Artículos

El conjunto de herramientas de recuperación RAG: BM25, Dense, HyDE, RRF, MMR y Reranking

La herramienta de recuperación RAG: BM25, vectores densos, HyDE, RRF, MMR y reclasificación, cuándo usar cada una, la latencia aproximada por etapa, y cómo un pipeline híbrido las integra.

Editor: REACTAPP.TOP8 min de lectura1736 palabras

Fuentes: The Retrieval Toolkit That’s Quietly Deciding Whether Your RAG Pipeline Works.

Después de probar varios pipelines RAG, uno suele toparse con el mismo problema. Las representaciones vectoriales son buenas y el prompt está bien redactado, pero las respuestas siguen siendo inadecuadas. A menudo, la culpa no es del modelo, sino de los datos con los que se trabaja. La recuperación de información es el cuello de botella oculto, y la mayoría de los fallos se deben a un reducido número de técnicas que los equipos omiten o utilizan incorrectamente.

Aquí está ese conjunto de herramientas: cómo funciona cada componente, por qué existe, cuándo usarlo y cómo se combinan en un pipeline de producción, además del tiempo que requieren para generar una respuesta.

1. BM25: una metodología de puntuación de palabras clave que nunca pasará de moda

BM25 (Best Matching 25) es una función de clasificación léxica dispersa que representa una mejora sobre TF-IDF. Clasifica los documentos según la cantidad de términos de la consulta que comparten, ajustados en función de la frecuencia del término y la longitud del documento.

Para cada término de consulta, un documento obtiene puntos cuando dicho término aparece con frecuencia, pero los beneficios disminuyen con cada aparición: veinte ocurrencias no son ni de cerca veinte veces más relevantes que una sola. Los documentos largos son penalizados para que no puedan acumular coincidencias simplemente por ser extensos.

from rank_bm25 import BM25Okapi
corpus = [doc.split() for doc in documents]  # tokenized docs
bm25 = BM25Okapi(corpus)
query = "vector database indexing".split()
scores = bm25.get_scores(query)
top_k = sorted(range(len(scores)), key=lambda i: -scores[i])[:5]

BM25 se preocupa por el token literal y no por su significado, lo que lo hace útil para:

  • Códigos de producto, SKU, códigos de error e ID
  • Entidades nombradas como personas, empresas y nombres de medicamentos
  • Términos raros o técnicos que los embeddings tienden a mezclar

Úsalo cuando la precisión en términos específicos es más importante que la tolerancia a la paráfrasis: búsquedas legales, médicas, de códigos y de registros. Es económico y rápido, y no necesita entrenamiento ni GPU. Su debilidad es que no tiene comprensión semántica alguna, por lo que “coche” y “automóvil” le parecen términos no relacionados.

2. Recuperación de vectores densos: coincidencia de significado

La recuperación densa convierte las consultas y los documentos en vectores de incrustación, con cientos a miles de dimensiones sin ceros, generados por un codificador neuronal. La similitud se obtiene a partir del producto coseno o punto, y un índice de vecino más cercano aproximado como HNSW o IVF encuentra los más cercanos.

from sentence_transformers import SentenceTransformer
import numpy as np
model = SentenceTransformer("BAAI/bge-small-en-v1.5")
doc_embeddings = model.encode(documents, normalize_embeddings=True)
query_embedding = model.encode([query], normalize_embeddings=True)
scores = np.dot(doc_embeddings, query_embedding.T).flatten()
top_k = np.argsort(-scores)[:5]

Capta la similitud semántica: “renuncia del ejecutivo” encuentra “el CEO renunció” sin palabras en común. La gente suele llamar a esto búsqueda semántica, aunque estrictamente el mecanismo es la recuperación densa y el resultado es la correspondencia semántica.

Úsalo con consultas conversacionales, preguntas parafraseadas, recuperación interlingüística, o siempre que los usuarios no utilicen el vocabulario exacto de sus documentos. Puede fallar con términos raros, códigos y números exactos, ya que el espacio de incrustación suaviza los detalles que BM25 mantiene.

3. Reescritura de consultas e HyDE: reparar primero la consulta

A veces el sistema de recuperación está bien y el problema radica en la consulta. Los usuarios escriben preguntas cortas, vagas o con redacción extraña, mientras que tanto BM25 como la recuperación densa funcionan mejor con texto bien estructurado y rico en información.

La reescritura de consultas consiste en que un LLM amplíe o aclare la pregunta antes de realizar la búsqueda, convirtiendo “¿con qué velocidad?” en una pregunta precisa sobre cuántos documentos por segundo puede manejar el pipeline de indexación X en su punto máximo.

HyDE (Hypothetical Document Embeddings) va aún más allá. En lugar de incrustar la consulta, se le pide a un LLM que invente una respuesta plausible; esa respuesta fabricada se incrusta y se utiliza para encontrar documentos reales. La idea es que una respuesta inventada suele estar más cerca en el espacio de incrustación de las respuestas reales que la propia pregunta.

hypothetical_doc = llm.generate(f"Write a short passage answering: {query}")
hyde_embedding = model.encode([hypothetical_doc], normalize_embeddings=True)
scores = np.dot(doc_embeddings, hyde_embedding.T).flatten()

Las consultas cortas o ambiguas se encuentran entre las causas más comunes de un bajo rendimiento en la recuperación de información, ya que simplemente no transmiten suficiente señal. Esto es especialmente problemático en interfaces de tipo chat y en dominios donde el vocabulario del usuario difiere del del corpus. La consecuencia es una llamada adicional al modelo LLM antes de que comience la recuperación, lo que genera mayor latencia y costo, además de que una respuesta hipotética deficiente puede orientar erróneamente el proceso de recuperación.

4. Búsqueda híbrida: uso de ambos métodos

BM25 y la recuperación densa fallan en extremos opuestos. La búsqueda híbrida ejecuta ambos métodos en paralelo y fusiona las listas de resultados, logrando identificar términos exactos y paráfrasis en una sola pasada. El problema radica en que las puntuaciones de BM25 y las similitudes coseno operan en escalas diferentes, por lo que promediarlas no funciona. Esa es la función de la fusión de rangos recíprocos.

5. Fusión de rangos recíprocos: combinar rangos, no puntuaciones

RRF evita por completo la normalización de las puntuaciones al combinar la posición en la clasificación e ignorar las puntuaciones brutas.

RRF_score(doc) = Σ  1 / (k + rank_r(doc))   for each ranker r

La constante k, que suele ser 60, atenúa el efecto para que un único puesto número uno no pueda dominar.

def reciprocal_rank_fusion(ranked_lists, k=60):
    scores = {}
    for ranked_list in ranked_lists:
        for rank, doc_id in enumerate(ranked_list):
            scores[doc_id] = scores.get(doc_id, 0) + 1 / (k + rank + 1)
    return sorted(scores.items(), key=lambda x: -x[1])
bm25_ranked = ["doc3", "doc1", "doc5"]
dense_ranked = ["doc1", "doc4", "doc3"]fused = reciprocal_rank_fusion([bm25_ranked, dense_ranked])

Los documentos que reciben una alta calificación de varios recuperadores ascienden naturalmente a la parte superior, sin necesidad de calibrar las escalas de puntuación. Es el método habitual para combinar resultados híbridos, y es adecuado para cualquier caso en el que se unan dos o más listas ordenadas de diferentes recuperadores, como BM25 junto con modelos densos, o varios modelos de embedding.

6. Relevancia marginal máxima: reducir la redundancia

Una clasificación perfecta puede seguir siendo inútil si los cinco primeros resultados dicen exactamente lo mismo. MMR vuelve a ordenar los resultados teniendo en cuenta tanto la relevancia como la diversidad.

MMR = λ · Sim(doc, query) − (1 − λ) · max(Sim(doc, selected))

El parámetro λ determina el equilibrio. Cuando λ = 1 se obtiene el método top-k simple, mientras que cuando λ = 0 se maximiza la diversidad independientemente de la relevancia.

import numpy as np
def mmr(query_vec, doc_vecs, doc_ids, lambda_param=0.5, top_k=5):
    selected, remaining = [], list(range(len(doc_ids)))
    sim_to_query = doc_vecs @ query_vec
    while len(selected) < top_k and remaining:
        if not selected:
            idx = remaining[np.argmax(sim_to_query[remaining])]
        else:
            mmr_scores = []
            for i in remaining:
                sim_to_selected = max(doc_vecs[i] @ doc_vecs[j] for j in selected)
                mmr_scores.append(
                    lambda_param * sim_to_query[i] - (1 - lambda_param) * sim_to_selected
                )
            idx = remaining[np.argmax(mmr_scores)]
        selected.append(idx)
        remaining.remove(idx)
    return [doc_ids[i] for i in selected]

Sin MMR, se puede utilizar toda la ventana de contexto en cinco textos casi idénticos en lugar de cinco fragmentos que abarquen diferentes aspectos de la respuesta. Esto resulta útil en corpus con contenido superpuesto o repetido, como páginas de Preguntas Frecuentes, documentos de producto con texto genérico, o cualquier conjunto de pasajes casi idénticos.

7. Reclasificación con codificador cruzado: el factor clave de precisión

Hasta ahora todo ha pertenecido al mundo del bi-encoder, donde las consultas y los documentos se incrustan por separado y luego se comparan. Un codificador cruzado introduce tanto la consulta como los candidatos en un mismo transformador, de modo que el modelo pueda tener en cuenta ambos textos. Esto permite juicios de relevancia mucho más precisos que al comparar vectores precalculados.

from sentence_transformers import CrossEncoder
reranker = CrossEncoder("cross-encoder/ms-marco-MiniLM-L-6-v2")
pairs = [(query, doc) for doc in candidate_docs]
scores = reranker.predict(pairs)
reranked = [doc for _, doc in sorted(zip(scores, candidate_docs), reverse=True)]

Los codificadores cruzados superan de manera consistente a los codificadores binarios en la evaluación de la relevancia, ya que analizan conjuntamente la consulta y el documento. Úsalos como paso final de refinamiento en un conjunto reducido de candidatos, generalmente los 20 a 50 mejores tras la fusión o el MMR, y nunca sobre todo el corpus, ya que cada par de documentos requiere su propio proceso de procesamiento. Después de la llamada al LLM, este representa el mayor aumento de latencia en esta lista, por lo que se coloca al final del pipeline y no al principio.

8. Tiempo de respuesta: el costo de todo esto

Cada técnica mencionada anteriormente mejora la calidad de la recuperación, pero ninguna es gratuita. Cada una añade latencia, y el tiempo total desde que se envía una consulta hasta que se recibe la respuesta final determina si un pipeline cuidadosamente ajustado puede utilizarse en producción. Costos típicos por etapa:

  • Reescritura de la consulta o HyDE, si se utiliza: aproximadamente de 200 ms a 1 s, con una llamada adicional al LLM antes de que comience la recuperación.
  • Búsqueda en BM25: unos 5 a 20 ms, a partir de un índice invertido en memoria de bajo costo.
  • Búsqueda de vectores densos (ANN): unos 10 a 50 ms, dependiendo del tamaño y tipo del índice, ya sea HNSW o plano.
  • Fusión RRF: de 1 a 5 ms, cálculos aritméticos simples en listas de rangos.
  • Reclasificación MMR: aproximadamente de 20 a 100 ms, aumentando con el conjunto de candidatos ya que es un proceso iterativo y por pares.
  • Reclasificación con codificador cruzado, si se utiliza: unos 50 a 300 ms, a menudo el costo más alto aparte de la generación.
  • Generación por LLM: de unos 500 ms a varios segundos, generalmente el componente más importante.

Para mantener ese tiempo total bajo control:

  • Ejecutar BM25 y la recuperación densa en paralelo. No dependen el uno del otro.
  • Aplicar MMR a un grupo pequeño, por ejemplo, los 20 a 30 mejores después de RRF, ya que las comprobaciones de similitud entre pares tienen un costo elevado en términos de rendimiento.
  • Almacenar en caché las representaciones vectoriales para consultas frecuentes o repetidas.
  • Añadir un codificador cruzado solo cuando la mejora en precisión justifique claramente el retraso adicional.
  • Seguir el tiempo de respuesta como métrica fundamental junto con la calidad de la respuesta, como la fidelidad o el recall@k. Un sistema un 2% más preciso pero tres veces más lento podría no ser adecuado para su implementación.

Cómo encajan las partes

Un pipeline híbrido de recuperación en entornos de producción suele funcionar de la siguiente manera:

  • Opcionalmente, la reescritura de consultas o HyDE limpian una consulta vaga antes de la recuperación, a costa de un mayor tiempo de respuesta.
  • BM25 detecta términos exactos.
  • La recuperación densa captura el significado.
  • RRF combina ambas listas sin complicaciones relacionadas con la normalización de puntuaciones.
  • MMR reduce la lista combinada a un conjunto de candidatos diverso y sin redundancias.
  • Un codificador cruzado realiza un proceso final de alta precisión sobre ese conjunto más pequeño.
  • El tiempo de respuesta es el indicador que determina si todo está listo para ser utilizado.

Guía rápida para tomar decisiones

  • ¿Consultas cortas, vagas o fuera del vocabulario? Escríbalas de nuevo o pruebe HyDE.
  • ¿Precisión en coincidencias exactas para códigos, identificadores y términos raros? BM25.
  • ¿Se permite la paráfrasis? Búsqueda densa.
  • ¿Se necesita ambos enfoques a la vez? Úselos combinados con RRF.
  • ¿Hay fragmentos redundantes o duplicados en el contexto? MMR.
  • ¿Se necesita la mayor relevancia en un conjunto final pequeño? Reclasificación con codificador cruzado.
  • ¿La precisión es alta pero la velocidad baja? Mida cada etapa antes de optimizar de forma indiscriminada.

Ninguna técnica por sí sola es la solución definitiva. Los mejores sistemas de producción combinan varias de ellas, ajustados según registros reales de consultas y no por intuición, y la combinación adecuada sigue dependiendo en gran medida del corpus y del presupuesto de latencia.