Accueil / Articles

Qualité RAG en temps d’indexation : types de fragments, nettoyage et étiquettes de récupération

Qualité du RAG en temps d’indexation : création de blocs de texte, de tableaux et d’images, suppression des bruits générés par le parseur, et ajout de labels de récupération rédigés par un LLM qui ont réduit d’environ la moitié les recherches infructueuses.

Éditeur : REACTAPP.TOP12 min de lecture2701 mots

Sources: The Part of RAG Where Quality Is Actually Won

Où une démo devient un système de production

Un analyseur RAG fournit une liste ordonnée de nœuds, et chaque nœud indique la section d’où il provient. Tout ce qui se passe jusqu’au moment où le modèle lit une requête relève des tâches effectuées au moment de l’indexation, et c’est cette étape qui distingue une démo d’un système pouvant fonctionner en production. Ce processus se divise en trois étapes : les nœuds sont transformés en blocs, qui existent en trois types chacun ayant son propre outil de création. Les blocs sont purgés des éléments que l’analyseur a extrait mais que le modèle n’a pas besoin de voir. Chaque bloc reçoit en outre une étiquette générée par un modèle de langage. Lors d’une évaluation interne sur des documents bancaires, ces étiquettes ont permis de réduire d’environ moitié le nombre de récupérations échouées, ce qui en fait le principal avantage de ce pipeline.

C’est aussi le domaine le moins glamour. Très peu de gens écrivent sur la suppression du bruit, et c’est précisément pour cette raison qu’il existe tant de solutions. Les chiffres ci-dessous proviennent d’une petite évaluation interne et non d’un test de référence rigoureux, et il convient de les interpréter en conséquence.

Trois types de blocs, trois outils de construction

Le pipeline naïf utilise un seul outil de segmentation : il divise le texte puis passe à l’étape suivante. Les documents réels contiennent au moins trois types de contenu qui se comportent de manière complètement différente. Un paragraphe en prose, un tableau de tarifs et une capture d’écran nécessitent chacun un traitement spécifique ; essayer de les traiter tous avec le même outil de segmentation est la cause de l’apparition de tableaux incomplets et d’étapes orphelines.

Les blocs de texte suivent une structure

La séparation selon le nombre de caractères n’est pas envisageable. Au lieu de cela, chaque ligne est d’abord saisie, que ce soit en tant que titre, en tant que étape numérotée ou en tant que phrase ordinaire, et les règles de fusion dépendent de ces types. Cela empêche qu’une étape numérotée ne soit accolée à un paragraphe sans rapport. Les textes trop longs sont tronqués à la fin des phrases, et couper au milieu d’une phrase n’est utilisé qu’en dernier recours. Les parties voisines se chevauchent légèrement, de sorte qu’une procédure qui franchit une frontière reste compréhensible de part et d’autre.

Les tableaux restent intacts

Les tableaux sont créés séparément, car leur sens réside dans leur mise en page. L’outil de création trouve la ligne de en-tête, la répète dans chaque partie si le tableau doit être divisé, et met de l’ordre dans les lignes. Il rejette également les tableaux ne contenant aucun contenu réel, le plus souvent le sommaire, que les analyseurs affichent sous forme d’un réseau ordonné de titres et de numéros de page.

La version précédente de ce système présentait un défaut précisément ici. Les tableaux des matières étaient détectés à l’aide d’un test rudimentaire, de nombreux enregistrements se terminant par un petit nombre. Les tableaux de tarifs correspondaient également à cette description, ce qui signifiait qu’un véritable calendrier des frais pouvait disparaître de l’index sans aucun avertissement. Le test de remplacement est plus ciblé : sur une véritable page de table des matières, les numéros de page ne font que monter, tandis que les montants des frais tels que 35 $, 30 $ et 7 $ descendent puis montent. Un tableau n’est considéré comme faisant partie de l’index que si les chiffres finaux ne descendent jamais. Désormais, les calendriers des frais restent en place, les pages de table des matières continuent d’apparaître, et une seule condition a permis d’éliminer le bug.

