Huit meilleures pratiques RAG testées sur 746 pages de manuels.
Quelles optimisations populaires de RAG ont résisté aux tests sur trois manuels de produits : suppression des traits d’union, taille des blocs, routage, seuils d’escalade et réclassement.
Sources: I Measured Every RAG “Best Practice” on 746 Pages of Product Manuals. Only Four Survived.
Un bot de support développé à partir de trois PDF réels — un manuel d’utilisation du véhicule électrique de 408 pages, un guide des programmes télévisés de 329 pages et un dépliant de 9 pages contenant une souris sans fil — est devenu un terrain d’essai pour huit optimisations populaires de type RAG. La règle était stricte : rien n’était adopté tant qu’une mesure effectuée sur ce corpus, évaluée à l’aide d’une clé de réponses étiquetée, ne montrait pas son efficacité. La littérature et les habitudes ne comptaient pas.
Le résultat a été une augmentation de la précision de 72 % à 89 %, tandis que le nombre de tokens de requête par demande est passé de 14 à 57 %. Cela a été obtenu grâce à quatre modifications, et non huit. Deux des idées abandonnées ont même eu un effet négatif, et une optimisation mise en œuvre s’est avérée ne jamais être exécutée. Toute cette étude n’a coûté que 0,015 dollar en appels API.
Voici comment chaque idée s’est comportée. L’élément le plus précieux de la liste n’est même pas une technique RAG.
Un bug plus grave que n’importe quel hyperparamètre
Avant d’ajuster le processus de récupération, vérifiez ce qui est réellement parvenu à l’index. Tout le monde confirme que le PDF a été analysé. La meilleure question est de savoir dans quel état se trouve le texte.
Une boucle infinie permet de y répondre :
SOFT = "" # U+00AD, the soft hyphen
for manual in manuals:
print(manual.name, extract(manual).count(SOFT))
Le manuel de la voiture a renvoyé 2 272.
Un trait souple est une instruction du compositionneur au rendu : séparer le mot ici si la ligne est pleine. Un lecteur PDF l’obéit et le cache, de sorte que l’œil ne le voit jamais, mais un extracteur de texte le transmet tel quel. La page affiche un mot propre, tandis que l’index reçoit des chaînes comme ve, trait souple, saut de ligne, hicle.
Dans ce manuel, 999 mots distincts ont été divisés en deux, y compris, à plusieurs reprises, le nom commun le plus important de tout manuel de voiture. Le mot Vehicle est apparu 1 486 fois en tant que mot entier, alors qu’il aurait dû apparaître 1 580 fois. Quatre-vingt-quatre occurrences étaient inaccessibles via la recherche.
Cela endommage simultanément chaque chemin de récupération. La recherche par mot-clé correspond à des chaînes littérales, et « hicle » n’est pas « vehicle », sans aucun point de crédit partiel. La recherche vectorielle se révèle pire que prévu, car les modèles d’embedding décomposent le texte en fragments sous-mots, et un fragment comme « hicle » se brise en morceaux dépourvus de sens. Même une correspondance chanceuse insère un contexte altéré dans la requête.
La correction se fait en une seule ligne, avec un piège à côté. Le texte justifié se brise également aux traits d’union réels. Un mot corrompu comme « cus-tomer » devrait se reconstituer en « customer », mais « toll-free » doit rester écrit avec un trait d’union, et il faut un dictionnaire pour les distinguer. Il y avait 95 de ces cas, et tous sont restés corrompus, avec le nombre noté. Un manque connu de 95 mots constitue un artefact très différent d’une supposition silencieuse qui corrompt discrètement un corpus. Aucune taille de chunk ne permet de récupérer un mot que l’index n’a jamais considéré comme tel.
La meilleure taille de chunk est un piège
Des blocs plus grands contiennent plus de contexte et correspondent donc à davantage de questions, mais ils entraînent également l’inclusion permanente de texte superflu et irrelevant dans chaque requête. Une superposition existe afin qu’une réponse située à une frontière ne soit pas coupée en deux.
Neuf configurations ont été testées à l’aide de la clé de réponse. La précision se mesure sans aucun coût, tandis que c’est le coût qui est à payer pour cela.
La superposition n’a jamais justifié son utilisation, du moins pas de la manière suggérée par une première lecture. Avec 700 caractères, l’élimination de la superposition a permis d’obtenir 11 points de plus, ce qui semble défavorable jusqu’à ce que l’autre combinaison soit examinée : avec 1800 caractères, une superposition nulle, 100 ou 200 a donné exactement 89 % de résultats. Une comparaison indique que la superposition est coûteuse, tandis qu’une autre affirme qu’elle n’a aucun effet.
Ce qui explique ces deux résultats est une statistique qui est presque restée invisible. Dans toutes les configurations, même les plus modestes, chaque réponse tenait entièrement dans un seul bloc, sans jamais être coupée à une frontière. La superposition servait de protection contre un problème absent dans ce corpus, ce qui faisait que sa valeur mesurée oscillait entre zéro et négatif. Vérifier avec quelle fréquence les réponses franchissent réellement une frontière devrait précéder le paiement de cette prime.
Puis vient le piège. Le meilleur score, 89 %, a été obtenu avec 1400 caractères et 200 unités de superposition. L’opter pour ce score signifie envoyer 1 283 tokens par requête au lieu de 750, ce qui représente une augmentation de 71 % pour chaque requête tout au long de la durée de vie du produit. Avec 18 questions dans le jeu de test, 89 % correspond à 16 réponses correctes, tandis que 83 % en correspond à 15. La meilleure configuration devance la moins chère d’une question exacte.
Ainsi, le gagnant ne doit pas être déterminé par défaut. Il faut choisir la configuration la moins chère dont le score ne peut pas être distingué de celui du gagnant, ce qui, ici, signifiait tout score inférieur ou égal à 8 points. Six des neuf configurations se sont retrouvées à égalité selon cette règle, et la moins chère a gagné. La précision est passée de 72 % à 83 %, avec en plus une facture moindre en ressources, ce qui représentait le premier « déjeuner gratuit » du projet, obtenu en refusant d’optimiser la métrique mesurée.
Un filtre qui a coûté 22 points
Le corpus couvre trois produits. Une question concernant une souris n’a aucune raison de rechercher dans un manuel de voiture de 408 pages, puisque les deux autres documents ne font qu’augmenter les chances d’erreur. Le plan était de détecter le produit et de limiter la recherche à ce manuel uniquement.
Un classificateur s’en est chargé. Il a choisi correctement 78 % du temps en moins de 4 ms, sans aucun coût, ce qui représente une majorité solide par rapport à une recherche sur trois fois plus de texte. Pourtant, la précision a chuté de 22 points.
Il a fallu un temps exagérément long pour observer ce mécanisme. Un filtre incorrect ne fait pas descendre la réponse correcte dans le classement ; il la supprime plutôt. Sans filtrage, une recherche confuse dans les trois manuels permet encore de trouver le passage correct, peut-être à la position 4 plutôt qu’à 1, et un outil de réclassement peut alors le récupérer. Avec filtrage, une question mal orientée cherche dans le mauvais document, et le fragment correct n’est pas classé en bas du classement parce qu’il n’existe pas dans l’ensemble des documents candidats.
Un classificateur qui a raison 78 % du temps ne compromet donc pas légèrement la qualité. Il impose un plafond strict de 78 % à tout ce qui suit, quel que soit le niveau de qualité du reste du processus.
La solution n’est pas un classificateur plus précis, mais plutôt la possibilité de s’abstenir : appliquer le filtre uniquement lorsqu’on est sûr, et rechercher tous les produits lorsque les deux meilleurs résultats ont des scores proches. Dans une question comme « Comment le connecter par Bluetooth ? », le mot « le » a beaucoup d’importance, et le classificateur est tout aussi incertain que n’importe qui.
Ou bien éviter complètement l’inférence
Le routeur le moins cher se présente sous forme de liste déroulante. Pour un outil de ce type, demander de savoir à quel produit concerne la question ne coûte qu’un seul clic, clic que l’utilisateur effectue de toute façon en ouvrant la messagerie depuis une page d’aide du produit. Cela résout instantanément une ambiguïté que le classificateur ne peut que deviner, et le filtre est toujours correct.
Les chiffres sont exacts. Le classificateur a mal dirigé 4 des 18 questions. La recherche sans filtre a trouvé 15 réponses, celle avec filtre en a trouvé 11, et chaque mauvaise orientation a transformé une réponse correcte en une fausse. En éliminant ces erreurs de redirection, la précision reste inchangée, tandis que la précision promise par le filtre est également atteinte.
L’optimisation qui n’a jamais été mise en œuvre
Cette partie serait tentante à supprimer pour des raisons esthétiques.
Le plan consistait à utiliser un modèle bon marché de 20 milliards de paramètres lorsque la récupération des informations semblait fiable, et à passer à un modèle de 120 milliards de paramètres en cas de doute. Les réponses incorrectes ont tendance à se regrouper là où la récupération des informations est incertaine, et le réordonneur produit déjà un score de confiance, donc cela ne coûte rien en ressources.
Le seuil a été fixé à 0,0, ce qui semblait conforme aux principes : un encodeur croisé émet un logit, sigmoid(0) vaut 0,5, et une valeur inférieure à zéro indique que le bloc principal est plus probablement irrelevante que pertinente. L’affichage de la distribution réelle montrait cependant autre chose : les scores les plus élevés observés présentaient une valeur minimale de 3,24, une médiane de 8,29 et une valeur maximale de 10,00.
Le seuil se situait en dessous du score le plus bas jamais enregistré, ce qui signifie qu’il n’a jamais été déclenché, pas une seule fois. La politique de progression était identique à celle qui consiste toujours à utiliser le modèle bon marché, avec en plus des coûts supplémentaires sous forme d’une ligne dans le tableau des résultats qui ne mesurait rien tout en ayant l’air d’une découverte. Un seuil nul n’est pas un paramètre par défaut neutre ; c’est plutôt une affirmation concernant une distribution que personne n’a examinée. Chaque seuil absolu de logit présente ce défaut, car la distribution change lorsque le réordonneur ou le corpus est modifié.
La solution consistait à calculer ce nombre. Il suffisait d’exécuter uniquement les deux points d’entrée correspondant aux scénarios « réel », « toujours bon marché » et « toujours cher », en enregistrant la note, la justesse et le coût de chaque question. Chaque seuil intermédiaire représentait alors une reprise des données déjà en mémoire, sans appel API supplémentaire, et impliquait un balayage exhaustif plutôt qu’un balayage aléatoire.
Cette analyse a révélé une surprise : chaque seuil ne modifie que le coût, tandis que la précision reste inchangée. Le diagnostic explique pourquoi. Sur douze questions, seule une a pu être récupérée, ce qui signifie que le modèle bon marché a échoué tandis que le modèle coûteux a réussi. Cette question a obtenu 10,00, le score le plus élevé, alors que les questions déjà correctement résolues par le modèle bon marché présentaient une médiane de 8,29. La seule erreur méritant une action corrective était celle pour laquelle le système de récupération était le plus sûr, de sorte qu’aucune règle basée sur une action en cas d’incertitude ne peut la détecter à aucun seuil. Le signal de routage échoue donc à prédire ce pour quoi il était conçu.
Un effet secondaire est également apparu. L’exécution en temps réel indiquait 92 %, tandis que la réexécution des lignes déjà payées prévoyait 83 %. La différence ne provient pas du seuil utilisé. Elle vient du fait que l’algorithme d’évaluation évalue différemment des réponses identiques lors d’une seconde analyse, évitant ainsi d’annoncer une augmentation de 9 points qui n’est en réalité que du bruit.
Le changement qui a rapporté le double
Le réclassement représentait la solution la plus efficace, et sa raison mérite à la fois d’être comprise et d’être adoptée.
Une recherche vectorielle incorpore la question ainsi que chaque fragment séparément, à l’aide d’un modèle qui ne les considère jamais en tant que paire. Un encodeur croisé, quant à lui, traite la paire comme une seule entrée et l’évalue directement, ce qui est bien plus précis, mais trop lent pour être appliqué à un corpus entier. Le processus récupère donc 25 candidats à moindre coût, les réordonne et conserve les 4 meilleurs.
Le taux de rappel est passé de 83 % à 89 %. Le chiffre le plus révélateur est la position obtenue. Avant le réclassement, le groupe pertinent se situait généralement en deuxième ou troisième position, donc cinq éléments étaient envoyés par précaution. Après cela, il était généralement en première position, et quatre éléments suffisaient. En se contentant d’une précision de base, on peut réduire le nombre de groupes à deux, ce qui correspond à 57 % de tokens en moins.
Puisque l’ordre améliore les résultats, moins de groupes atteignent la même précision, et ce sont les tokens de la requête qui sont facturés. Ce changement permet d’obtenir une meilleure précision tout en bénéficiant d’une remise. Son coût est de 73 ms d’utilisation du CPU local, contre 1 252 ms pour une appel à un LLM, une différence que personne ne remarque.
Ce qui reste inchangé
- Examinez l’entrée avant d’utiliser le système de récupération. Une simple boucle peu coûteuse permet soit de détecter un problème comme celui des traits d’union, soit de confirmer qu’il n’y a pas de limite, ce qui rend les dépenses ultérieures plus sûres.
- Une configuration par défaut correspond aux mesures d’une autre personne, ou à celles de personne. Un chevauchement de 100 % n’a jamais été utile, et un seuil de 0,0 % n’a jamais déclenché d’action ; en environnement de production, aucun de ces deux cas ne se manifesterait. Le seul symptôme serait une facture plus élevée sans explication.
- Examinez comment se comporte une optimisation dans les cas où elle commet des erreurs, et non seulement la fréquence à laquelle elle erre. Un filtre qui rate 22 % du temps mais se contente de réorganiser les résultats est acceptable. Un filtre qui rate 22 % du temps et rend la réponse inaccessible ne l’est pas.
- Une idée rejetée est un résultat. Quatre idées restées non déployées représentent quatre éléments supplémentaires sans maintenance, suivi, gestion des versions ou alertes en pleine nuit.
Il y a une précision importante à faire. La clé de réponses contient 18 paires de questions et de citations vérifiées, ce qui signifie qu’une seule question vaut 5,6 points. Le problème de défaillance lié aux 22 points concerne quatre questions, ce qui est suffisant pour agir. Le résultat d’overlap de 11 points concerne deux questions, ce qui n’indique qu’une orientation au mieux. Une clé de réponses basée sur les propres données d’une équipe doit primer sur la confiance envers les chiffres de quiconque. Le traitement complet prend environ cinq centièmes de seconde sur le CPU d’un ordinateur portable, avec une limite budgétaire stricte qui entraîne une exception plutôt que de figurer ce montant sur une facture.