Cuándo la búsqueda híbrida (BM25) te empeora el retrieval: un caso real multilingüe
Nota para el equipo
Escrito a partir de un caso real del proyecto de Avicena (corpus de consultoría en español con algunos documentos fuente en inglés). La conclusión no es "el híbrido es malo", sino "el híbrido con un sparse léxico simple puede ser una regresión neta en según qué corpus, y hay que medirlo antes de darlo por bueno".
TL;DR
Montamos búsqueda híbrida (denso bge-m3 + sparse BM25 fusionados con RRF, más un
reranker cross-encoder) sobre un retrieval denso que ya funcionaba. Las respuestas
empeoraron, así que lo medimos etapa por etapa.
Decisión
Denso por defecto, híbrido detrás de un flag (HYBRID_SEARCH_ENABLED). En este
corpus el híbrido no aporta calidad y cuesta latencia (hasta 16,8 s vs 6,8 s del
denso) y riesgo de ruido. No lo tiramos: queda desactivable para retomarlo con un
sparse mejor (p. ej. SPLADE) cuando un corpus lo pida.
Ojo con el A/B
Nuestra primera tanda dijo que el híbrido era mucho peor. Era mentira parcial: un segundo bug (la síntesis recibía las fuentes recortadas a 150 caracteres) contaminaba la comparación. Con ese bug arreglado, las tres configuraciones daban respuestas casi idénticas. El abismo de calidad era, en gran parte, ese confound, no el ranking.
El resto del artículo es el "cómo lo supimos": las mediciones por etapa, el confound y la receta para reproducirlo.
El contexto
Teníamos un retrieval denso (embeddings bge-m3 sobre Qdrant) que respondía bien.
Como mejora de calidad montamos búsqueda híbrida: añadimos un canal sparse léxico
(BM25 auto-hospedado), fusionamos denso + sparse con RRF (Reciprocal Rank Fusion) y
pusimos un reranker cross-encoder (jina-reranker-v2) como etapa final. Sobre el papel,
todo apunta a más recall y mejor precisión.
En la práctica, las respuestas empeoraron. Y bastante.
RRF (Reciprocal Rank Fusion)
Forma de fusionar dos listas de resultados (la densa y la sparse) en una sola: cada documento puntúa según su posición en cada lista, no según su score crudo, y se suman. Evita tener que normalizar scores de escalas distintas.
El síntoma
Pregunta de prueba: "¿Cuáles son los puntos fuertes de Qlik?" (el corpus incluye un informe de Gartner sobre plataformas de BI). Esto es lo que devolvía el sistema antes y después de activar el híbrido:
| Denso (solo ranking) | Híbrido + rerank | |
|---|---|---|
| Qué responde | Motor en memoria y análisis asociativo; dos modos (BI tradicional + descubrimiento visual con NPrinting); facilidad de uso y paneles atractivos; empoderamiento del usuario; red de socios; cuartil superior de interés | Cuartil superior de interés; "su ejecución de mercado se ha visto moderada por desafíos"; "una porción menor de referencias lo usan para descubrimiento visual" |
| Fortalezas concretas | 5-6, de producto y adopción | prácticamente ninguna |
| Debilidades | ninguna (no se preguntaban) | 2, sin que nadie las pidiera |
Las respuestas completas, palabra por palabra:
Denso (lo que dábamos por bueno)
Los principales puntos fuertes de Qlik son:
Capacidades Tecnológicas y Producto: su motor en memoria y el análisis asociativo permiten construir aplicaciones interactivas robustas [...]. Ofrece soporte para dos modos operativos: BI tradicional (Modo 1) y descubrimiento visual gobernado (Modo 2), gracias a NPrinting en Qlik Sense.
Experiencia del Usuario: la facilidad de uso y sus paneles atractivos son diferenciadores clave. Empodera a usuarios avanzados de negocio para que sean desarrolladores de contenido sin depender de TI.
Mercado y Comunidad: sólido ecosistema de socios; se ubica en el cuartil superior por interés de mercado.
Híbrido + rerank (lo que empezó a devolver)
Los resultados indican que Qlik estuvo en el cuartil superior de interés según búsquedas en gartner.com y consultas de clientes en 2017. Sin embargo, su ejecución en el mercado comparada con otros líderes ha estado moderada por desafíos al apoyar tanto QlikView establecido como las soluciones menos maduras. Además, una porción menor de las referencias de Qlik lo usan para integración de datos o descubrimiento visual que en años anteriores [...].
Misma pregunta, mismo modelo de redacción, mismos documentos en el índice. Lo único que cambió fue el retrieval. Así que fuimos a mirarlo.
Cómo lo supimos: aislar cada etapa del retrieval
En vez de teorizar, ejecutamos la misma consulta contra Qdrant por separado en cada modalidad y miramos los 5 primeros chunks de cada una:
| # | Denso solo | Sparse solo (BM25) | Híbrido (RRF denso+sparse) |
|---|---|---|---|
| 1 | Gartner p34: Qlik, cuartil + NPrinting (fortaleza) | Marketing Mix: "medios para comunicar el producto" | Gartner p34 (fortaleza) |
| 2 | Gartner p34: "market execution tempered" (caution) | Estrategia: "puntos de contacto con el cliente" | Marketing Mix (irrelevante) |
| 3 | Gartner p35: "narrow use case" (caution) | Informe SaaS de IDC | Gartner p34 (caution) |
| 4 | Gartner p34: partner network (fortaleza) | Retrato pyme 2015 | Comunicación (irrelevante) |
| 5 | Gartner p33: análisis asociativo (fortaleza) | Segmentación: PivotLink | Gartner p35 (caution) |
Tres cosas saltan a la vista:
- El denso trae 5 chunks, todos del perfil de Qlik. Relevantes.
- El sparse (BM25) trae 5 chunks y ninguno habla de Qlik. Son documentos en español que comparten palabras con la consulta ("medios", "puntos", "estrategia").
- El híbrido, al fusionar, mete 2 de esos chunks irrelevantes en el top-5, desplazando contenido bueno que el denso sí tenía.
Eso bastó para profundizar en las dos piezas nuevas: el sparse y el reranker.
1. BM25 es de baja precisión con consultas cortas en español
El usuario pregunta en español ("puntos fuertes", "puntos clave"). BM25 tokeniza, aplica stemming y pondera por frecuencia/IDF. El problema: palabras como "puntos", "clave", "medios" son comunes y aparecen en documentos sin relación con la pregunta.
Se ve en la columna sparse de la tabla de arriba: para "puntos clave de la estrategia", los primeros resultados eran "Base para Marketing Mix → estrategia de medios" y "identificar todos los puntos de contacto con el cliente". El denso, en cambio, captaba la semántica y traía los chunks correctos.
Cuándo brilla BM25
BM25 brilla con terminología rara y específica (nombres propios, códigos, siglas). Con lenguaje natural genérico y consultas cortas, su señal léxica es ruido. En un idioma con mucha palabra-función y morfología (el español), peor.
2. El reranker cross-encoder se degrada cross-lingüe
La consulta va en español; los documentos, en inglés. El bi-encoder (bge-m3) es
robustamente multilingüe y mantiene la relevancia. El cross-encoder de reranking no
tanto.
Medición directa sobre los mismos 6 chunks del perfil de Qlik, variando solo el idioma de la consulta (el número es el score del reranker; mayor = más relevante):
| Chunk | Tipo | rerank (consulta ES) | rerank (consulta EN) |
|---|---|---|---|
| quartile + NPrinting | fortaleza | +1.27 | +0.96 |
| partner network | fortaleza | −0.16 (puesto 6) | +0.27 (puesto 4) |
| associative/dashboards | fortaleza | +0.67 | +0.45 |
| market execution tempered | caution | +1.03 | +0.94 |
El mismo chunk de fortalezas ("partner network") cae al puesto 6 (fuera del top-5) con la consulta en español y sube al puesto 4 con la consulta en inglés. La penalización cross-lingüe del cross-encoder es real y medible.
3. El reranker no distingue polaridad (fortaleza vs debilidad)
Aún en inglés, el chunk "market execution tempered by challenges" (una debilidad) queda en el puesto 2, por encima de varias fortalezas. El cross-encoder puntúa por solape temático con la consulta ("Qlik" + vocabulario de mercado), no por si el texto responde afirmativa o negativamente. Para una pregunta de "puntos fuertes", eso es exactamente lo que no quieres.
Una trampa en el A/B: la variable de confusión
Para cuantificarlo end-to-end montamos una tanda A/B.
La tanda A/B, en una frase
Lanzamos la misma batería de 9 preguntas de negocio contra el agente, con el mismo modelo de redacción y la misma temperatura, cambiando solo la configuración de retrieval: (a) denso solo, (b) híbrido + rerank, (c) híbrido sin rerank. Comparamos respuestas y tiempos. Los interruptores: un flag para denso/híbrido y parar el contenedor del reranker para la variante sin rerank.
El primer veredicto fue tajante: denso solo el mejor; híbrido + rerank peor (respuestas sesgadas a cautions); híbrido sin rerank el peor (ruido de BM25 directo al top-k). Parecía cerrado, pero no lo estaba.
Al investigar la latencia encontramos otro bug, esta vez en la síntesis (lo contamos en el artículo hermano "Quién redacta la respuesta en un RAG agéntico"). En resumen: el componente que redactaba la respuesta del agente recibía las fuentes recortadas a 150 caracteres. Las respuestas ricas del camino denso no las hacía el agente: venían "de gorra" de una síntesis interna de LlamaIndex que el camino híbrido no tenía.
Confound (variable de confusión)
Un factor que cambia a la vez que la variable que crees estar midiendo, de forma que no puedes saber a cuál atribuir el efecto observado. Aquí: creíamos medir "denso vs híbrido" (retrieval), pero el camino denso traía además una síntesis mejor alimentada (generación). Dos variables moviéndose juntas, y lo leímos todo como si fuera retrieval.
Reevaluación, ya sin el confound
Arreglado el recorte (el sintetizador recibe el texto completo del chunk), re-corrimos las tres configuraciones. Primero, los tiempos (segundos):
| Config | medio (9 preg.) | máx | rag-02 | rag-03 | rag-04 (Qlik) |
|---|---|---|---|---|---|
| Denso | 3.9 | 6.8 | 6.8 | 4.8 | 6.1 |
| Híbrido + rerank | 6.5 | 16.8 | 16.8 | 13.4 | 10.4 |
| Híbrido sin rerank | 5.3 | 9.3 | 8.9 | 9.3 | 9.3 |
Y, lo importante, las respuestas a la pregunta de Qlik en las tres configuraciones, ya con la síntesis arreglada:
| Config | Respuesta a "puntos fuertes de Qlik" (resumen) |
|---|---|
| Denso | Despliegue rápido (motor en memoria); facilidad de uso y visuales; habilitación de usuarios; momentum (cuartil); red de socios; componentes de visión |
| Híbrido + rerank | Despliegue rápido; facilidad de uso y visuales; habilitación de usuarios; momentum; red de socios; componentes de visión |
| Híbrido sin rerank | Despliegue rápido; facilidad de uso y visuales; habilitación de usuarios; momentum; red de socios; componentes de visión |
El hallazgo incómodo
Con la síntesis bien alimentada, las tres dan respuestas ricas y casi idénticas. La pregunta de Qlik, que antes "se rompía" con el híbrido, ahora sale bien en las tres. El abismo de calidad de la primera tanda era, en gran parte, el bug de síntesis, no el ranking.
Entonces, ¿el híbrido daba igual?
No exactamente. Dos cosas siguen siendo verdad:
- El ruido de BM25 en el retrieval es real (lo vimos en la tabla de la pista: traía Marketing Mix y Comunicación para una consulta de Qlik). Lo que aprendimos es que un sintetizador bien alimentado absorbe un par de chunks irrelevantes: coge lo relevante e ignora el ruido. Por eso el daño end-to-end fue mucho menor de lo que la primera tanda sugería. Pero es un riesgo latente: en otra consulta el ruido podría desplazar contenido relevante fuera del top-k.
- El híbrido + rerank cuesta latencia (rag-02: 16,8 s vs 6,8 s del denso; el cross-encoder corre en CPU sobre un pool de 20 candidatos) sin una ganancia de calidad que lo justifique en este corpus.
Por eso la decisión del TL;DR: denso por defecto, híbrido tras flag. La razón ya no es "el híbrido responde mucho peor" (eso era el confound), sino "no aporta calidad aquí y cuesta más latencia y más riesgo de ruido".
Qué nos llevamos
- El híbrido no es gratis ni universalmente mejor. Suma una señal (léxica) que puede ser ruido según el corpus y el idioma, y cuesta latencia (sobre todo el rerank en CPU). Mide el coste/beneficio, no asumas que "más señales = mejor".
- BM25 ≠ "sparse moderno". Un sparse neuronal (SPLADE) aprende expansión de términos y semántica léxica; BM25 es frecuencias puras. Para híbrido en entorno multilingüe, BM25 es la opción más frágil.
- El reranking cross-encoder hereda los límites del modelo: cross-lingüe y polaridad. Si consulta e idioma del corpus no coinciden, mídelo; quizá convenga traducir la consulta, usar un reranker más fuerte, o no usarlo.
- Aísla cada etapa. Denso solo, sparse solo, RRF, RRF+rerank. El bug casi nunca está donde crees; el nuestro estaba en el reranker y en el sparse, no en la fusión.
- Cuidado con los confounds en el A/B. Casi condenamos el híbrido por un problema que era de la síntesis. Cuando compares pipelines, asegúrate de que solo cambia la variable que crees: mismo modelo, mismo material de síntesis, todo igual salvo lo que mides.
- Un sintetizador bien alimentado es robusto al ruido de retrieval. Si el LLM recibe el texto completo de los top-k, puede ignorar uno o dos chunks irrelevantes. No excusa el ruido (es riesgo latente), pero relativiza cuánto te duele en la práctica un retrieval imperfecto.
- Una etapa que devuelve resultados plausibles pero peores es más peligrosa que una que falla: nadie ve el error, solo "responde algo raro".
Anexo: cómo lo medimos (la receta)
Esto es lo que ejecutamos, por si sirve para reproducirlo:
- Aislar el retrieval por modalidad. Embeber la consulta con el mismo
bge-m3del índice y lanzar tres consultas a Qdrant:densesolo,sparsesolo, y unprefetchde ambas confusion: rrf. Comparar los top-5 (la tabla de "cómo lo supimos"). - Probar el reranker en aislado. Llamar al servicio de reranking con el pool fusionado y comparar el orden antes/después. Repetir variando el idioma de la consulta (ES vs EN) para detectar la degradación cross-lingüe.
- A/B end-to-end con el modelo fijo. Pasar la batería de preguntas por el agente con el mismo modelo de redacción y temperatura, alternando configuraciones de retrieval mediante el flag y parando contenedores (reranker/sparse). Comparar respuestas y tiempos.
- Cuando un resultado sorprenda, buscar el confound antes de concluir: ¿hay algo más que esté cambiando entre las dos ramas además del retrieval? En nuestro caso lo había (la síntesis), y obligó a re-correr todo el punto 3.