Les images sont vérifiées une par une

Chaque image peut coûter de l’argent, c’est pourquoi chacune essaie successivement trois méthodes avant de s’arrêter à la première qui fonctionne.

  1. Le chemin gratuit utilise le texte que le analyseur a déjà extrait. Si le document fournit une légende ou une annotation utilisable qui n’est pas un placeholder comme « image », ce texte est utilisé et aucun modèle de vision n’est appelé. Dans de nombreux documents, entre un tiers et la moitié des images empruntent cette voie, ce qui représente une véritable économie.
  2. Le chemin payant fait appel à un modèle de vision. Cela s’applique lorsque aucun texte utilisable n’est disponible et que l’image est plus grande qu’un diviseur minuscule. Un prompt bien formulé incite le modèle à effectuer une classification en premier. Si l’image est un logo ou un ornement, il répond par le mot DECORATIVE et le fragment est éliminé. Sinon, il remplit deux champs. SCREEN est une seule ligne indiquant à quoi sert l’écran, par exemple un écran d’entrée pour les communications sortantes des comptes professionnels, rédigée de manière à se situer proche des questions des utilisateurs dans l’espace vectoriel. CONTENT énumère chaque étiquette de champ, bouton et code visible, dans l’ordre de lecture, fournissant aux chercheurs les chaînes exactes qu’ils pourraient taper. Ainsi, une capture d’écran peut être trouvée selon son objet ainsi que selon son texte affiché.
  3. Le chemin de secours utilise le titre. Si les deux méthodes échouent mais que la section est connue, un espace réservé est stocké indiquant de quoi concerne l’image et où elle se trouve, afin qu’elle reste accessible via sa section au moment de la requête.

Il existe une petite faille dans la vérification des tailles. Les images très petites évitent l’appel à la vision grâce à un seuil de largeur et de hauteur, mais les deux dimensions doivent être faibles ; par conséquent, une ligne de séparation très fine et très large passe à travers et est néanmoins envoyée au modèle. Cela entraîne une légère perte financière pour la décoration sans affecter la précision, et cela figure sur la liste des tâches à effectuer.

Le diagramme montre les trois chemins d’image essayés dans l’ordre. Le modèle de vision s’exécute une fois au moment de l’indexation, et chaque requête ultérieure est gratuite.

Améliorer ce que le analyseur a bien géré

La fidélité est précisément ce qui rend les analyseurs gênants dans ce contexte. Ils extraitent tout le contenu d’une page : un tampon « CONFIDENTIEL » répété sur 200 pages, les numéros de page, les pieds de page relatifs aux droits d’auteur, une phrase coupée en deux par une saut de page. Chacun de ces éléments devient un bloc, et tous constituent des éléments superflus. Ces éléments superflus altèrent également le classement, car une requête de l’utilisateur peut tout aussi facilement aboutir à une publicité qu’à une procédure réelle.

Ainsi, une fois que ces blocs existent, ils sont soumis à une étape de nettoyage où chaque étape vise un type de contenu inutile réellement présent dans les documents réels. Dans l’ordre suivant :

  1. Le filtrage des éléments standard élimine les numéros de page simples, les tampons de version ainsi que les lignes de lien telles que « Contactez-nous » ou « Lire plus » situées en début de page. Le jeu de règles varie en fonction du type de fichier, car les PDF, les fichiers Word et les diapositives présentent chacun leur propre type d’éléments superflus.
  2. Éliminer les éléments répétés. Le texte qui apparaît de manière identique sur de nombreuses pages est considéré comme superflu. L’inscription « CONFIDENTIEL, À USAGE INTERNE SEUL » sur quinze pages est comptée et supprimée, tandis que « Voir page 3 » apparaissant deux fois sur une même page reste, car elle ne s’étend pas sur plusieurs pages.
  3. Réparer les ruptures anormales. Un segment qui commence au milieu d’une phrase ou avec un point d’interrogation isolé a été séparé au mauvais endroit. S’il peut être joint au segment précédent dans la même section, ils sont fusionnés. Sinon, il est marqué comme une continuation afin que son intitulé conserve ce contexte.
  4. Fusionner les fragments isolés. Un segment de 40 caractères resté seul à la frontière d’une section est absorbé par un segment voisin, mais uniquement si les deux appartiennent exactement à la même section et au même titre. Aucun fusionnement n’a lieu entre sections, et un bref élément vraiment indépendant est laissé tel quel.
  5. La déduplication fusionne une phrase répétée sous une même rubrique en un seul bloc. Si elle apparaît sous deux rubriques, les deux copies sont conservées, car elles contiennent des informations différentes grâce au chemin de la section associé. Les images sont totalement exemptées, car deux écrans ayant un aspect similaire restent deux captures d’écran distinctes.

