volver al blog

RAG en español: dónde se pierde el recall cuando el corpus no está en inglés

ragembeddingsbusqueda-hibrida

El sistema funcionaba en la demo

Montas el RAG con un corpus de documentación técnica en inglés. Recall@10 de 0,91 en tu conjunto de evaluación. Enseñas la demo, la demo convence.

Entonces llega el corpus real del cliente: manuales, circulares y pliegos, todo en español. Mismo modelo de embeddings, mismo código de ingesta, mismo prompt. Recall@10 de 0,64.

La reacción habitual es cambiar de modelo de embeddings. Es la pieza más visible y la más cara de tocar. También suele ser la que menos culpa tiene. El idioma no afecta a una capa del pipeline: atraviesa tres, y dos de ellas ni siquiera son el modelo.

El tokenizador cobra más por lo mismo

Los tokenizadores BPE de los modelos generalistas se entrenaron sobre corpus donde el inglés manda. Eso significa que una palabra inglesa frecuente suele ser un token, mientras que su equivalente en español se parte en tres o cuatro piezas. “Settings” es un token; “configuraciones” rara vez lo es.

La consecuencia no es filosófica, es de presupuesto. Un chunk de 512 tokens guarda menos contenido en español que en inglés, así que troceas más fino sin haberlo decidido tú. Y el coste por documento sube al traducir el corpus aunque el número de páginas sea el mismo.

Antes de suponer nada, mídelo en tu texto real:

import tiktoken

enc = tiktoken.get_encoding("cl100k_base")

es = "Las configuraciones de seguridad se restablecen automáticamente."
en = "The security settings are automatically restored."

print(len(enc.encode(es)))  # más tokens
print(len(enc.encode(en)))  # menos, para el mismo significado

# Lo que importa no es este par de frases, sino la razón sobre tu corpus:
# tokens_es / tokens_en en una muestra de 200 documentos traducidos.
# Con esa razón ya puedes ajustar el tamaño de chunk en lugar de heredarlo.

El arreglo aquí no es cambiar de modelo. Es aceptar que tu tamaño de chunk venía de un tutorial escrito en inglés y recalcularlo.

La mitad léxica no sabe español

Si usas búsqueda híbrida —y deberías—, la mitad de tu recall viene de BM25 o de tsvector. Esa mitad es sensible al idioma de una forma brutal, porque trabaja con las formas de las palabras y no con su significado.

Dos fallos concretos, los dos silenciosos:

Los acentos. El documento dice “garantía”. La persona escribe “garantia”, porque está en el móvil y con prisa. Sin plegado de acentos, la coincidencia léxica es cero. El híbrido no falla con estrépito: degrada a búsqueda vectorial pura y tú lo ves como “el retriever está flojo hoy”.

La morfología. El español flexiona más que el inglés: género, número, conjugaciones, enclíticos. “Configurarlo”, “configuraciones” y “configuró” son la misma raíz para cualquiera menos para un índice sin stemmer en español.

En Elasticsearch el analizador spanish trae stopwords y stemmer, pero no pliega acentos: asciifolding hay que añadirlo a mano. En PostgreSQL la configuración spanish stemiza, y para los acentos necesitas la extensión unaccent metida en la propia configuración de búsqueda:

CREATE EXTENSION IF NOT EXISTS unaccent;

-- Configuración propia: primero quita acentos, luego stemiza en español.
CREATE TEXT SEARCH CONFIGURATION es_rag ( COPY = spanish );
ALTER TEXT SEARCH CONFIGURATION es_rag
  ALTER MAPPING FOR hword, hword_part, word
  WITH unaccent, spanish_stem;

-- Ahora "garantia" y "garantías" caen en el mismo lexema.
SELECT to_tsvector('es_rag', 'período de garantías extendidas');
--  'extend':4 'garant':3 'period':1

-- Indexa la columna generada, no la expresión suelta:
ALTER TABLE chunks ADD COLUMN tsv tsvector
  GENERATED ALWAYS AS (to_tsvector('es_rag', texto)) STORED;
CREATE INDEX chunks_tsv_idx ON chunks USING GIN (tsv);

Una tarde de trabajo, coste de inferencia cero, y recupera consultas que hoy no devuelven nada.

El encoder multilingüe alinea traducciones

Los encoders multilingües se entrenan, entre otras cosas, para que una frase y su traducción caigan juntas en el espacio vectorial. Es exactamente lo que quieres para buscar en inglés sobre documentos en español.

Y es exactamente lo que te destroza el top-k cuando el corpus tiene el mismo manual en los dos idiomas. El vecino más cercano de un párrafo en español no es el párrafo que responde a la pregunta: es ese mismo párrafo en inglés. Pides diez resultados y recibes cinco pares, es decir, cinco contenidos distintos ocupando diez plazas.

El síntoma es un contexto que parece lleno y responde mal. La causa no es el recall, es la diversidad del top-k. Se arregla con un metadato lang por chunk y un filtro previo, o deduplicando pares traducidos en ingesta. Nada de esto exige tocar el modelo.

Síntoma, causa, dónde se arregla

SíntomaCausa probableDónde se arregla
El coste por documento sube al traducir el corpusel tokenizador parte el español en más piezastamaño de chunk
La consulta sin tildes no devuelve nadael índice léxico no pliega acentosanalizador del índice
”Garantías” no encuentra “garantía”stemmer ausente o en inglésanalizador del índice
El top-10 son cinco contenidos duplicadosalineamiento entre idiomas del encodermetadato de idioma y deduplicación
Recall bajo y sin patrón claroel conjunto de evaluación está traducidoconjunto de evaluación

El conjunto de evaluación traducido miente

Este es el error que más caro sale, porque esconde a todos los demás.

Coges tus 200 preguntas de evaluación en inglés, se las pasas a un LLM para que las traduzca y te declaras cubierto. El problema es que la traducción y el documento comparten registro: ambos salen de un texto formal, con el vocabulario del manual. Tus métricas salen razonables y el sistema sigue fallando en producción.

Las consultas reales en español no se parecen a eso. La gente escribe “cuanto tarda en llegar”, no “¿cuál es el plazo de entrega estimado?”. Sin tildes, sin signo de apertura, con el término coloquial en lugar del término del pliego.

Con 30 consultas copiadas del buzón de soporte mides mejor que con 500 traducidas. Y son las únicas que detectan los dos fallos léxicos de arriba, porque son las únicas que llegan mal escritas.

Por dónde empezar

El orden importa, y es casi el inverso del instinto:

  1. Reúne 30 consultas reales en español y mide recall@10 antes de tocar nada. Sin esta línea base, cualquier cambio posterior es fe.
  2. Arregla la capa léxica. Plegado de acentos y stemmer en español. Es lo más barato del pipeline y suele ser lo más roto.
  3. Etiqueta el idioma de cada chunk y filtra antes de buscar, si el corpus es mixto.
  4. Recalcula el tamaño de chunk con la razón de tokens de tu propio corpus.
  5. Y solo entonces, compara modelos de embeddings. Con una línea base decente, sabrás si el cambio aporta algo o si estabas pagando por tapar un unaccent que faltaba.

Cambiar de modelo es lo primero que se propone en la reunión y lo último que conviene hacer. En un corpus en español, casi todo el recall perdido está en la tubería, no en el encoder.