La mayoría de las demos de RAG se ven brillantes en una laptop. Le pasas a un script un PDF de veinte páginas, haces una pregunta y ves cómo cita el párrafo correcto. Pero llevar ese mismo pipeline a producción es donde termina el romance. Los documentos legales se cortan por la mitad a nivel de oración. Las densas referencias de API ahogan la señal importante en ruido de boilerplate. La latencia se dispara. Los usuarios esperan, se impacientan y se van. Chocamos contra ese muro con fuerza. Así que desmantelamos la capa de recuperación desde sus cimientos y la reconstruimos como un sistema medido y ajustable. El resultado fue un pipeline que alcanza un 95% de recall sin convertir la experiencia del usuario en una presentación de diapositivas.
Por qué las demos de RAG mueren en producción
El stack estándar es sorprendentemente uniforme tanto en proyectos de aficionados como en productos en etapas iniciales: chunks de tokens fijos, embeddings prefabricados y una única llamada de búsqueda vectorial. Esa simplicidad es seductora y funciona cuando tu corpus es limpio, pequeño y sintácticamente predecible. Los datos de producción no son ninguna de esas cosas. Un chunk fijo de 512 tokens cortará sin problemas por la mitad de una cláusula de indemnización en un contrato SaaS. De repente, tu capa de recuperación le está entregando al modelo de lenguaje la mitad de una obligación legal y le pide que responda una pregunta sobre responsabilidad civil. El modelo alucina porque el contexto está roto.
Los documentos técnicos extensos agravan el problema. La documentación de las API está llena de firmas de funciones, tablas y bloques de código. Una ventana fija podría capturar la mitad de una interfaz de TypeScript, pero omitir el nombre de la función que está arriba y el ejemplo de uso que está abajo. El vector de embedding termina representando fragmentos de sintaxis y ruido en línea en lugar de la capacidad real por la que el usuario pregunta. Si entra basura, sale alucinación.
Fragmentación por estructura, no por recuento de tokens
El primer cambio que hicimos fue dejar de pensar en los chunks como sacos de tokens. Los chunks son unidades semánticas. La estrategia adecuada depende enteramente de lo que estés indexando.
Para documentos legales, pasamos a una fragmentación recursiva que respeta la jerarquía del documento. Trata las secciones, subsecciones y cláusulas como límites. Una cláusula permanece intacta porque una cláusula es una unidad de significado. Si la cortas, la lógica legal se pierde.
Para la documentación de API, la fragmentación consciente de la estructura trata las funciones, clases y endpoints como elementos atómicos. Un chunk puede contener la firma de una función, sus argumentos y su docstring. No se desborda arbitrariamente hacia la siguiente función de utilidad solo porque un contador de tokens haya llegado a un límite. Esto mantiene el embedding enfocado en una capacidad discreta.
Los tickets de soporte son más desordenados. Son conversacionales, con hilos y no lineales. Los chunks fijos tomarán una actualización de estado de un ingeniero y una queja de un cliente del mismo hilo y pretenderán que forman una unidad coherente. Cambiamos a la fragmentación semántica, dividiendo cuando el tema o el interlocutor cambia, en lugar de cuando se agota el presupuesto de tokens.
Las wikis internas suelen ser los datos más descuidados de una organización. El formato es inconsistente, faltan encabezados y las secciones se mezclan. Para estos casos, utilizamos fragmentación basada en LLM. Un modelo pequeño lee por adelantado e identifica los límites lógicos antes de que generemos un embedding. Cuesta más inicialmente que una división por caracteres, pero la calidad de la recuperación se amortiza de inmediato.
Recuperación híbrida: combinar señales
La búsqueda vectorial es potente, pero tiene puntos ciegos. Si pegas un código de error exacto como ERR_CONNECTION_REFUSED_0x800, la búsqueda por similitud puede devolver una guía de resolución de problemas de un módulo no relacionado porque el espacio de embeddings los agrupó cerca. Las coincidencias exactas importan, y la búsqueda vectorial por sí sola puede diluirlas.
La búsqueda por palabras clave con BM25 resuelve el problema de las coincidencias exactas de maravilla. Pero se bloquea ante la distancia conceptual. Si un usuario pregunta sobre "degradación del rendimiento bajo carga pesada", BM25 pasará por alto una nota de diagnóstico que describa "rendimiento lento durante picos de tráfico" porque no hay suficiente solapamiento de palabras clave.
Dejamos de elegir un bando y empezamos a ejecutar ambos en paralelo. Las búsquedas vectorial y por palabras clave devuelven sus propias listas clasificadas. Las fusionamos mediante Reciprocal Rank Fusion. El RRF es simple y brutal en su efectividad. Clasifica cada documento basándose en su posición en cada lista. Los documentos que se sitúan cerca de la parte superior en ambos sistemas reciben un impulso masivo. Los documentos que solo le gustan a un motor siguen ganándose un lugar en el conjunto de candidatos final.
After fusion, we run the top candidates through a cross-encoder reranker. This is not free. It adds roughly 50 milliseconds of compute. It also increases recall by 15%. The cross-encoder evaluates the full query and each candidate chunk together, producing a relevance score far more nuanced than a bi-encoder embedding ever could. That extra 50 milliseconds is a bargain. It prevents you from shipping a garbage context window to the LLM and spending two seconds waiting for a confused or hallucinated answer.
Fix the Query Before You Search
Users do not write queries like search engineers. They type "app broken." They paste cryptic log fragments. They ask vague, ambiguous questions. If you send those raw strings straight to the index, you get garbage back.
We transform every query before it touches the retrieval engine.
First, query expansion. The system generates multiple search terms from a single short question. A user asks, "How do I fix the timeout?" The engine expands that to cover connection timeouts, read timeouts, gateway timeouts, and retry logic. This approach alone moved our recall from 78% to 96%.
Second, query decomposition. Complex questions get broken into smaller sub-questions. A query like "What's the refund policy for enterprise customers past 90 days and how does it differ from monthly plans?" becomes two focused searches rather than one bloated embedding lookup. Each sub-question hits the index independently. The results are stitched back together downstream. This keeps retrieval narrow and precise, which stops the dilution that happens when a single embedding tries to match a dozen concepts at once.
Let Bayesian Search Tune Your Pipeline
If you are still hand-tuning chunk size, overlap ratios, and retrieval weights, you are leaving performance on the table. We stopped guessing.
We defined a search space where chunk size, overlap percentage, vector-versus-BM25 weights, and reranker thresholds are all variables. Then we applied Bayesian optimization. Instead of grid-searching through hundreds of random configurations, Bayesian search builds a probabilistic model of what works. It proposes a configuration, observes the recall and latency, updates its beliefs, and proposes the next one. Over time it converges on balances a human would never stumble into manually.
It found combinations we never would have tried. Smaller chunks with heavier overlap. A slightly lower weight on dense vector search paired with a more aggressive reranker threshold. These non-obvious tradeoffs gave us both higher recall and lower latency.
This is not a one-time setup task. We re-run hyperparameter optimization monthly. Your corpus drifts. User behavior shifts. Your pipeline should adapt instead of rusting in place.
The Payoff
The raw output of that rebuild is hard to argue with.
Recall at position ten went from 78% to 95%. When the correct answer lives in our knowledge base, we surface it nineteen times out of twenty. Latency at the 95th percentile fell from 850 milliseconds to 320 milliseconds. The chat feels instant instead of ponderous.
Better retrieval gave the language model better grounding. Hallucination rate dropped from 12% to 3%. When the model has the right context in front of it, it stops inventing facts. Cost per query fell by 38%. Faster, sharper retrieval means fewer tokens wasted on irrelevant context, retry loops, and verbose but useless prompts.
Build It Like Infrastructure
If you are moving from prototype to production, treat retrieval as infrastructure code rather than a configuration