Aucune de ces règles n’est complexe. Chacune a été élaborée en observant un document réel qui échappait à la version simplifiée, et ensemble elles expliquent pourquoi une requête est acheminée vers une procédure et non vers une ligne relative aux droits d’auteur.

L’étiquette de recherche

La étape la plus utile, et celle que la plupart des systèmes omettent, vient ensuite. Prenons un bloc simple comme celui-ci :

Saisissez le montant et appuyez sur F4.

Il contient son chemin de section, Wire Transfer > Initiation > Amount Entry, ce qui est déjà d’une grande aide. Avant d’être intégré, un modèle de langage rédige également une courte étiquette de récupération de 18 à 22 mots, qui est stockée avec le chunk. Pour ce chunk, l’étiquette ressemble à ceci :

Étape de la procédure : saisissez le montant du virement, puis appuyez sur F4 pour confirmer et lancer le virement par virement bancaire.

Considérez l’effet. Sans étiquette, l’instruction brute se trouve dans l’espace d’incorporation à côté de toutes les autres étapes indiquant de saisir une valeur et d’appuyer sur une touche. Avec une étiquette, elle se situe près des virements et des confirmations, qui sont des termes que l’utilisateur réel utiliserait. Une question reformulée telle que « comment puis-je confirmer le montant d’un virement » trouve alors ce fragment. Lors de l’évaluation interne, les étiquettes ont réduit d’environ la moitié des erreurs de récupération. Il s’agissait d’un petit test sur un seul corpus, il convient donc de le considérer comme un indice fort et non comme une constante universelle.

Cette technique dérive de l’approche Contextual Retrieval d’Anthropic, qui a montré que l’ajout d’une phrase contextuelle spécifique à chaque bloc avant son indexation réduit considérablement les erreurs de récupération. Ici, elle a été adaptée pour des contenus où des identifiants précis sont essentiels. Les codes de transaction et les raccourcis de fonction doivent être copiés tels quels, sans jamais être reformulés, et chaque étiquette commence par un mot indiquant son rôle, comme « Étape de procédure » ou « Table de référence », afin que les blocs soient regroupés avec les types de questions appropriés.

Ce que voit l’auteur des étiquettes

Une étiquette n’est bonne que dans la mesure où le contexte fourni au modèle qui la crée est de qualité. Le bloc seul ne suffit pas, car c’est précisément ce qui a perdu son sens lorsqu’il a été extrait de la page. Ainsi, chaque demande d’étiquette comprend plusieurs éléments :

  • le texte du bloc lui-même
  • le chemin de la section et son intitulé
  • les en-têtes de colonne, lorsque le bloc est une table
  • un bloc précédent et un bloc suivant, afin de montrer ce qui l’entoure
  • un bref résumé du document dans son ensemble, pour avoir une vue d’ensemble

