Comment déboguer une récupération vide lorsque le système RAG ne renvoie rien.
Un système RAG qui ne renvoie rien, débogué étape par étape : ingestion, seuils de score, filtres, réécriture des requêtes, recherche hybride, réclassement et solutions de secours.
La génération augmentée par récupération fournit à un modèle de langage des connaissances externes, ce qui lui permet de ne pas se fier uniquement aux données d’entraînement pour répondre, ce qui signifie que la réponse finale n’est bonne que dans la mesure où les informations récupérées le sont. Lorsqu’une recherche ne produit rien d’utile, le modèle dispose de peu ou pas de preuves. Il peut alors recourir à une réponse générique, refuser de répondre ou inventer des détails. Ce qui suit explique d’où proviennent des résultats vides ou presque vides et comment les identifier.
La récupération peut échouer à de nombreux niveaux
Un résultat vide n’est pas une seule forme d’échec. Un candidat peut disparaître pendant l’ingestion, l’encodage, la recherche vectorielle, le filtrage des métadonnées, le calcul des seuils ou le classement. Le débogage est plus rapide lorsque l’on examine chaque étape séparément et que l’on cesse de considérer l’ensemble du système RAG comme une boîte noire.
1. Commencer par l’indexation et les encodages
Avant de toucher au système de récupération, assurez-vous que la base de connaissances contient bien ce que vous pensez qu’elle contient. Une exécution réussie de l’ingestion montre une progression logique des documents sources vers du texte analysé, puis vers des blocs, des embeddings et des vecteurs stockés. Si 2 000 blocs ont été générés mais que seuls 200 vecteurs ont été enregistrés, aucune mise au point du système de récupération ne sera utile.
- Ouvrez manuellement un petit nombre de blocs stockés. Ils doivent contenir du texte lisible, des identifiants stables, des embeddings ainsi que tous les métadonnées sur lesquels vos filtres s’appuient.
- Utilisez les mêmes modèles et dimensions d’embeddings pour les documents et pour les requêtes des utilisateurs. Changer le modèle de requête sans reconstruire l’index fait perdre tout sens aux scores de similarité.
- Évaluez également le parseur. Les PDF, les scans, les tableaux et les pages HTML peuvent se transformer en blocs vides ou corrompus, même si la tâche indique qu’elle a réussi.
2. Examinez les seuils, la signification des scores et les filtres
Des seuils de score agressifs sont une cause fréquente d’absence de résultats. Les scores d’incorporation ne représentent pas des pourcentages de confiance, et la plage utile varie en fonction du modèle, de la métrique de distance, des données et de la base de données vectorielle. Lors du débogage, affichez les 10 ou 20 meilleurs candidats sans aucun seuil et consultez leurs scores bruts :
results = vector_store.search(query_embedding, top_k=10)
for r in results:
print(r.score, r.metadata.get('source'))
Vérifiez ensuite dans quelle direction s’affichent les scores. Certaines API retournent une similarité cosinus, où plus la valeur est élevée, mieux c’est. D’autres retournent la distance cosinus ou euclidienne, où plus la valeur est faible, mieux c’est. Utiliser la mauvaise méthode de comparaison élimine silencieusement vos meilleurs candidats et conserve ceux de moindre qualité. En règle générale, ne définez aucune condition de seuil avant d’avoir confirmé la convention de notation de la base de données et d’avoir examiné les distributions réelles des scores provenant de requêtes représentatives.
Les filtres peuvent éliminer de bons résultats
Le filtrage des métadonnées est essentiel pour les systèmes multi-locataires, les permissions, les plages de produits, les dates, les langues et les types de documents ; il fait également partie des moyens les plus simples de se retrouver avec un ensemble vide. Les causes typiques incluent la comparaison de chaînes de caractères avec des entiers, une capitalisation incohérente, des enregistrements anciens manquant de métadonnées, des IDs de locataire incorrects, ainsi que des filtres qui sont chacun valides mais impossibles à appliquer ensemble.
3. Évaluation de la réécriture de la requête et de la qualité des blocs
La réécriture d’une requête peut aider à la récupération des informations, mais elle peut aussi enlever le terme précis qui permettait à la requête d’être trouvée. Imaginez quelqu’un qui demande comment réinitialiser l’X500, tandis que l’outil de réécriture transforme cette question en « restaurer la configuration par défaut pour la série X ». Si le manuel doit s’intituler « Réinitialiser l’X500 », l’expression utilisée par l’utilisateur correspondra probablement mieux. Enregistrez à la fois la requête originale et celle qui a été réécrite, puis testez chacune d’elles. Pour les identifiants exacts, les codes d’erreur, les noms de produits et les paramètres API, conservez les termes littéraux même si le reste de la requête est normalisé.
Le découpage échoue de manière similaire. Un titre tel que « Réinitialiser le routeur X500 » ne doit pas être séparé des instructions qui le suivent. Découpez le document en fonction de sa structure afin que le titre, ses paragraphes, ses éléments de liste et le contexte environnant restent unis. La taille idéale d’un bloc dépend de la charge de travail, bien qu’une fourchette de départ raisonnable soit d’environ 500 à 1 000 tokens avec une légère superposition, suivie d’une évaluation sur des requêtes réelles.
Que mesurer : prenez un petit ensemble de requêtes étiquetées et notez si la source attendue apparaît parmi les résultats top-k. Ce test axé sur le taux de rappel est préférable à la sélection aléatoire des tailles de blocs ou des seuils.
4. Combiner la recherche lexicale et la recherche vectorielle
La recherche vectorielle se révèle efficace lorsque l’utilisateur et le document expriment la même idée de manière différente. La récupération lexicale, telle que BM25, est utile lorsque l’exactitude des tokens est cruciale. Comme les requêtes complexes sont rarement de type unique, les systèmes en production utilisent souvent les deux méthodes. BM25 a tendance à être préférable pour des éléments tels que les codes d’erreur (par exemple ERR-XK-1042), les références de produits, les numéros de facture, les noms de produits, les noms de classes, les clés de configuration et les noms de paramètres API. La recherche vectorielle permet de trouver des correspondances conceptuelles proches, tandis que BM25 garantit que les preuves lexicales exactes ne soient pas négligées. Une conception typique commence par récupérer un ensemble large de candidats avant d’appliquer un algorithme de rérankage plus poussé pour améliorer la précision.
5. Récupérer de manière large, puis réranker
Le récupérateur de première étape et le réclassificateur ont des fonctions distinctes. Le récupérateur doit maximiser le taux de rappel en collectant tout ce qui pourrait répondre à la question. Le réclassificateur, quant à lui, doit maximiser la précision en comparant la requête avec ces candidats et en plaçant les meilleures preuves en tête de liste.
Un schéma courant consiste à rassembler de 20 à 50 candidats issus de recherches vectorielles et lexicales, à les fusionner et à en éliminer les doublons, puis à réclasser les résultats pour n’en garder que les 5 à 10 meilleurs. Les encodeurs croisés sont efficaces et conçus pour évaluer la pertinence, tandis qu’un réclassificateur basé sur un LLM peut suivre des instructions plus complexes, mais est généralement plus lent et coûteux.
Évitez de supprimer prématurément les candidats. Si un seuil strict précède le réclassificateur, un élément qui aurait vraiment pu être utile ne sera jamais pris en compte par le modèle de pertinence plus performant. Souvent, une première analyse approximative suivie d’un réclassage donne de meilleurs résultats que de laisser uniquement le score vectoriel déterminer l’ordre de classement.
Fusion et déduplication
Lorsque les résultats vectoriels et ceux de BM25 sont combinés, un même fragment peut apparaître dans les deux listes et les échelles de score ne coïncident pas. Utilisez une méthode de fusion telle que la fusion par rang réciproque, des scores pondérés normalisés ou une simple fusion par rang, puis éliminez les doublons en utilisant des identifiants de fragments ou de documents stables avant l’opération de réordonnancement. L’objectif de cette conception est d’ajuster la première étape pour améliorer le taux de rappel, tout en laissant le réordonnanceur et la logique de sélection du contexte assurer la précision, ce qui facilite l’ajustement et l’évaluation du pipeline.
6. Élargir la requête, avec une solution de secours prévue
Les personnes et les documents partagent rarement le même vocabulaire. Quelqu’un qui tape « Je ne peux pas me connecter » aura peut-être besoin de pages intitulées « échec d’authentification », « dépannage de connexion » ou « accès au compte ». L’extension génère quelques recherches alternatives, les exécute toutes, puis élimine les doublons et réordonne les résultats. Restez maîtres de la situation : laissez de côté les entités nommées et les identifiants exacts, évitez de créer de nombreuses requêtes presque identiques, et limitez les tentatives afin qu’une question difficile ne déclenche pas une boucle coûteuse.
Assouplissez uniquement ce qui est sûr de l’être. Les filtres de sécurité, de locataire et de permissions ne sont jamais optionnels et doivent rester activés. Des éléments tels qu’une catégorie de document optionnelle, une fenêtre de date restreinte ou des étiquettes secondaires peuvent être assouplis si les règles de votre application le permettent. Une séquence simple :
docs = retrieve(query)
if not useful(docs): docs = retrieve(expand(query), hybrid=True)
if not useful(docs): return ask_for_clarification_or_no_answer()
Résultat vide versus récupération incorrecte
Ces deux cas exigent des solutions différentes. Si rien n’est retourné, suspectez une ingestion problématique, des filtres, des permissions, des seuils, une interprétation erronée des scores ou un index inaccessible. Si des résultats arrivent mais sont hors sujet, examinez le découpage en blocs, la qualité des embeddings, les lacunes dans le vocabulaire, une couverture lexicale insuffisante ou le classement.
7. Une architecture de production plus résiliente
Un système RAG mature n’envoie pas chaque requête par le même chemin de recherche. Il classe la demande et choisit une stratégie de récupération adaptée aux besoins : les identifiants exacts reposent sur la recherche lexicale, les questions conceptuelles larges s’appuient sur la recherche vectorielle, tandis que les requêtes ambiguës ou mixtes utilisent les deux méthodes.
Par la suite, une étape d’agrégation permet de regrouper les sources en un seul endroit, d’éliminer les fragments dupliqués, d’appliquer les règles de métadonnées et de permissions obligatoires, puis d’envoyer une liste de candidats cohérente au réordonneur. La fiabilité ne doit pas reposer sur un seul score vectoriel. De meilleurs indicateurs sont le score du réordonneur, l’écart entre les résultats les plus pertinents, le nombre de sources contribuant, le fait que les entités nécessaires à une réponse apparaissent bien dans le contexte, ainsi que le fait que plusieurs fragments soient en accord.
La fiabilité détermine l’étape suivante
Lorsque la fiabilité est suffisante, les éléments les mieux classés sont envoyés au LLM. En cas de faible fiabilité, le système peut étendre ou reformuler la requête, réessayer ou poser une question de clarification à l’utilisateur. C’est préférable à toujours répondre, car le modèle est clairement informé lorsque les éléments disponibles sont insuffisants pour justifier une réponse fiable.
Éviter d’inclure la sécurité dans le cycle de réessai
Tout design de secours doit séparer les règles de pertinence des règles d’autorisation. L’expansion peut assouplir la formulation, et les filtres de sujets optionnels peuvent parfois être supprimés, mais les limites des utilisateurs, les ACL, les permissions au niveau des lignes et les politiques de confidentialité restent inchangés à chaque tentative. Le mécanisme de tentative ne doit en aucun cas devenir un moyen de contourner le contrôle d’accès.
8. Rendre la récupération observable
Avec uniquement la réponse finale dans les journaux, le débogage de RAG se transforme en devinette. La couche de récupération doit générer suffisamment de données pour reconstituer ce qui s’est passé pour chaque requête. Enregistrez au moins : la requête originale, les versions réécrites ou étendues, les filtres, le modèle d’incorporation avec ses dimensions, le récupérateur utilisé, le critère top-k, le nombre de candidats bruts obtenus, leurs scores, les extraits choisis, les scores du réordonneur, la latence et le mécanisme de secours utilisé.
log({
'query': query, 'rewritten': rewritten_query, 'filters': filters,
'candidate_count': len(raw_results), 'scores': scores,
'selected_chunk_ids': selected_ids, 'fallback': fallback_stage
})
Un ordre de débogage judicieux
Lorsqu’un incident de récupération vide se produit, commencez par les vérifications les moins coûteuses et les plus déterministes, puis passez progressivement aux modifications de classement plus subjectives. Cela empêche les équipes d’ajuster les prompts ou les embeddings alors que le problème réel est l’absence d’index ou l’existence d’un filtre impossible.
- Tout d’abord, vérifiez l’ingestion : comptez les documents, le texte analysé, le nombre de blocs, les vecteurs stockés ainsi que quelques enregistrements que vous savez devoir exister.
- Ensuite, effectuez une récupération brute : envoyez la requête originale en désactivant tous les filtres et seuils optionnels, puis consultez les résultats les plus pertinents ainsi que leurs scores.
- Réintégrez les filtres un par un afin d’identifier précisément celui qui vide l’ensemble des résultats.
- Vérifiez la sémantique des scores : est-il préférable d’avoir des valeurs plus élevées ou plus basses, et une normalisation est-elle appliquée ?
- Comparez les requêtes originales et celles qui ont été réécrites, en conservant les identifiants exacts, pour déterminer si la réécriture améliore ou nuit au taux de rappel.
- Évaluer la récupération hybride et le réclassement à l’aide d’un ensemble de requêtes étiquetées, en vérifiant si les sources attendues apparaissent dans les premiers k ou n résultats après réclassement.
- Tester le mécanisme de secours en forçant l’apparition de résultats vides ou faibles, afin de s’assurer que le système précise son avis ou refuse de répondre sans inventer d’informations.
Quoi mesurer
La qualité de la réponse seule ne suffit pas à évaluer la récupération des informations. Suivez le taux de rappel@k (la source attendue fait-elle partie de l’ensemble des résultats ?), la précision après réclassement, le taux de résultats vides, le taux d’utilisation du mécanisme de secours, la latence, ainsi que la proportion de requêtes nécessitant une expansion ou une précision supplémentaire. Analysez ces indicateurs en fonction du type de requête, comme les questions sémantiques par rapport aux identifiants exacts, ainsi que selon le locataire, la langue et le corpus de documents, car les moyennes globales peuvent masquer des problèmes spécifiques.
Ne blâmez pas d’abord le LLM
Lorsque RAG ne renvoie rien, évitez de blâmer le modèle. La cause réside généralement plus tôt dans le processus de recherche : ingestion incomplète, embeddings incompatibles, seuils trop stricts, filtres de métadonnées erronés, interprétation incorrecte des scores, réécriture agressive de la requête, ou une stratégie de récupération inadaptée à la requête. Les systèmes fiables combinent un indexage solide avec une récupération hybride, une génération étendue de candidats, un réclassement, une expansion contrôlée des requêtes, un comportement de secours explicite et une surveillance approfondie. Le modèle mental utile est que RAG est avant tout un problème de recherche et de classement, et seulement secondairement un problème de génération. Lorsque la couche de récupération fournit de manière fiable des éléments pertinents et autorisés, et sait quand elle ne le peut pas, le modèle devient plus précis, plus facile à expliquer et moins sujet aux hallucinations.
Lectures complémentaires
- Dartboard RAG : Que faire lorsque Top-K retourne trois fois le même fragment — La récupération simple par Top-K peut produire des fragments presque identiques. Dartboard RAG prend en compte à la fois la pertinence et la diversité ; voici l’algorithme, le code ainsi que les situations où l’utiliser.
- RAG avancé en environnement de production : architecture, outils et compromis — Une présentation du RAG en production : ingestion, analyse, segmentation en fragments, génération d’embeddings, stockage vectoriel, récupération hybride, réclassement, évaluation et sécurité, avec les avantages et inconvénients de chacun.