La plupart des démos RAG ont l'air brillantes sur un ordinateur portable. Donnez un PDF de vingt pages à un script, posez une question, et regardez-le citer le bon paragraphe. Mais le passage de ce même pipeline en production est le moment où le romantisme s'arrête. Les documents juridiques se retrouvent coupés en deux au niveau des phrases. Les références d'API denses noient le signal important dans un bruit de code répétitif. La latence explose. Les utilisateurs attendent, s'impatientent et partent. Nous nous sommes heurtés de plein fouet à ce mur. Nous avons donc démantelé la couche de récupération jusqu'aux fondations pour la reconstruire en tant que système mesuré et ajustable. Le résultat est un pipeline qui atteint un rappel (recall) de 95 % sans transformer l'expérience utilisateur en diaporama.
Pourquoi les démos RAG échouent en production
La pile technologique standard est étonnamment uniforme, des projets amateurs aux produits en phase de démarrage : découpage en blocs de tokens fixes, embeddings prêts à l'emploi et un seul appel de recherche vectorielle. Cette simplicité est séduisante, et elle fonctionne lorsque votre corpus est propre, petit et syntaxiquement prévisible. Les données de production ne sont rien de tout cela. Un bloc fixe de 512 tokens coupera sans problème au milieu d'une clause d'indemnisation dans un contrat SaaS. Soudain, votre couche de récupération fournit au modèle de langage la moitié d'une obligation juridique et lui demande de répondre à une question de responsabilité. Le modèle hallucine parce que le contexte est brisé.
Les documents techniques volumineux aggravent le problème. La documentation d'API est remplie de signatures de fonctions, de tableaux et de blocs de code. Une fenêtre fixe peut capturer le milieu d'une interface TypeScript mais manquer le nom de la fonction au-dessus et l'exemple d'utilisation en dessous. Le vecteur d'embedding finit par représenter des fragments de syntaxe et du bruit en ligne plutôt que la capacité réelle sur laquelle porte la question de l'utilisateur. Données erronées, hallucinations en sortie.
Découper par structure, pas par nombre de tokens
Le premier changement que nous avons effectué a été de cesser de considérer les blocs (chunks) comme des sacs de tokens. Les blocs sont des unités sémantiques. La bonne stratégie dépend entièrement de ce que vous indexez.
Pour les documents juridiques, nous sommes passés à un découpage récursif qui respecte la hiérarchie du document. Il traite les sections, les sous-sections et les clauses comme des limites. Une clause reste intacte car une clause est une unité de sens. Si vous la coupez, la logique juridique s'en échappe.
Pour la documentation d'API, le découpage sensible à la structure traite les fonctions, les classes et les points de terminaison (endpoints) comme des éléments atomiques. Un bloc peut contenir une signature de fonction, ses arguments et sa docstring. Il ne déborde pas arbitrairement sur la fonction utilitaire suivante simplement parce qu'un compteur de tokens a basculé. Cela permet de maintenir l'embedding concentré sur une capacité discrète.
Les tickets de support sont plus désordonnés. Ils sont conversationnels, organisés en fils de discussion et non linéaires. Des blocs fixes saisiront une mise à jour de statut d'un ingénieur et une plainte d'un client dans le même fil et prétenderont qu'ils forment une unité cohérente. Nous sommes passés au découpage sémantique, en séparant les blocs lorsque le sujet ou l'interlocuteur change, plutôt que lorsque le budget de tokens est épuisé.
Les wikis internes sont souvent les données les plus négligées d'une organisation. Le formatage est incohérent, les en-têtes manquent et les sections s'entremêlent. Pour ceux-là, nous utilisons un découpage basé sur les LLM. Un petit modèle lit à l'avance et identifie les limites logiques avant même que nous ne générions un embedding. Cela coûte plus cher au départ qu'un simple découpage par caractères, mais la qualité de la récupération est rentabilisée immédiatement.
Récupération hybride : combiner les signaux
La recherche vectorielle est puissante, mais elle a des angles morts. Collez un code d'erreur exact comme ERR_CONNECTION_REFUSED_0x800 et la recherche de similarité peut renvoyer un guide de dépannage pour un module sans rapport parce que l'espace d'embedding les a regroupés à proximité. Les correspondances exactes sont importantes, et la recherche vectorielle seule peut les lisser.
La recherche par mots-clés avec BM25 résout magnifiquement le problème de la correspondance exacte. Mais elle s'étouffe face à la distance conceptuelle. Si un utilisateur pose une question sur la « dégradation des performances sous une charge lourde », BM25 passera à côté d'une note de diagnostic décrivant un « débit lent lors des pics de trafic » car il n'y a pas assez de chevauchement de mots-clés.
Nous avons cessé de choisir un camp pour commencer à exécuter les deux en parallèle. Les recherches vectorielles et par mots-clés renvoient chacune leurs propres listes classées. Nous les fusionnons via le Reciprocal Rank Fusion (RRF). Le RRF est simple et brutal dans son efficacité. Il évalue chaque document en fonction de sa position dans chaque liste. Les documents qui se situent près du sommet dans les deux systèmes bénéficient d'un boost massif. Les documents qu'un seul moteur apprécie conservent tout de même une place dans l'ensemble des candidats finaux.
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