Le résumé du document provient d’une requête distincte effectuée avant le début de l’étiquetage. Il sélectionne des extraits du début, du milieu et de la fin du document, ainsi qu’une table et une image, et renvoie deux ou trois phrases indiquant le type de document, son public cible ainsi que les produits et codes concernés. Chaque demande d’étiquetage inclut ensuite ce résumé. L’expression « entrer le montant » doit être étiquetée différemment dans un manuel de virement bancaire qu’en une fiche d’information sur un prêt, et c’est grâce à ce résumé que le modèle sait quel document il est en train de lire.

Les voisins méritent une analyse plus approfondie, car « précédent et suivant » ne signifie pas nécessairement que les deux éléments soient physiquement adjacents. L’élément voisin peut appartenir à une procédure complètement différente. Ainsi, les voisins sont sélectionnés en fonction de leur pertinence, en trois niveaux, en prenant le premier qui correspond :

  1. l’élément le plus proche dans le même chemin de section
  2. sinon, l’élément le plus proche dans la même section parente
  3. sinon, l’élément le plus proche sous le même titre

Les candidats sont examinés à partir de l’élément actuel, et tout ce qui se trouve en dehors de la section appropriée est ignoré. Le modèle voit donc l’élément suivant qui appartient réellement à celui-ci. Chaque voisin est tronqué à quelques centaines de caractères, ce qui suffit comme contexte sans le coût lié au transfert d’éléments complets.

Pour l’exemple F4, le modèle constate que le bloc précédent dans sa section portait sur le choix d’un bénéficiaire et que le suivant concerne la confirmation du virement. Cela lui permet de générer une étiquette précise concernant la saisie du montant du virement, plutôt qu’un résumé vague. Des voisins bien choisis permettent aux étiquettes de rester exactes.

La figure montre le modèle d’étiquetage qui lit ses voisins de la même section, puis écrit une phrase spécifique, stockée à côté du bloc, et la transmet aux deux voies de recherche.

Pourquoi c’est abordable

Trois caractéristiques rendent cette démarche pratique sans être coûteuse.

  • Coût unique. L’étiquette est générée lors de l’indexation et enregistrée, de sorte que les requêtes n’en paient jamais le coût.
  • Baix coût unitaire. Un modèle petit et rapide étiquette les blocs par groupes de 25, plusieurs groupes fonctionnant en même temps, et les dépenses restent bien en deçà d’un centime par bloc.
  • Échec élégant. Un lot dont les étiquetages échouent est simplement indexé sans étiquette tandis que l’exécution se poursuit. Ces parties conservent leur texte et leur chemin de section, mais manquent simplement du traitement de boost ; ainsi, une panne affecte la qualité sans jamais interrompre l’indexation.

L’étiquette se trouve dans son propre champ, séparée du corps du bloc. Cette séparation est importante plus tard, au moment de la recherche, car l’étiquette reçoit un poids supplémentaire dans la recherche par mots-clés. L’idée pour l’instant est que le calcul le plus coûteux est effectué avant toute recherche, et c’est ce qui permet aux requêtes paraphrasées de fonctionner.

Déterminisme et le prompt versionné

La génération des étiquettes est déterministe : une température nulle et une graine fixe signifient que le ré-indexage d’un document produit les mêmes étiquettes. C’est ce qui sous-tend l’affirmation selon laquelle le coût n’est payé qu’une seule fois. Le ré-indexage ne remet jamais en ordre les vecteurs, il les recrée simplement.

Le déterminisme dépend également de la versionnement du prompt. Une graine ne produit un résultat identique que pour une entrée identique, et le texte des instructions fait partie de cette entrée. En modifiant le prompt d’enrichissement entre les versions, chaque réindexation ultérieure génère des étiquettes différentes, de sorte que l’index se divise silencieusement en deux générations sans qu’aucun signal ne l’indique. Le prompt est donc enregistré avec une étiquette de version, chargé grâce à cette étiquette lors des indexations, et modifié uniquement par une mise à jour intentionnelle et traçable. La reproductibilité entre les versions doit être assurée, car une simple graine ne suffit pas à y parvenir.

Incarnation et stockage

