La plupart des équipes d'ingénierie se heurtent au même mur avec la génération augmentée par récupération (RAG). Elles suivent le manuel des tutoriels : découper les documents en blocs (chunks) fixes de cinq cent douze ou mille vingt-quatre tokens, les passer par un seul modèle d'embedding, et interroger une base de données vectorielle avec une simple recherche top-k. Sur une présentation, cela semble solide. En production, tout s'effondre.
Les blocs fixes ne tiennent pas compte du contenu. Ils n'hésiteront pas à diviser un contrat juridique au milieu d'une phrase, laissant des clauses de responsabilité suspendues entre deux segments de texte sans rapport. Ils peuvent condenser toute la description d'un point de terminaison (endpoint) d'API dans un seul bloc hypertrophié, si large que le paramètre spécifique demandé par l'utilisateur se noie dans le bruit. Et lorsque la récupération est lente, chaque milliseconde de latence impacte directement l'expérience utilisateur. Nous l'avons appris à nos dépens. Nous avons alors démantelé notre couche de récupération pour la reconstruire. Notre rappel (recall) à dix est passé de 78 % à 95 %. La latence n'a pas augmenté. Elle s'est effondrée.
Le problème du RAG "copier-coller"
La pile RAG standard est devenue une sorte de configuration par défaut. Petits blocs, un seul modèle d'embedding, recherche vectorielle, et voilà. Cette approche survit à une démo parce que les démos utilisent des questions claires et des documents bien structurés. Les données de production ne sont jamais bien structurées.
Les documents juridiques ont une structure hiérarchique. Les sections contiennent des sous-sections. Les sous-sections contiennent des clauses. Si vous les découpez avec un compteur de tokens rudimentaire, vous détruisez les relations mêmes sur lesquelles le modèle doit raisonner. La documentation d'API a également une structure, mais elle est différente. Une signature de fonction, ses paramètres, sa valeur de retour et un exemple d'utilisation forment une unité logique. Si vous forcez cela dans une fenêtre de tokens fixe, vous allez soit tronquer l'exemple, soit remplir le bloc avec des fonctions sans rapport. Les tickets de support sont désordonnés, conversationnels et sujets à des changements de sujet soudains. Les wikis sont vastes et font l'objet de renvois croisés. Une seule stratégie de découpage ne peut pas servir à tout cela, et pourtant, les équipes déploient systématiquement cette approche. Nous avons cessé de prétendre que c'était possible.
Découpage stratégique : adapter la méthode au contenu
Nous sommes passés au découpage sensible au contenu (content-aware chunking). Pour les documents juridiques, nous utilisons un découpage récursif qui respecte la hiérarchie du document. Cela permet de garder les clauses intactes et de préserver les relations parent-enfant entre les sections. Pour la documentation d'API, nous avons conçu un découpage sensible aux fonctions (function-aware chunking) qui traite chaque fonction ou endpoint comme une limite. Si la description d'un paramètre est longue, le bloc s'étend autour de cette fonction, et non autour d'une limite de tokens. Pour les tickets de support, nous utilisons un découpage sémantique qui détecte les limites naturelles des sujets. Lorsqu'un client passe soudainement d'une plainte de facturation à un bug technique, la coupure se fait à ce moment précis. Pour les wikis et les bases de connaissances non structurées, nous utilisons un découpage agentique (agentic chunking) où un LLM léger évalue le texte et décide de l'emplacement d'une limite pertinente. C'est plus lent à mettre en place qu'un découpage par caractères, mais c'est ce qui fait la différence entre une récupération qui fonctionne et une récupération qui devine.
Récupération hybride : pourquoi la recherche vectorielle seule ne suffit pas
La recherche vectorielle comprend le sens, mais elle peut manquer des correspondances exactes. Si un utilisateur colle un code d'erreur tel que ERR_CONNECTION_RESET_0x5F3, la similarité sémantique pourrait le classer en dessous de paragraphes qui discutent simplement des erreurs réseau en général. BM25, en revanche, trouve les chaînes exactes mais manque la parenté conceptuelle. Vous avez besoin des deux.
Nous exécutons la recherche vectorielle et BM25 en parallèle. Ensuite, nous combinons les résultats avec le Reciprocal Rank Fusion (RRF), qui normalise les scores des deux espaces de recherche différents sans les forcer à la même échelle. Après la fusion, nous envoyons les meilleurs candidats à travers un ré-classeur (reranker) cross-encoder. Cela ajoute une légère latence, mais le gain de précision est significatif. Le reranker lit la requête et chaque candidat ensemble et attribue un score de pertinence bien plus précis que la similarité cosinus de l'embedding initial. En pratique, cette combinaison capture les codes d'erreur exacts que la recherche vectorielle pure manque, tout en faisant remonter les étapes de dépannage conceptuellement liées que la recherche par mots-clés ignorerait.
Expansion de requête : corriger l'entrée utilisateur avant qu'elle n'atteigne l'index
Les utilisateurs n'écrivent pas des requêtes de recherche parfaites. Ils posent des questions à étapes multiples (multi-hop), comme « pourquoi mon dernier déploiement a-t-il échoué et comment puis-je l'annuler ? », ce qui nécessite de trouver deux corps de connaissances distincts et de les relier. Ou ils posent des questions vagues qui correspondent mal à l'index.
Nous transformons les requêtes avant de lancer la recherche. Une question à étapes multiples (multi-hop) est décomposée en sous-questions. Une intention vague est développée en plusieurs requêtes de recherche spécifiques. Nous avons constaté qu'en développant une seule requête utilisateur en cinq requêtes de recherche distinctes, le rappel (recall) peut passer de 78 % à 96 %. Il ne s'agit pas de pousser l'LLM davantage par le prompting. Il s'agit de donner au système de récupération plus de chances de trouver le bon contexte. Chaque requête générée capture un angle ou une terminologie différente, et la fusion des résultats permet d'obtenir une vision complète.
Optimisation bayésienne : arrêtez de deviner
Une fois que vous disposez de multiples stratégies de découpage (chunking), d'une recherche hybride et d'une expansion de requêtes, vous êtes confronté à un nouveau problème. Il y a trop de paramètres à ajuster. La taille des segments (chunk size), le pourcentage de chevauchement (overlap), le poids vectoriel par rapport au poids BM25, les seuils de réordonnancement (reranking) et les valeurs top-k interagissent tous de manière non linéaire. Le réglage manuel devient un jeu de devinettes.
Nous avons arrêté de deviner. Nous traitons le