Après le nettoyage, le placement et l’étiquetage, la dernière étape lors de l’indexation consiste à générer le vecteur. Un modèle d’incarnation couvre tous les types de fragments : text-embedding-3-large, réduit à 1536 dimensions, de sorte que le texte, les tableaux et les captures d’écran sont tous évalués par rapport à une requête dans le même espace.

Ce qui est introduit dans l’embedder est une chaîne de caractères structurée selon une séquence fixe : préfixe structurel, étiquette, puis texte principal. Le préfixe s’appuie sur des informations déjà connues concernant le fragment. Celles-ci incluent le type de document déduit du chemin de la section, comme un tableau des frais ou une procédure de virement bancaire, ainsi que le titre et le chemin de la section ; pour les images, il s’agit de la page et de l’emplacement. En étiquetant un fragment de type procédure comme telle, on le rapproche des questions commençant par « comment faire quelque chose ».

Le chemin de la section est également ajouté au texte à ce stade, c’est là que la version précédente présentait une erreur. À l’époque, le préfixe était inscrit dans le chunk lors du nettoyage, et toute modification ultérieure pouvait l’ajouter une seconde fois, endommageant ainsi discrètement le vecteur. Le code actuel supprime tout préfixe stocké, puis en crée un nouveau au moment de l’incorporation. Il existe en un seul endroit et est ajouté une seule fois, juste avant que le modèle ne le lise, ce qui a éliminé toute une série d’erreurs silencieuses liées à des préfixes doubles.

Le vecteur final est enregistré dans le stockage aux côtés du corps du chunk, de l’étiquette, des métadonnées structurelles ainsi que d’une colonne de texte intégral que la recherche par mots-clés utilise. Ensuite, le document devient actif. Si une étape du pipeline échoue, le document est marqué comme rejeté et la raison est conservée aux fins de débogage, tandis qu’une version active précédente reste consultable tout au long du temps. Un document partiellement indexé n’est jamais visible.

À quoi ressemble une ligne terminée

Pour rendre cela concret, voici le bloc « Entrez le montant et appuyez sur F4 » tel qu’il apparaît dans le magasin. Chaque champ provenant de ce bloc a été généré lors d’une étape précédente.

text              : "Enter amount and press F4."
                     (built + cleaned + section-prefixed)
retrieval_context : "Procedure step entering the amount
                     and pressing F4 during wire transfer
                     initiation."          (the label)
chunk_type        : TEXT
section_path      : "Wire Transfer > Initiation >
                     Amount Entry"         (Part 2)
preceding_heading : "Amount Entry"
table_headers     : null                  (TABLE only)
page_start / end  : 12 / 12
image_object_key  : null                  (IMAGE only)
embedding_model   : text-embedding-3-large
embedding_dim     : 1536
embedding         : [ ...1536 floats... ] (body+label)
text_search_vector: label weighted high, body lower

Dans un groupe de tableaux, les en-têtes de colonne remplacent les parties où un groupe de texte contient des valeurs nulles. Un groupe d’images stocke une clé de blob, un type MIME et une position sur la page, avec le contenu dump dans text et la ligne SCREEN dans retrieval_context. Ces trois types partagent le même format de ligne. La récupération s’appuie sur deux colonnes : embedding pour la recherche vectorielle et text_search_vector pour la recherche par mots-clés, chacune étant dérivée du corps du texte ainsi que de l’étiquette associée. Les autres colonnes permettent de localiser le groupe, de l’afficher ou de le restreindre au bon utilisateur.

C’est là l’ensemble du résultat obtenu lors de la création de l’index : des lignes comme celles-ci, une par groupe, nettoyées, étiquetées et vectorisées, en attente de la première requête.

Lectures complémentaires

  • Taille des blocs et chevauchement dans RAG : pas, limites de tokens et joints — Comment diviser un livre long pour RAG : choisir la taille et le chevauchement, comprendre le pas, rester en deçà de la limite de tokens d’encodage, stocker des métadonnées et observer le chevauchement dans une division réelle. —