Métriques d’évaluation RAG : retrieval, reranking et génération
Traduction automatique Cet article a été traduit automatiquement depuis la version originale en anglais.
Un système RAG dont les filtres sont défaillants peut fonctionner pendant des mois sans déclencher d’alerte opérationnelle. Il renvoie toujours des réponses et respecte son objectif de latence, mais ces réponses reposent sur des éléments de preuve incomplets. Le Recall@k calculé par rapport au gold set d’origine révèle cette perte. Les dashboards de latence et de disponibilité, eux, ne la révèlent pas.
Pour les ingénieurs qui exploitent ou évaluent des systèmes RAG à plusieurs étapes, cette référence relie les défaillances du parsing des documents, du filtrage, du retrieval, du reranking et de la génération à la métrique qui permet d’identifier chacune d’elles, puis indique où placer cette mesure dans un gate de release ou de monitoring.
Vous voulez passer directement à l’exécution du code ?
Le repository exécutable
slavadubrov/rag-evals-demoapplique ces métriques à SciFact.make evalexécute la suite, etmake benchmarkcompare les configurations de chunking, d’embedding et de LLM. Les notebooks 00–09 isolent chaque métrique. La démo utilise Qdrant embarqué et ne nécessite donc pas Docker.
En bref
- Une étape sans métrique est une étape qui échoue silencieusement.
- Une stack d’évaluation utile couvre l’ingestion, le retrieval, l’ancrage de la génération, la conformité à l’ontologie et les signaux système. RAGAS, TruLens, DeepEval, Arize Phoenix et le TREC 2024 RAG Track fournissent des outils. Ils ne choisissent pas vos métriques à votre place.
- Pour un RAG fondé sur les métadonnées et une ontologie, un tag erroné ou un prédicat hard fragile peut faire tomber le recall à zéro. Le Recall@k standard détecte la perte lorsqu’il conserve le gold set d’origine. Une métrique de fausse exclusion par filtre en identifie la cause. La faithfulness peut toujours évaluer des claims par rapport à un contexte incomplet, mais elle ne peut pas diagnostiquer la cause liée au filtre ou au retrieval. Un refus vide peut ne produire aucune statement et
NaN, selon l’implémentation.
Les sections suivent l’ordre du pipeline. Commencez par la table de décision, puis utilisez les sections suivantes comme référence pour chaque étape.
Table de décision pour l’évaluation RAG
Utilisez cette table comme point de départ avant de choisir un framework. La bonne métrique dépend du mode de défaillance que vous cherchez à détecter, pas du nom de l’outil.
| Question | Famille de métriques | À utiliser lorsque | Point d’attention |
|---|---|---|---|
| Le parsing a-t-il préservé la source ? | Complétude de l’extraction, couverture des tableaux/figures | Des PDF, slides, scans et pages HTML entrent dans le corpus | Un texte d’apparence propre peut tout de même perdre les légendes, notes de bas de page ou la structure des tableaux |
| Le retrieval a-t-il trouvé les bons éléments de preuve ? | Recall@k, nDCG@k, MRR, précision/rappel du contexte | Vous pouvez annoter les chunks ou documents pertinents | Un filtre de métadonnées hard peut supprimer le bon document avant le début du ranking |
| Le reranking a-t-il amélioré la shortlist ? | Uplift du reranker, Precision@1, delta de nDCG | Des cross-encoders ou des rankers LLM interviennent après le retrieval | Mesurez la latence et le coût avec le gain de qualité |
| La réponse a-t-elle utilisé les éléments de preuve ? | Faithfulness, groundedness, support des citations | La réponse cite des documents ou énonce des faits issus du contexte | La faithfulness ne peut pas diagnostiquer un mauvais parsing ou un mauvais retrieval |
| Le système est-il stable en production ? | Drift, régénération, fallback, latence p95, coût par réponse | Le trafic évolue après le lancement | La télémétrie de production nécessite une revue humaine échantillonnée pour rester calibrée |
Pour une comparaison plus courte des outils, consultez Best RAG Evaluation Tools: Ragas, DeepEval, and TruLens.
Partie 1 : définir le succès avant l’architecture
Préparez le jeu d’évaluation avant le diagramme d’architecture. Il fournit une cible mesurable à chaque choix de composant effectué ensuite.
Vous ne pouvez pas choisir entre BM25 et le retrieval dense, entre le chunking récursif et sémantique, ou entre Cohere Rerank et BGE avant de savoir ce que vous optimisez. « De meilleures réponses » n’est pas une métrique. Un contrat illustratif pourrait être : « faithfulness ≥ 0,85 sur un golden set de 200 requêtes couvrant nos trois intentions principales, avec une latence p95 < 1,5 s et un taux de fausse exclusion par filtre < 2 %. » Les valeurs sont indicatives ; l’essentiel est que la qualité, la couverture, la latence et le filtrage disposent de gates explicites.
Définissez le harness avant d’écrire le code de retrieval. Le premier harness sera erroné et vous le réviserez. Réviser une métrique coûte bien moins cher que réviser un système déjà livré.
Trois couches du pipeline et deux modes d’exécution
Le RAG moderne est un pipeline : son évaluation doit donc être un pipeline. Aucun nombre unique ne détecte tous les modes de défaillance.
L’évaluation en production comporte trois couches. L’évaluation de l’ingestion vérifie que le corpus et l’index préservent la source. L’évaluation au moment de la requête vérifie que la réécriture, le filtrage, le retrieval, le reranking et l’assemblage du contexte ont trouvé les bons éléments de preuve. L’évaluation de la réponse et de la production vérifie que la réponse a utilisé ces éléments et que la qualité se maintient sur le trafic réel. Réduire ces couches à un score unique permet à un bug de normalisation de disparaître dans un score de réponse acceptable.
Ces couches indiquent où une défaillance se produit. Offline et online indiquent quand le contrôle s’exécute et sur quelles données. L’évaluation offline utilise un dataset fixe avec une ground truth connue ; elle est reproductible et intervient dans le choix des composants, les comparaisons A/B et les gates CI. L’évaluation online évalue un échantillon du trafic réel et capture les régénérations, le temps passé, les feedbacks explicites et le drift réel des requêtes. Elle est plus bruitée et plus difficile à instrumenter.
Chaque couche du pipeline peut fournir des contrôles offline et online. Un corpus d’ingestion fixe détecte les régressions du parser avant la release, tandis que les monitors de fraîcheur et d’échec du parsing couvrent les mises à jour en production. Un jeu de requêtes fixe mesure le retrieval avant la release, tandis que des traces live échantillonnées révèlent le drift en production. L’offline seul ignore les changements réels ; l’online seul rend les régressions difficiles à reproduire.
Évaluation au niveau des composants vs. de bout en bout
Deux erreurs sont fréquentes. Une évaluation uniquement de bout en bout indique que le système est défaillant, mais pas où. Une évaluation uniquement par composant peut montrer que chaque partie passe les tests alors que le système complet échoue. La solution consiste à utiliser quelques métriques de bout en bout comme critères go/no-go, complétées par des métriques de composant pour le diagnostic. Les métriques de retrieval détectent les régressions du retriever. Les métriques de génération détectent celles du générateur. La correction de la réponse de bout en bout détecte les échecs d’intégration.
Les frameworks de référence (tour d’horizon assumé)
| Framework | Point fort principal | Limites |
|---|---|---|
| RAGAS | Mon critère : un vocabulaire commun pour la faithfulness, la pertinence de la réponse et la précision/le rappel du contexte (metrics) | Coût du LLM-judge ; composants du score opaques lors du debugging ; changements de version |
| ARES | Mon critère : un classifier judge spécifique à la tâche justifie les coûts d’entraînement et d’annotation (paper) ; sa précision dépend du benchmark | Mise en place plus lourde ; vous devez réellement entraîner des modèles |
| TruLens | Mon critère : les feedback functions liées aux traces et l’intégration OpenTelemetry comptent davantage qu’un catalogue de métriques RAG (project) | Moins de métriques RAG natives que RAGAS |
| DeepEval | Mon critère : l’intégration avec le test runner et les métriques custom comptent davantage qu’une valeur par défaut du framework (project) | Un usage intensif du LLM-judge entraîne des pics de coût |
| Arize Phoenix | Mon critère : le tracing et les visualisations d’embeddings aident à examiner une hypothèse de drift (project) | Vous devez définir vos propres métriques |
| TREC 2024 RAG Track | Benchmark public pour l’évaluation des nuggets (AutoNuggetizer), du support et de la fluidité sur MS MARCO Segment v2.1 | Pas un outil runtime ; un benchmark servant de référence |
Ma stack par défaut est RAGAS pour le vocabulaire métrique, DeepEval pour les gates CI, Phoenix pour le tracing en production, ainsi que du code custom pour les métriques propres à l’ontologie. Vous dépasserez le framework par lequel vous commencez. Choisissez celui qui facilite la création de métriques custom.
Pour les benchmarks, utilisez BEIR (Thakur et al., NeurIPS 2021) pour la généralisation du retrieval zero-shot, MTEB pour la qualité générale des embeddings, MIRACL pour le retrieval multilingue et le TREC 2024 RAG Track pour l’évaluation RAG de bout en bout.
Partie 2 : placer les points d’évaluation sur le pipeline
Un système RAG de production est plus vaste que « embedder des documents, récupérer des chunks, appeler un LLM ». Chaque étape entre l’acquisition des documents et la livraison de la réponse peut échouer.
Chaque étape du diagramme possède au moins une métrique. Une étape sans métrique peut échouer sans que personne ne s’en aperçoive.
Les trois couloirs correspondent aux endroits où les éléments de preuve peuvent être perdus. Le couloir d’ingestion couvre le parsing, le nettoyage, le chunking, l’embedding et l’indexation. Le couloir de traitement de la requête couvre la réécriture, le filtrage, le retrieval, le reranking et l’assemblage du contexte. Le couloir de réponse et de production couvre la faithfulness, la vérification des citations, les signaux utilisateur, le drift, la latence et le coût.
Les erreurs se cumulent dans la chaîne : un mauvais parsing limite ce que le chunking peut accomplir, un mauvais chunking limite le retrieval, et un mauvais retrieval limite à la fois le reranking et la génération. La faithfulness mesure uniquement la réponse finale, jamais la cause en amont.
Partie 3 : évaluation de l’ingestion
De nombreux échecs RAG en production commencent lors de l’ingestion. Le système fonctionne sur des documents de test propres, puis échoue sur de vrais PDF, scans, tableaux et pages de corpus mal structurées.
Acquisition et parsing des documents
Mesures à suivre :
-
Complétude de l’extraction du texte :
extracted_chars / expected_charssur un échantillon annoté, calculé par classe de documents. Il n’existe pas de package canonique : écrivez un petit harness qui compare la sortie du parser à une référence nettoyée manuellement. Vérifiez notamment les notes de bas de page, en-têtes et légendes. -
Précision de l’OCR : CER (Character Error Rate) et WER (Word Error Rate), les métriques standard de la reconnaissance vocale et de l’OCR :
où , et sont respectivement les substitutions, suppressions et insertions au niveau des caractères, et le nombre de caractères de référence (indice pour la version mots). N’appliquez pas le même seuil de CER à tout le corpus. Calibrez-le par classe de documents et en fonction de la perte de qualité des réponses en aval. Les textes imprimés, l’écriture manuscrite et les contenus multilingues ont des profils d’erreur différents. Calculez-le avec
jiwer(jiwer.cer(refs, hyps),jiwer.wer(refs, hyps)) ou avecevaluatede HuggingFace. Pour les corpus d’évaluation, FUNSD et SROIE sont des benchmarks publics.from jiwer import cer, wer refs = ["Mars has two moons, Phobos and Deimos."] hyps = ["Mars has two m00ns, Phobos and Deirnos."] print(f"CER = {cer(refs, hyps):.3f}") # CER = 0.105 print(f"WER = {wer(refs, hyps):.3f}") # WER = 0.286 -
Fidélité de l’extraction des tableaux : TEDS (Tree-Edit-Distance-based Similarity) mesure la proximité entre l’arbre HTML d’un tableau prédit et celui de référence, normalisée par la taille du plus grand arbre. D’après Zhong et al., 2020 (PubTabNet) :
TEDS utilise à la fois la structure (lignes, colonnes, spans) et le contenu des cellules. TEDS-S supprime le contenu et évalue uniquement la structure. Implémentation de référence :
teds.pyde PubTabNet (qui utiliseapteden interne). Pour les corpus d’évaluation, consultez PubTabNet, FinTabNet et SciTSR. Les parsers naïfs échouent souvent sur les tableaux. Faites un benchmark avant de leur faire confiance. -
Préservation de la mise en page et de la structure : ordre des titres, intégrité des listes et ordre de lecture dans les PDF à plusieurs colonnes. Utilisez DocLayNet comme benchmark annoté. Une comparaison prête à l’emploi peut inclure un parser d’éléments tel que
unstructured, une bibliothèque PDF telle quepymupdfet un parser VLM tel quedocling.
Comparez des familles de parsers distinctes, par exemple une baseline Tesseract, un modèle OCR fondé sur un VLM et le candidat de votre fournisseur. Utilisez un échantillon stratifié de classes de documents réelles, à DPI constant, incluant des scans propres, des photos, des tableaux, du texte multilingue, des formules mathématiques et de l’écriture manuscrite. Rapportez le CER ou le WER pour chaque classe, ainsi que le TEDS pour les pages contenant des tableaux.
Nettoyage et normalisation
-
Précision de la suppression du boilerplate : précision/rappel par rapport à des spans de boilerplate annotés manuellement. Une suppression trop agressive élimine du contenu pertinent ; une suppression insuffisante pollue les embeddings. Outils à comparer :
trafilatura,jusText,Resiliparse. Barbaresi (2021) les compare en conditions identiques. -
Normalisation Unicode : le pourcentage de documents produisant des sorties NFC et NFKC identiques (calculé avec
unicodedata.normalize, disponible dans la stdlib) constitue un bon signal de drift. Les divergences expliquent notamment comment les zero-width joiners et les caractères ressemblants peuvent dégrader le recall du retrieval. -
Précision de la détection de langue : F1 sur un échantillon multilingue annoté. Elle est essentielle pour les index multilingues. Utilisez
fasttext-langdetect(lelid.176de Facebook),lingua-pyoucld3. FLORES-200 fournit des textes d’évaluation dans 200 langues, mais c’est votre mix de langues en production qui doit déterminer la portion testée. -
Efficacité de la déduplication (MinHash / LSH) : précision/rappel de votre détecteur de quasi-doublons par rapport à un ensemble annoté manuellement. L’idée sous-jacente consiste à estimer la similarité de Jaccard entre des ensembles de shingles de documents au moyen de fonctions de hachage par permutation aléatoire (Broder, 1997), puis à regrouper les quasi-doublons avec le banding LSH (Indyk & Motwani, 1998). Faites varier le nombre de hashes et le seuil de Jaccard sur votre corpus. Suivez séparément le taux de fusions erronées (qui corrompt les réponses) et le taux de fusions manquées (qui gaspille de l’espace d’index).
datasketchfournit l’implémentation utilisée ci-dessous ; ses paramètres sont illustratifs :from datasketch import MinHash, MinHashLSH def shingles(text: str, k: int = 5) -> set[str]: text = text.lower() return {text[i:i + k] for i in range(len(text) - k + 1)} def to_minhash(text: str, num_perm: int = 128) -> MinHash: m = MinHash(num_perm=num_perm) for s in shingles(text): m.update(s.encode("utf-8")) return m docs = { "d1": "Mars has two moons, Phobos and Deimos.", "d2": "Mars has two moons, Phobos and Deimos!", # near-dup "d3": "Curiosity rover landed on Mars in 2012.", } lsh = MinHashLSH(threshold=0.8, num_perm=128) for did, text in docs.items(): lsh.insert(did, to_minhash(text)) print(sorted(lsh.query(to_minhash(docs["d1"])))) # ['d1', 'd2'] -
Suppression des PII : précision et rappel, calculés séparément par type d’entité (e-mails, SSN, noms, adresses). Les erreurs de rappel créent un risque de conformité ; les erreurs de précision dégradent la qualité des réponses. Définissez le point de fonctionnement avec l’équipe juridique. Parmi les outils candidats figurent Microsoft Presidio,
scrubadubou un modèle NER fine-tuné sur un ensemble annoté.
Le chunking contrôle la qualité du retrieval
Le chunking peut créer un écart de recall à plusieurs niveaux même si le modèle d’embedding reste inchangé. Dans le benchmark fournisseur de NVIDIA de 2025, le chunking au niveau des pages a produit la meilleure précision et la plus faible variance pour les documents paginés. Considérez ce résultat comme une observation sur le corpus testé, et non comme un vainqueur universel.
Le chunking sémantique regroupe les phrases adjacentes en fonction de leur similarité d’embedding et coupe aux frontières dissemblables. Le SemanticChunker de LangChain et le SemanticSplitterNodeParser de LlamaIndex implémentent cette stratégie. Elle peut améliorer le recall par rapport aux fenêtres fixes lorsque les frontières thématiques sont importantes.
Le découpage récursif par caractères tente successivement les sauts de paragraphe, de phrase puis de mot, jusqu’à ce que chaque chunk respecte la taille cible. Le RecursiveCharacterTextSplitter de LangChain implémente cette séquence. Choisissez des valeurs candidates de fenêtre et de chevauchement adaptées à la structure de vos documents, puis laissez le golden set déterminer les valeurs finales.
Métriques à suivre :
- Cohérence des chunks : , où sont les embeddings des phrases. Des chunks sains sont similaires en interne et dissemblables au niveau des frontières. Calculez-la avec
sentence-transformerset lecosine_similaritydescikit-learn. - Qualité des frontières : annotation humaine de « cette coupure est-elle pertinente ? » sur un échantillon, complétée par un contrôle structurel vérifiant que les chunks ne coupent pas les tableaux, listes ou sections numérotées.
- Taille optimale des chunks : faites varier les tailles en tokens (128, 256, 512, 1024), puis tracez Recall@k en fonction de la taille sur votre golden set. Choisissez le coude de la courbe. Ne prenez pas la valeur indiquée par un tutoriel.
- Efficacité du chevauchement : supprimez successivement plusieurs fractions de chevauchement et mesurez le Recall@k. Cessez d’augmenter le chevauchement lorsque la courbe locale de recall s’aplatit ou que le coût de duplication dépasse le gain.
- Fidélité de l’attribution des chunks : pourcentage de chunks conservant un pointeur vérifiable vers la source (numéro de page, ancre de section, ID du document). L’auditabilité l’exige.
- Chunking late vs. early : le late chunking (Günther et al., 2024) embedde le document complet, puis le segmente, afin de préserver le contexte global (implémentation de référence dans
jina-embeddings-v3). Le Contextual Retrieval (Anthropic, 2024) préfixe chaque chunk avec du contexte généré par un LLM. Les deux approches ajoutent du coût. Faites un benchmark sur votre corpus avant d’adopter l’une ou l’autre.
Mon avis : le chunking structurel (découpage sur les titres, tableaux et sections — implémenté par des parsers comme unstructured.io ou en parcourant l’AST déjà produit par votre parser) est sous-utilisé. Si vos documents ont une structure, exploitez-la avant d’ajouter des heuristiques de similarité. Le découpage récursif par caractères est la baseline ; le chunking sémantique justifie surtout sa surcharge sur de la prose non structurée.
Extraction et enrichissement des métadonnées
- Précision/rappel/F1 du NER : par type d’entité, sur un sous-ensemble annoté. Utilisez le format standard de type CoNLL/MUC. Calculez-les avec
seqeval(from seqeval.metrics import f1_score) pour la version compatible avec les tags BIO/IOB, ou avec scikit-learn pour comparer des ensembles de spans. CoNLL-2003 et OntoNotes 5.0 sont les corpus de référence canoniques. - F1 de l’extraction de relations : encore plus important pour les systèmes fondés sur une ontologie. Annotez manuellement un ensemble stratifié par type de relation et classe de documents. TACRED et DocRED sont des benchmarks publics ; parmi les implémentations candidates figurent les pipelines de relations
opennreetspaCy. - Précision de l’extraction des titres et en-têtes : exact-match et similarité de Levenshtein normalisée () par rapport à la ground truth —
python-Levenshteinourapidfuzzfournissent les deux en un seul appel. - Préservation des métadonnées hiérarchiques : pourcentage de chunks conservant correctement leur section parente, leur document parent et leur chemin d’ascendance. C’est cette métrique qui détermine si votre RAG peut répondre à des questions du type « que dit l’enfant de la policy X ? ».
Génération des embeddings
- Benchmarks de sélection du modèle : utilisez les résultats des tâches MTEB (nDCG@10 est la métrique principale ; le package Python MTEB permet de reproduire le leaderboard localement), BEIR pour la généralisation zero-shot et MIRACL pour le retrieval multilingue. Considérez le transfert des résultats MTEB en anglais vers une langue moins dotée comme une hypothèse à tester sur l’ensemble annoté de cette langue.
- Évaluation spécifique au domaine : ne considérez pas le rang obtenu sur un benchmark général comme un résultat de domaine. Dimensionnez un golden set de domaine à partir de sa matrice de couverture et de l’incertitude acceptable pour votre décision. Re-classez ensuite les modèles candidats avec
ranxoupytrec_eval. Un jeu de domaine peut inverser l’ordre d’un leaderboard ; publiez donc la portion de dataset, le protocole de retrieval et l’intervalle de confiance avec le résultat. - Détection du drift des embeddings : suivez la divergence KL distributionnelle ou le drift fondé sur un modèle entre une fenêtre de référence fixe et les embeddings de production sur une fenêtre glissante ; mesurez également la stabilité des plus proches voisins pour un ensemble de sondes fixe.
evidentlyetalibi-detectimplémentent des détecteurs fondés sur un modèle et des détecteurs statistiques. L’étude comparative d’Evidently constitue une évaluation fournisseur ; comparez les méthodes sur des shifts connus dans vos propres embeddings. - Multi-vector vs. single-vector : l’interaction tardive préserve les représentations au niveau des tokens au lieu de réduire chaque document à un seul vecteur ; ColBERT est l’architecture canonique, avec des implémentations de référence dans RAGatouille et PyLate. Cette représentation plus riche augmente le coût de l’index et du retrieval. Comparez la qualité, le stockage et la latence à une baseline single-vector sur le même ensemble de domaine avant de l’adopter.
Construction de l’index
- Recall@k sous approximation : comparez l’index approximate-nearest-neighbour (ANN) à une baseline exacte par force brute pour le même k — dans FAISS, il s’agit de
IndexHNSWFlat(ouIndexIVFFlat) contreIndexFlatIP/IndexFlatL2. Définissez la perte de recall acceptable à partir de votre budget de qualité en aval. Le projetann-benchmarkssuit les courbes de Pareto recall–QPS entre bibliothèques. - Réglage de HNSW : HNSW (Hierarchical Navigable Small World) est un graphe de proximité en couches ; voir Malkov & Yashunin, 2018. Il est implémenté dans
hnswlib, leIndexHNSWFlatde FAISS et la plupart des bases vectorielles. HNSW expose trois paramètres :M(fan-out du graphe),efConstruction(largeur des candidats lors de la construction) etefSearch(largeur des candidats lors de la requête). Commencez par les valeurs par défaut documentées par la bibliothèque, puis faites varier les paramètres jusqu’à ce que la courbe recall–latence satisfasse les exigences de votre jeu d’évaluation. - Réglage d’IVF : IVF (Inverted File index — partitionnement des vecteurs par k-means en
nlistcellules, puis, lors de la requête, parcours desnprobecellules les plus proches ; voirIndexIVFFlatetIndexIVFPQde FAISS). Faites variernlistetnprobeen les comparant au recall et à la latence de la recherche exacte. Évaluez séparément les requêtes filtrées, car les familles d’index et les bases vectorielles implémentent différemment le parcours avec filtres. - Délai de fraîcheur des mises à jour : temps écoulé entre le commit du document et sa disponibilité dans le retrieval. Suivez les p50 et p99. Pour les systèmes soumis à des exigences réglementaires, suivez également le pourcentage de requêtes servies par des index obsolètes.
Partie 4 : évaluation au moment de la requête
Le couloir de traitement de la requête contient les métriques qui diagnostiquent le chemin de retrieval. Le Recall@k seul ne permet pas de déterminer si la réécriture, le filtrage, le reranking ou l’assemblage du contexte a causé l’échec.
Compréhension et réécriture de la requête
- Qualité de l’expansion de requête : uplift du Recall@k sur votre golden set, entre la requête étendue et la requête brute. Définissez à l’avance le gain minimal utile et son incertitude. Si l’expansion ne franchit pas ce gate local, elle ne justifie ni sa latence ni son coût. Les baselines classiques de PRF (pseudo-relevance feedback), comme RM3 et Bo1, restent utiles pour un sanity check ; l’expansion fondée sur un LLM doit les dépasser.
- Évaluation de HyDE : HyDE (Gao et al., 2022) génère une réponse hypothétique avec le LLM, l’embedde, puis effectue le retrieval à partir de celle-ci. Elle ajoute de la latence de génération et une nouvelle surface de défaillance. Mesurez séparément le Recall@10 sur les slices in-domain, out-of-domain et à faible confiance, puis décidez si HyDE doit appartenir au chemin par défaut, à un fallback ou à aucun des deux.
- Génération multi-query : union du Recall@k obtenu avec N réécritures par rapport à une requête unique. Faites varier N et choisissez un point sur votre frontière recall–latence. Implémentations :
MultiQueryRetrieverde LangChain etQueryFusionRetrieverde LlamaIndex. - Précision de la classification d’intention : précision/rappel/F1 standard par intention (à calculer avec
sklearn.metrics.classification_report), mais la métrique opérationnelle est la correction du routage : le bon pipeline downstream est-il invoqué ? - Routage adaptatif : Adaptive-RAG (Jeong et al., NAACL 2024) montre que toutes les requêtes ne méritent pas la même stratégie de retrieval. Suivez la précision du router comme un problème de classification sur un ensemble annoté de requêtes « sans retrieval / one-shot / itératives ».
Métriques de retrieval
Ce sont les métriques de baseline. Si vous ne les suivez pas, vous ne pouvez pas savoir si le retrieval s’améliore.
| Métrique | Ce qu’elle mesure | Quand l’utiliser |
|---|---|---|
| Recall@k | fraction des documents pertinents d’une requête renvoyés dans les k premiers | lorsque l’absence d’une partie quelconque de l’ensemble pertinent est importante |
| Precision@k | pourcentage des k premiers documents qui sont pertinents | lorsque la fenêtre de contexte est le goulot d’étranglement |
| MRR | moyenne de 1/rang du premier document pertinent | lorsque les utilisateurs ne regardent que le top-1 ou le top-3 |
| nDCG@k | gain pondéré par le degré de pertinence et la position | métrique standard pour une pertinence graduée |
| MAP | moyenne, sur les requêtes, de la précision moyenne | lorsque toute la liste classée vous intéresse |
| Hit Rate@k | présence ou non d’au moins un document pertinent dans les k premiers | moyenne du résultat binaire sur les requêtes, pour un sanity check rapide |
| Coverage | pourcentage des documents gold récupérés au moins une fois sur l’ensemble des requêtes | détecte les lacunes systématiques de l’index |
Les formules, pour référence (pertinence binaire avec l’ensemble pertinent pour la requête , et si le document récupéré en position appartient à ) :
Pour une pertinence graduée, ; le nDCG binaire est le cas particulier utilisé dans le code ci-dessous. MAP est la moyenne, sur les requêtes, de . Consultez Manning, Raghavan, Schütze, Introduction to Information Retrieval, chapitre 8, pour les dérivations.
Pour le code de production, utilisez ranx, pytrec_eval ou ir_measures : ils implémentent toute la famille de métriques TREC et gèrent correctement la pertinence graduée. Définissez les objectifs de release à partir d’un golden set réaliste, de la qualité des réponses en aval et du coût d’un échec. Ne reprenez pas les seuils d’un tutoriel.
Le harness de test associé est court. Vous pouvez l’exécuter dans un notebook avant même d’avoir choisi une base vectorielle.
from math import log2
from statistics import mean
# synthetic gold set: query_id -> set of relevant doc ids
gold = {
"q1": {"d3"},
"q2": {"d7", "d2"},
"q3": {"d11"},
"q4": {"d5"},
}
# ranked retrieval results: query_id -> ranked list of doc ids (top-10)
runs = {
"q1": ["d8", "d3", "d1", "d4", "d2", "d9", "d6", "d10", "d12", "d13"],
"q2": ["d2", "d6", "d4", "d7", "d1", "d3", "d8", "d11", "d5", "d9"],
"q3": ["d11", "d2", "d3", "d4", "d1", "d6", "d7", "d8", "d10", "d12"],
"q4": ["d1", "d2", "d3", "d6", "d8", "d9", "d10", "d12", "d13", "d14"],
}
def recall_at_k(ranked, gold_set, k):
if not gold_set:
return 0.0
hit = sum(1 for d in ranked[:k] if d in gold_set)
return hit / len(gold_set)
def reciprocal_rank(ranked, gold_set):
# MRR contribution per query: 1/rank of the first relevant doc.
for rank, d in enumerate(ranked, start=1):
if d in gold_set:
return 1.0 / rank
return 0.0
def ndcg_at_k(ranked, gold_set, k):
# binary relevance: rel ∈ {0, 1}
gains = [1.0 if d in gold_set else 0.0 for d in ranked[:k]]
dcg = sum(g / log2(i + 2) for i, g in enumerate(gains))
# ideal DCG: all gold docs ranked first, capped by k
n_gold_in_topk = min(k, len(gold_set))
idcg = sum(1.0 / log2(i + 2) for i in range(n_gold_in_topk))
return dcg / idcg if idcg else 0.0
K = 5
print(f"Recall@{K}: {mean(recall_at_k(runs[q], gold[q], K) for q in gold):.3f}")
print(f"MRR: {mean(reciprocal_rank(runs[q], gold[q]) for q in gold):.3f}")
print(f"nDCG@{K}: {mean(ndcg_at_k(runs[q], gold[q], K) for q in gold):.3f}")
# Recall@5: 0.750
# MRR: 0.625
# nDCG@5: 0.627
Voilà votre gate CI pour le retrieval. Reliez-le à un sous-ensemble rapide piloté par la couverture sur chaque PR, et exécutez le golden set complet dans le gate de release plus lent. Bloquez une merge lorsque l’une des métriques préenregistrées dépasse son budget de régression.
Le repository compagnon fige les valeurs exactes ci-dessus (Recall@5 = 0.750, MRR = 0.625, nDCG@5 = 0.627) comme test unitaire dans tests/test_retrieval_metrics.py ; le notebook 01 fait varier Recall@k / MRR / nDCG sur un index SciFact réel, et le harness de forme production se trouve dans evaluation/retrieval.py.
Retrieval hybride et fusion reciprocal rank
BM25 est un scoreur lexical sparse qui combine la correspondance exacte des termes, la pondération des termes et la normalisation par la longueur. Il est disponible dans rank_bm25, Elasticsearch, OpenSearch et la plupart des moteurs de recherche.
La Reciprocal Rank Fusion (Cormack, Clarke et Buettcher, SIGIR 2009) combine les rankings BM25 et dense selon leur position. La valeur k=60 originale constitue une baseline utile. RRF ne dépend pas des scores, ce qui évite la normalisation inter-couloirs requise par l’interpolation linéaire. Avec un ensemble annoté suffisamment grand pour estimer un delta stable, testez également une combinaison convexe et ajustez α.
Mon hypothèse est que le retrieval hybride associé à un reranker cross-encoder peut aider sur les corpus techniques, de logs et de code. Le gain peut être faible sur des corpus fortement sémantiques. Mesurez-le par rapport aux couloirs dense-only et sparse-only, car une mauvaise configuration de fusion peut être moins performante que chacune des entrées. Le notebook SciFact compagnon est un test borné, pas un résultat général.
L’implémentation tient en quelques lignes.
from collections import defaultdict
# two retrieval lanes: dense embeddings and BM25.
dense = ["d3", "d7", "d1", "d4", "d2", "d9", "d10"]
sparse = ["d2", "d3", "d8", "d1", "d11", "d4", "d6"]
def rrf(rankings: list[list[str]], k: int = 60) -> list[tuple[str, float]]:
"""Reciprocal Rank Fusion (Cormack et al., SIGIR 2009).
score(d) = sum over rankings of 1 / (k + rank(d))
Score-agnostic: only rank position matters. k=60 is the canonical default.
"""
scores: dict[str, float] = defaultdict(float)
for ranking in rankings:
for rank, doc in enumerate(ranking, start=1):
scores[doc] += 1.0 / (k + rank)
return sorted(scores.items(), key=lambda kv: kv[1], reverse=True)
fused = rrf([dense, sparse], k=60)
for doc, score in fused[:5]:
print(f"{doc} score={score:.5f}")
# d3 score=0.03252 <- rank 1 dense, rank 2 sparse
# d2 score=0.03178 <- rank 5 dense, rank 1 sparse
# d1 score=0.03150
Notez ce que RRF ne fait pas : il ne consulte jamais les scores de similarité bruts. Un retriever dense renvoyant une similarité cosinus de 0,98 et un couloir BM25 renvoyant un score de 17,4 ne sont pas directement comparables. Si vous les normalisez avec des z-scores ou une mise à l’échelle min-max, vous pouvez favoriser le couloir dont la variance est la plus élevée dans le batch.
RRF utilise uniquement le rang. Si un retriever place un document en position 2, ce vote vaut 1 / (60 + 2), quel que soit le score brut qui l’a produit.
Hybrid + RRF sur SciFact : le notebook 02 compare dense, BM25 et RRF avec des deltas par requête. Le fuser de forme production se trouve dans retrieval/hybrid_rrf.py ; tests/test_rrf.py fige l’ordre canonique d3 / d2 / d1 à k=60.
Reranking
- ΔnDCG / ΔMRR : uplift par rapport à l’absence de reranking, sur votre golden set et à la profondeur réellement utilisée par l’application. Calculez les métriques de retrieval avec et sans reranker sur des ensembles de candidats identiques.
- Cross-encoder vs. bi-encoder : un bi-encoder embedde indépendamment la requête et le document (un vecteur par côté), puis calcule un score par produit scalaire ; un cross-encoder concatène la requête et le document et exécute un forward pass unique avec une attention conjointe. Les cross-encoders échangent un forward pass par candidat contre une interaction requête–document plus riche. Implémentation de référence :
sentence-transformersCrossEncoder. Faites le benchmark de la pertinence et de la latence sur un matériel, une taille de batch et une profondeur de candidats nommés ; ne transférez pas le résultat d’un modèle ou d’un service managé à un autre environnement. - Listwise vs. pointwise : pointwise évalue chaque paire (requête, document) indépendamment ; listwise évalue conjointement toute la liste de candidats afin que le modèle puisse comparer les candidats. Évaluez les deux approches sur les mêmes ensembles de candidats. Calibrez tout seuil de score par modèle et par corpus au lieu de considérer un exemple publié comme portable.
from sentence_transformers import CrossEncoder
reranker = CrossEncoder("BAAI/bge-reranker-v2-m3")
query = "How do I rotate database credentials in production?"
candidates = [
"Production database credentials are rotated via Vault every 30 days.",
"The new logo was unveiled at the all-hands meeting.",
"To rotate prod DB creds, run the `rotate-secrets` GitHub Action.",
]
scores = reranker.predict([(query, c) for c in candidates])
ranked = sorted(zip(candidates, scores), key=lambda x: -x[1])
for doc, score in ranked:
print(f"{score:+.3f} {doc}")
Un reranker aide souvent un pipeline RAG basique, mais ce n’est pas un gain garanti. Mesurez son ΔPrecision@1 et son ΔnDCG sur votre golden set, puis ne le conservez que si le gain respecte votre budget de latence et de coût. Comparez ce gain mesuré à celui de modifications plus modestes du retrieval avant de choisir la prochaine optimisation.
ΔnDCG et ΔPrecision@1 obtenus avec un cross-encoder sur SciFact : notebook 03 ; module : retrieval/reranker.py.
Construction du contexte et lost-in-the-middle
De nombreux échecs du type « bon retrieval, mauvaise réponse » commencent lors de la construction du contexte.
- Pertinence du contexte : score de pertinence par chunk fourni par RAGAS
ContextRelevanceou par un cross-encoder, agrégé sous forme de moyenne et de pourcentage de chunks sous un seuil. - Utilisation du contexte : parmi les chunks placés dans le contexte, combien ont réellement été cités ou utilisés dans la réponse. Calculez sur un échantillon annoté. Définissez le seuil opérationnel à partir de la qualité de réponse et du coût en tokens, plutôt que d’utiliser un pourcentage universel.
- Détection du lost-in-the-middle : évaluation synthétique dans laquelle le chunk gold est placé en première, au milieu ou en dernière position d’un long contexte, puis où la correction de la réponse est mesurée. L’étude citée de Liu et al. (TACL 2023) rapporte une dégradation en U dans ses conditions de long contexte. Considérez l’observation du même schéma avec un modèle actuel comme une hypothèse à tester. Atténuations possibles : effectuer le reranking, puis réordonner le top-k afin de placer le chunk au score le plus élevé en première ou dernière position (le
LongContextReorderde LangChain fait exactement cela), ou compresser fortement les chunks centraux. Mesurez avec une évaluation stratifiée par position, et non avec un score agrégé uniquement. Une évaluation exécutable et détaillée, stratifiée par position, se trouve dans le notebook 06 (module :evaluation/lost_in_middle.py). - Compression du contexte : rapportez le ratio de compression (tokens d’entrée / tokens de sortie) en parallèle de la correction de la réponse. Les outils incluent le
ContextualCompressionRetrieverde LangChain et LongLLMLingua. Définissez à l’avance la perte maximale de correction acceptable en fonction du risque applicatif et du budget de tokens, puis rejetez les configurations qui la dépassent.
Partie 5 : taux de fausse exclusion par filtre
Cette métrique dispose de sa propre section, car les scores agrégés de retrieval ne permettent pas d’attribuer une absence de résultat au filtre.
Un filtre de métadonnées hard tel que tenant_id = X AND product = Y AND locale = en-US peut faire tomber le recall effectif à zéro. Un Recall@k correctement implémenté détecte la perte, car son dénominateur reste l’ensemble des documents pertinents d’origine. Il ne permet pas de déterminer si la cause est le filtre, le retriever ou le ranker. La faithfulness évalue les claims par rapport au contexte récupéré. Elle peut toujours attribuer un bon score à des claims soutenus par ce contexte incomplet, mais elle ne peut pas diagnostiquer la cause liée au filtre ou au retrieval. Un refus vide peut ne produire aucune statement et NaN, selon l’implémentation ; ne considérez pas cela comme la preuve que la faithfulness a validé le refus.
La branche mise en évidence correspond à la défaillance courante : le bon document existe, mais le filtre le supprime avant le retrieval. Le Recall@k enregistre la baisse ; seul le taux d’exclusion attribue cette perte au prédicat.
La métrique
filter_false_exclusion_rate =
(# queries where all gold docs were excluded by metadata filter) /
(# queries with at least one gold doc)
Cette définition au niveau de la requête compte les exclusions catastrophiques : aucun document pertinent ne subsiste. Pour les requêtes comportant plusieurs golds, le Recall@k standard révèle tout de même une perte partielle ; ajoutez un taux d’exclusion par document si cette granularité est importante. Pour calculer l’un ou l’autre taux, vous avez besoin (a) des IDs des documents de ground truth pour chaque requête d’évaluation et (b) d’une instrumentation qui journalise les prédicats de filtre appliqués, et pas uniquement les résultats finaux. Définissez la cible à partir du coût de l’exclusion d’une réponse valide et de l’intervalle de confiance de votre échantillon de production.
Voici une implémentation fonctionnelle. Elle compare le recall standard correct à un évaluateur invalide qui redéfinit la pertinence après filtrage.
# A small worked example where hard filters remove relevant documents.
docs = [
{"id": "d1", "tenant": "acme", "locale": "en-US"},
{"id": "d2", "tenant": "acme", "locale": "en-GB"},
{"id": "d3", "tenant": "globex", "locale": "en-US"},
{"id": "d4", "tenant": "acme", "locale": "en-US"},
{"id": "d5", "tenant": "acme", "locale": "de-DE"},
]
queries = [
# the gold doc lives in en-GB but the dynamic filter forced en-US
{"qid": "q1", "gold": {"d2"}, "filter": lambda d: d["locale"] == "en-US"},
# the gold doc is correctly within the tenant filter
{"qid": "q2", "gold": {"d4"}, "filter": lambda d: d["tenant"] == "acme"},
# the gold doc is in a different tenant and gets dropped
{"qid": "q3", "gold": {"d3"}, "filter": lambda d: d["tenant"] == "acme"},
# the gold doc passes the filter (de-DE locale match)
{"qid": "q4", "gold": {"d5"}, "filter": lambda d: d["locale"] == "de-DE"},
]
def filter_false_exclusion_rate(queries, docs):
n_with_gold, n_excluded = 0, 0
for q in queries:
if not q["gold"]:
continue
n_with_gold += 1
survivors = {d["id"] for d in docs if q["filter"](d)}
if not (q["gold"] & survivors):
n_excluded += 1
return n_excluded / n_with_gold if n_with_gold else 0.0
rate = filter_false_exclusion_rate(queries, docs)
print(f"filter_false_exclusion_rate = {rate:.2%}")
# filter_false_exclusion_rate = 50.00%
# Correct Recall@k keeps the original gold set as its denominator.
def standard_recall_at_k(queries, docs, k=10):
recalls = []
for q in queries:
# demo only: the survivor set stands in for a ranked run.
# A real harness ranks the survivors first, then slices to k.
survivors = [d for d in docs if q["filter"](d)][:k]
survivor_ids = {d["id"] for d in survivors}
recalls.append(len(q["gold"] & survivor_ids) / len(q["gold"]))
return sum(recalls) / len(recalls) if recalls else 0.0
print(f"standard recall@10 = {standard_recall_at_k(queries, docs):.2%}")
# standard recall@10 = 50.00%
# INVALID: rebuilding the gold set after filtering changes the question.
# It drops queries whose relevant documents did not survive, then scores 100%.
def invalid_recall_over_filtered_gold(queries, docs, k=10):
recalls = []
all_doc_ids = {d["id"] for d in docs}
for q in queries:
all_survivors = {d["id"] for d in docs if q["filter"](d)}
filtered_gold = q["gold"] & all_doc_ids & all_survivors
if not filtered_gold:
continue
top_k_ids = set(list(all_survivors)[:k])
recalls.append(len(filtered_gold & top_k_ids) / len(filtered_gold))
return sum(recalls) / len(recalls) if recalls else 0.0
invalid = invalid_recall_over_filtered_gold(queries, docs)
print(f"INVALID recall (filtered gold) = {invalid:.2%}")
# INVALID recall (filtered gold) = 100.00%
assert rate == 0.5
assert standard_recall_at_k(queries, docs) == 0.5
assert invalid == 1.0
La moitié des requêtes perdent leur document gold à cause du filtre ; le Recall@10 correct tombe donc à 50 %. Ce score détecte le symptôme, mais ne peut pas l’attribuer. Le taux de fausse exclusion montre que le prédicat a supprimé deux réponses avant l’exécution du retriever. L’évaluateur volontairement invalide renvoie 100 % uniquement parce qu’il retire ces échecs de son gold set. Aucun modèle ne peut récupérer un document filtré.
Le taux de 50 % ci-dessus est reproduit comme test unitaire dans le repository compagnon : tests/test_filter_exclusion.py::test_50_percent_exclusion_rate. Le Notebook 04 l’exécute sur SciFact avec des métadonnées synthétiques afin que vous puissiez observer un filtre réel annuler le recall ; la métrique runtime (avec son complément précision/rappel du prédicat) se trouve dans evaluation/filter_exclusion.py.
Métrique complémentaire : précision et rappel du prédicat
Lorsque le filtrage est dynamique (par exemple lorsqu’un LLM extrait des prédicats de filtre depuis la requête), traitez l’extracteur de prédicats comme un modèle de classification et évaluez-le comme tel. Mesurez la précision et le rappel des prédicats sur un ensemble annoté de paires (query, correct predicate). Un taux d’erreur des prédicats ne se traduit pas directement par une perte équivalente du recall du retrieval ; mesurez la fréquence à laquelle ces erreurs excluent un document gold. Dès qu’un filtre hard supprime le document gold, aucun reranking ne peut aider.
Soft boost vs. hard filter
Cette métrique force une décision de conception. Utilisez des filtres hard lorsque la correction est binaire : juridiction légale, limites d’ACL, publié contre brouillon. Utilisez des soft boosts lorsque la pertinence est graduée : préférence de locale, récence, version. Sans mesure du taux d’exclusion, le mauvais choix est difficile à détecter.
La règle de décision, mesurable :
For each filter predicate F:
hard_recall_F = retrieval_recall@k with F as a hard filter
soft_recall_F = retrieval_recall@k with F as a +0.X rerank boost
hard_precision = relevant_in_top_k / k under hard filter
soft_precision = relevant_in_top_k / k under soft boost
exclusion_rate = % of queries where the gold doc was filtered out (hard)
Use hard filter only if exclusion_rate < ε AND hard_precision >> soft_precision.
Otherwise prefer soft boost.
Choisissez ε à partir du dommage causé par une fausse exclusion, du bénéfice en précision supplémentaire et de la taille de l’échantillon d’évaluation. Un article consacré à cet arbitrage est prévu ; consultez les suites listées à la fin.
Partie 6 : évaluation de la génération
Les métriques de retrieval indiquent que le système pourrait répondre correctement. Elles ne disent pas qu’il l’a effectivement fait. Les métriques de génération couvrent cet écart.
Faithfulness et groundedness
La faithfulness de RAGAS décompose la réponse en claims atomiques (énoncés factuels courts et autonomes), puis vérifie chacun d’eux par rapport au contexte récupéré via un LLM judge :
Le score correspond au pourcentage de claims soutenus. Cette structure est plus utile qu’un nombre unique, car elle indique quels claims ne sont pas étayés. Le code de production se trouve dans le package ragas. Ce qui suit est un schéma d’API legacy sans exécution pour RAGAS 0.4.3, et non un programme prêt à copier-coller. Pour l’exécuter, verrouillez ragas==0.4.3, installez un client fournisseur, configurez ses identifiants, créez un LLM RAGAS adossé à ce fournisseur et transmettez-le via evaluator_llm. La configuration est volontairement placée hors du bloc et dépend du fournisseur ; la fonction reçoit l’objet configuré en argument au lieu de supposer une valeur par défaut. Pour le nouveau code, utilisez l’API actuelle fondée sur les collections, que RAGAS documente comme chemin de migration depuis l’API legacy des métriques :
# No-run: legacy RAGAS 0.4.3 API shape.
# Before calling this function, configure a provider-backed RAGAS LLM.
# For example, with the provider credentials already set:
# from openai import AsyncOpenAI
# from ragas.llms import llm_factory
# evaluator_llm = llm_factory("gpt-4o-mini", client=AsyncOpenAI())
from ragas import EvaluationDataset, evaluate
from ragas.metrics import (
Faithfulness,
LLMContextPrecisionWithoutReference,
ResponseRelevancy,
)
def run_legacy_ragas_043(evaluator_llm):
dataset = EvaluationDataset.from_list([
{
"user_input": "How many moons does Mars have?",
"response": "Mars has two moons, Phobos and Deimos.",
"retrieved_contexts": ["Mars has two moons named Phobos and Deimos."],
"reference": "Mars has two moons.",
}
])
return evaluate(
dataset,
metrics=[Faithfulness(), ResponseRelevancy(), LLMContextPrecisionWithoutReference()],
llm=evaluator_llm,
)
RAGAS a renommé ces champs en 0.2 (question → user_input, answer → response, contexts → retrieved_contexts, ground_truth → reference). Le code écrit pour la version 0.1 échoue de manière déroutante : les anciennes clés sont supprimées plutôt que rejetées, si bien que l’erreur signale l’absence des nouvelles colonnes.
Voici la même boucle déroulée avec un judge déterministe de substitution, afin d’en visualiser la forme de bout en bout.
def extract_claims(answer: str) -> list[str]:
# Production: an LLM call that decomposes the answer.
# Demo: split on sentence-final punctuation.
return [c.strip() for c in answer.replace("?", ".").replace("!", ".").split(".") if c.strip()]
def verify_claim(claim: str, context: str) -> bool:
# Production: an NLI (natural-language inference) model or LLM judge.
# Demo: a deterministic stand-in so the example runs offline.
entailed_pairs = {
"Mars has two moons": True,
"Phobos and Deimos orbit Mars": True,
"Mars has a thick atmosphere": False, # unsupported by context
"Curiosity landed in 2012": True,
}
for k, v in entailed_pairs.items():
if k.lower() in claim.lower() or claim.lower() in k.lower():
return v
words = [w.lower() for w in claim.split() if len(w) > 3]
return all(w in context.lower() for w in words) if words else False
context = (
"Mars has two moons, Phobos and Deimos. NASA's Curiosity rover "
"landed on Mars in 2012."
)
answer = (
"Mars has two moons. Phobos and Deimos orbit Mars. "
"Mars has a thick atmosphere. Curiosity landed in 2012."
)
claims = extract_claims(answer)
verdicts = [(c, verify_claim(c, context)) for c in claims]
faithfulness = sum(1 for _, ok in verdicts if ok) / len(verdicts)
for c, ok in verdicts:
print(f" [{'✓' if ok else '✗'}] {c}")
print(f"faithfulness = {faithfulness:.2f}")
# faithfulness = 0.75 (one unsupported claim about the atmosphere)
La structure est importante. En production, verify_claim devient un modèle NLI ou un appel à un LLM. Le reste du harness reste identique : extraire, vérifier, agréger.
Extraction et vérification de claims de bout en bout sur des réponses SciFact générées : notebook 05 ; module : evaluation/faithfulness.py. Le repository exécute la même boucle avec deux familles de judges — le modèle du générateur lui-même et un judge d’une autre famille (RAG_EVALS_JUDGE_MODEL) — ainsi qu’une baseline lexicale déterministe, afin de montrer où les familles divergent.
Une alternative conçue spécifiquement pour remplacer le LLM-as-judge est HHEM-2.1-Open (Hughes Hallucination Evaluation Model, Vectara), un classifier fine-tuné pour la détection des hallucinations. Sa model card documente le checkpoint, le score brut entre 0 et 1 qu’il émet et les résultats de balanced accuracy sur AggreFact et RAGTruth. Aucun seuil de décision par défaut n’est publié : c’est à vous de le choisir. Considérez ces résultats comme des éléments de la model card, et non comme une garantie sur votre corpus : calibrez le seuil sur des labels locaux et comparez-le à votre judge avant le déploiement.
Évaluation des faits atomiques
FActScore (Min et al., EMNLP 2023) décompose les générations longues en faits atomiques, récupère des éléments de preuve pour chaque fait, annote chacun comme supported / not-supported et rapporte la fraction soutenue :
Implémentation de référence : shmsw25/FActScore. Elle fonctionne bien pour les biographies, résumés et autres sorties longues. Attention : des faits triviaux répétitifs peuvent gonfler le score, et les attaques de type « MontageLie » (faits vrais présentés dans un ordre trompeur) peuvent la contourner. VeriScore gère les claims avec les modificateurs nécessaires ; le filtre Core aide à éviter le remplissage artificiel de faits.
Exactitude des citations
Suivez la précision des citations (les spans cités étayent effectivement le claim) et le rappel des citations (les claims qui devraient être cités le sont) :
Le TREC 2024 RAG Track définit un protocole reproductible d’évaluation du support. Thakur et al. (SIGIR 2025) rapportent que GPT-4o est en accord avec les juges humains 56 % du temps lors d’une évaluation manuelle from scratch, et 72 % après post-édition des prédictions du LLM. C’est un multiplicateur de force utile dans leurs conditions, pas un remplacement de l’évaluation humaine dans les contextes à forts enjeux. Pour une approximation automatisée, ALCE (Gao et al., EMNLP 2023) implémente la précision et le rappel des citations avec une vérification fondée sur le NLI.
Correction, complétude et refus de la réponse
- Correction de la réponse par rapport à la ground truth : lorsque vous en disposez, exact match ou token-F1 pour les tâches à réponse courte (
evaluate.load("squad")), similarité sémantique pour les réponses ouvertes (bert-score, similarité cosinus d’embeddings viasentence-transformersouAnswerCorrectnessde RAGAS). - Complétude via les nuggets : un « nugget » est une information atomique qu’une réponse correcte doit contenir (par exemple, pour « Quand l’entreprise a-t-elle été fondée ? », les nuggets pourraient être
{year: 1994, founder: Jane Doe}). L’AutoNuggetizer de TREC extrait depuis une référence les nuggets gold d’une réponse correcte, puis évalue la fraction couverte par le système — forte corrélation avec l’évaluation manuelle sur 21 topics × 45 runs au TREC 2024. - Comportement de refus : les requêtes dont la réponse n’existe pas dans le corpus doivent produire une abstention, pas une hallucination. Suivez la précision de l’abstention (refus corrects) et le rappel de l’abstention (requêtes hors périmètre ayant déclenché un refus). NoMIRACL est le benchmark public ; dans votre propre domaine, annotez une portion de requêtes hors périmètre et suivez la précision de l’abstention.
Vérification post-génération
Les gains de fiabilité les moins coûteux viennent souvent de contrôles déterministes postérieurs à la génération, plutôt que de modèles plus grands.
- Contrôle d’ancrage des entités : chaque entité nommée de la réponse doit apparaître dans le contexte récupéré, ou pouvoir en être dérivée. Un contrôle simple par regex + exact match (ou
spaCy’sentssur une chaîne de contexte normalisée) détecte une fraction étonnamment importante des hallucinations. - Vérification des claims : extrayez les claims, exécutez un NLI par rapport au contexte, puis échouez ou signalez ceux qui sont sous le seuil. Modèles NLI-as-faithfulness :
cross-encoder/nli-deberta-v3-large,MoritzLaurer/DeBERTa-v3-large-mnli-fever-anli-ling-wanli. Cela ajoute de la latence, mais vaut la peine dans les domaines à forts enjeux. - Self-consistency (Wang et al., ICLR 2023) : échantillonnez plusieurs générations à une température > 0 ; rapportez le taux d’accord (par exemple la proportion de générations correspondant à la réponse modale, ou le BERTScore par paires) ; choisissez le nombre d’échantillons à partir de la courbe stabilité–coût et signalez les réponses à faible accord pour une revue humaine.
- Calibration de la confiance : recueillez une confiance verbalisée (« Quel est votre niveau de confiance, de 0 à 1 ? ») et comparez-la à la correction réelle sur le jeu d’évaluation. Tracez une courbe de calibration et rapportez l’Expected Calibration Error : , où sont les bins de confiance. Implémentations :
netcal,torchmetrics.CalibrationError. Un modèle qui affiche une confiance de 0,9 devrait être correct dans environ 90 % des cas comparables ; mesurez l’écart au lieu de supposer la calibration.
Partie 7 : évaluation d’un RAG fondé sur une ontologie
Les métriques standard ci-dessus couvrent le RAG sur corpus ouvert. Si votre RAG effectue le retrieval sur une ontologie structurée, une taxonomie ou un graphe de connaissances, ces métriques sont nécessaires mais insuffisantes. Exemples : produits d’un catalogue, conditions de SNOMED, composants d’une BOM et techniques de sécurité de MITRE ATT&CK. Vous devez également mesurer la couche ontologique.
Précision de l’entity linking
La première tâche consiste à mapper une mention de la requête vers une entité de l’ontologie (« Aspirin » → wikidata:Q18216, « the 737 » → aircraft:Boeing_737).
- Précision/rappel/F1 au niveau des mentions : métriques standard calculées par rapport aux spans de mentions gold (avec
seqevalou un comparateur d’ensembles de spans). - Précision de la désambiguïsation : parmi les mentions correctement détectées, quelle fraction est associée au bon ID d’entité ? Les références publiques incluent ReFinED, REL et GENRE ; des benchmarks comme AIDA-CoNLL et BELB montrent que les résultats varient selon le système et le domaine.
- Gestion de NIL : précision/rappel sur les cas où l’entité n’existe pas dans l’ontologie. Mesurez séparément le sur-linking vers des entités proches mais incorrectes et l’abstention correcte.
Évaluation tenant compte de la hiérarchie
Une précision simple traite de la même manière « prédire Sedan alors que la vérité est Hatchback » et « prédire Sedan alors que la vérité est Submarine ». Ces erreurs ne se valent pas.
-
Précision/rappel/F1 hiérarchiques (Kosmopoulos et al., 2015) : attribuez du crédit aux ancêtres et descendants dans le DAG de l’ontologie. Avec , le nœud prédit et tous ses ancêtres, et , le nœud vrai et tous ses ancêtres :
Implémentez cette mesure avec
networkxsur le graphe de l’ontologie : enrichissez chaque prédiction et chaque label avec ses ancêtres, puis calculez les intersections d’ensembles ci-dessus. -
Similarité de Wu-Palmer entre l’entité prédite et l’entité gold dans la taxonomie (Wu & Palmer, 1994) :
où LCA est le plus proche ancêtre commun dans la taxonomie. Disponible nativement dans NLTK pour WordNet (
from nltk.corpus import wordnet as wn; wn.synset("car.n.01").wup_similarity(wn.synset("truck.n.01"))) ; pour les taxonomies custom, calculez le LCA avecnetworkx. -
Taux de confusion entre frères et parents : suivez séparément les confusions avec des frères, des parents et des enfants —
count_sibling / total_errors,count_parent / total_errors,count_descendant / total_errors. Utilisez des exemples revus manuellement pour déterminer si les erreurs entre frères viennent de mentions ambiguës ou si les erreurs vers les parents viennent d’une généralisation excessive.
Taux de fausse exclusion par filtre (rappel, désormais critique)
Dans les systèmes fondés sur une ontologie, les filtres hard proviennent souvent de l’ontologie elle-même (« ne récupérer que les documents tagués avec la catégorie X »). La métrique de taux d’exclusion (définie dans la Partie 5) devient un signal de correction principal. Une prédiction de catégorie erronée peut annuler le recall ; le taux d’exclusion attribue cette perte au filtre.
Conformité de la génération contrainte
Lorsque votre sortie doit respecter une ontologie (chaque nom d’entité de la réponse doit être un membre valide de l’ontologie ; chaque prédicat doit provenir d’un vocabulaire fermé), mesurez :
- Taux de validité du schéma : pourcentage de sorties qui peuvent être parsées et validées par rapport au schéma de l’ontologie. Validez avec
jsonschemaoupydantic. JSONSchemaBench est le benchmark public pour les structured outputs en général ; pour les schémas propres à une ontologie, construisez votre propre validator. - Conformité au vocabulaire : pourcentage d’entités nommées dans la sortie qui sont des IDs valides de l’ontologie — un simple contrôle d’appartenance à l’ensemble du vocabulaire fermé.
- Conformité sémantique : une sortie syntaxiquement valide peut tout de même sélectionner une entité valide mais incorrecte. Associez la conformité à la correction de la réponse en aval.
Les frameworks de constrained decoding (Outlines, XGrammar, Guidance, OpenAI Structured Outputs) sont conçus pour garantir la validité du schéma. JSONSchemaBench compare l’efficacité, la couverture et la qualité entre implémentations. Réexécutez les cas correspondant à vos schémas et à votre backend de serving, car la couverture et la latence dépendent des deux.
Auditabilité
Pour les systèmes fondés sur une ontologie dont les réponses sont soumises à revue :
- Complétude des citations : pourcentage de claims factuels comportant au moins une citation vérifiable.
- Profondeur de provenance : pourcentage de citations remontant jusqu’à un document source doté d’un ID stable, et non jusqu’à un simple hash de chunk.
- Taux de reproductibilité : à snapshot fixe, réexécuter la même requête renvoie la même réponse. Verrouillez la version du modèle, le runtime, la configuration de decoding et la seed, puis définissez le taux de répétition requis selon les besoins d’auditabilité du workflow. Une température nulle ne garantit pas à elle seule le déterminisme. Un échec peut venir de la génération, du runtime de serving ou de n’importe quelle étape en amont.
Partie 8 : évaluation au niveau système
Qualité globale des réponses
- LLM-as-judge (Zheng et al., NeurIPS 2023) : approche d’évaluation fondée sur un modèle et scalable. G-Eval (Liu et al., EMNLP 2023) déduit un rubric à partir d’un critère en langage naturel, puis attribue un score pondéré par les log-probs. L’accord dépend du judge, de la tâche, du prompt et du jeu de calibration.
- Préférence par paires : présentez au judge la réponse A contre la réponse B, puis enregistrez sa préférence. Cela évite les problèmes de calibration des scores absolus. MT-Bench a rapporté un accord du judge GPT-4 supérieur à 80 % avec les préférences humaines et l’accord interhumain dans les conditions de son benchmark ; ne transposez pas ce taux à un autre domaine sans calibration.
Le LLM-as-judge présente de vrais biais :
- Biais de position : les judges préfèrent la première ou la seconde réponse indépendamment de sa qualité. Atténuation : randomisez l’ordre, ou exécutez les deux ordres et faites la moyenne.
- Biais en faveur de la verbosité : les judges peuvent confondre longueur et qualité. Une étude contrôlée de 2026 a constaté un comportement hétérogène entre paires d’expansion. Trois judges préféraient les réponses plus longues, Claude préférait les réponses concises et GPT-4o était approximativement neutre. Les cinq judges obtenaient de bons résultats sur les contrôles par troncature. Ces résultats dépendent du benchmark : précisez dans le rubric comment le judge doit traiter la complétude et le remplissage, puis rapportez les performances contrôlées par la longueur sur votre propre rubric.
- Biais de préférence pour soi-même : GPT-4 préfère les sorties de GPT-4 ; ce biais est corrélé à la perplexité des sorties, les judges préférant les textes qui leur sont familiers. Atténuation : utilisez une autre famille de judge que celle du système évalué. N’utilisez pas un modèle pour se juger lui-même.
Recette pratique : sélectionnez un judge sur des données de calibration annotées par des humains, randomisez l’ordre des réponses, masquez les identités des modèles et explicitez la politique de longueur dans le rubric. Ne répétez les cas que lorsque les échantillons supplémentaires réduisent réellement l’incertitude. Pour les évaluations à forts enjeux, comparez des judges issus de familles de modèles différentes et analysez les désaccords par rapport aux labels humains.
Schema-Guided Reasoning pour les judges
Les sorties libres sont une source de variation entre les exécutions d’un judge. Deux runs sur la même réponse peuvent organiser le rubric différemment et produire des scores différents. Le Schema-Guided Reasoning (SGR) rend ce rubric explicite : définissez les étapes de l’évaluation comme un schéma Pydantic, puis utilisez une sortie contrainte via Outlines, XGrammar, les structured outputs de vLLM ou response_format d’OpenAI, afin que chaque run renvoie les mêmes champs dans le même ordre.
Pour l’évaluation RAG, le schéma décompose le jugement en champs explicites et auditables, au lieu de laisser le modèle passer directement à un nombre :
from pydantic import BaseModel, Field
from typing import Literal
class FaithfulnessJudgment(BaseModel):
extracted_claims: list[str] = Field(
description="Atomic factual claims in the answer, one per item."
)
supported_claims: list[str] = Field(
description="Subset of extracted_claims that are entailed by the context."
)
unsupported_claims: list[str] = Field(
description="Subset that is NOT entailed by the context."
)
failure_mode: Literal[
"none", "fabrication", "overgeneralization", "wrong_entity", "stale_fact"
]
score: float = Field(ge=0.0, le=1.0)
rationale: str
Les champs structurés permettent de reconstituer le score sous la forme len(supported) / len(extracted) et montrent précisément sur quels claims deux judges ont divergé. Le modèle Pydantic rend également une modification du rubric visible sous forme de diff de code. La sortie contrainte garantit la forme, pas l’absence de biais dans le verdict ; la randomisation de la position, les judges de familles différentes et la calibration humaine restent donc nécessaires.
Cette approche fonctionne avec tout judge fondé sur un rubric, pas uniquement pour la faithfulness. La préférence par paires, le support des citations et la correction des refus en bénéficient également.
Un harness G-Eval / pairwise / biais de position / judge de familles différentes se trouve dans le notebook 07 ; module : evaluation/llm_judge.py. Le sweep du benchmark (make benchmark dans le repository) connecte trois modèles (gpt-5-mini, claude-haiku-4-5, gemini-2.5-flash) à un système A/B pairwise à judge tournant : chaque modèle juge les deux autres et la préférence pour soi-même apparaît sous forme de nombre.
Latence et coût
- p50, p95, p99 à chaque étape du pipeline. Choisissez le percentile du SLO et le seuil d’alerte à partir du parcours utilisateur, du volume de trafic et de l’error budget.
- Time-to-first-token contre le temps total de génération. Les utilisateurs sont sensibles au TTFT pour l’UX en streaming.
- Décomposition par étape : retrieval, reranking, génération, post-traitement. Utilisez la trace pour localiser la queue de latence au lieu de supposer quelle étape en est responsable ; consignez le device du reranker et la taille du batch lors des comparaisons.
- Coût total $/requête = embedding + retrieval + reranking + génération + stockage amorti. Suivez les p50 et p99 ; c’est dans la longue traîne que le budget est consommé.
- Taux de hit des caches au niveau des caches d’embeddings, de retrieval et de KV cache. Définissez des objectifs distincts à partir de la répétition observée, de la politique d’invalidation et du coût évité à chaque couche.
Les p50/p95/p99 par étape avec décomposition sont intégrés au notebook 08 et au runner evaluation/latency.py ; le rapport de benchmark combine latence et faithfulness dans une matrice unique que vous pouvez réexécuter avec make benchmark.
Tests A/B
- Unité de randomisation : choisissez l’unité à partir de l’estimand, des effets de report et des interférences. Utilisez une affectation par utilisateur ou par session lorsqu’une exposition répétée peut modifier le comportement ou créer une UX incohérente. Une affectation par requête n’est défendable que lorsque ces effets sont négligeables et que l’analyse modélise les observations répétées.
- Métriques primaires, guardrails et exploratoires : préenregistrez-les. Choisissez la mesure primaire en fonction du résultat produit ; les proxys de satisfaction incluent les pouces, les régénérations et le temps passé. Traitez la latence et le coût comme des guardrails lorsqu’ils contraignent l’expérience.
- Taille de l’échantillon : effectuez une analyse de puissance avant le lancement, à partir de l’effet minimal à détecter, de la variance de baseline, de l’unité d’affectation et de la règle d’arrêt.
Partie 9 : construction du jeu de test
Une métrique n’est jamais meilleure que le jeu de test sur lequel elle s’exécute. Si votre golden set couvre trois intentions et que le trafic de production en comporte douze, Recall@10 ne mesure que ces trois intentions. Pire encore, un jeu de test surajusté à des questions faciles (« Quelle est la policy de remboursement de l’entreprise ? ») peut valider un système qui échoue sur les questions difficiles (« Quelle est l’éligibilité au remboursement pour une annulation partielle relevant du Digital Services Act européen de 2023, facturée en EUR et provenant d’Irlande ? »). Le score agrégé augmente alors que le système continue d’échouer sur une partie importante du trafic de production.
Le même problème concerne la ground truth. Si les SMEs ont annoté les documents évidents mais oublié les documents pertinents de la longue traîne, Recall@k sous-évaluera un retriever qui les a effectivement trouvés. Vous optimisez les labels, pas la vérité.
Construisez d’abord le jeu de test autour de la distribution réelle et de la difficulté des requêtes. Choisissez ensuite les métriques qui répondent aux modes de défaillance ciblés et ajustez le système à partir de ces métriques.
Génération synthétique de requêtes
Utilisez un LLM pour générer des questions à partir de votre corpus :
- Par chunk : « Générer 3 questions qu’un utilisateur pourrait poser et auxquelles ce chunk répond. »
- Multi-hop : échantillonnez deux chunks, puis générez une question nécessitant les deux.
- Adversarial : générez des questions avec des entités distractrices, des formulations quasi dupliquées et des mentions ambiguës.
RAGAS intègre une distribution des types de questions (raisonnement, conditionnel, multi-contexte). DataMorgana génère des benchmarks synthétiques configurables selon les catégories d’utilisateurs et de questions. Les données synthétiques sont utiles pour les démarrages à froid et les tests de couverture. Elles ne peuvent pas remplacer les requêtes réelles.
Construction du dataset gold
Les données sélectionnées par des humains ancrent le golden set.
- Échantillonnez des requêtes utilisateur réelles (ou simulées avant le lancement), stratifiées par intention.
- Demandez à des SMEs de répondre à chaque question et d’identifier le ou les documents contenant la réponse.
- Dimensionnez l’ensemble à partir de la matrice de couverture et de l’intervalle de confiance nécessaire aux décisions de release ; la couverture compte davantage qu’un nombre de requêtes repris ailleurs.
- Reconstituez le jeu lorsque la cadence de release, les signaux de drift, le risque du domaine et la capacité d’annotation le justifient.
Jeux de test adversariaux
- Contrefactuels : remplacez les entités clés de la requête. Le système récupère-t-il les bons chunks pour la requête modifiée ?
- Distracteurs : requêtes pour lesquelles le corpus contient une réponse plausible mais erronée qui ne devrait pas être récupérée. C’est ce que RGB (Chen et al., AAAI 2024) soumet à un stress test : robustesse au bruit, rejet des négatifs, intégration de l’information et robustesse contrefactuelle.
- Négation et quantificateurs : requêtes contenant « ne… pas », « sauf » et « uniquement ». Les retrievers denses ont souvent des difficultés avec ces formulations.
- Hors périmètre : requêtes sans réponse dans le corpus. Le système doit dire « Je ne sais pas », pas halluciner. NoMIRACL couvre ce cas. Évaluez explicitement l’abstention sur vos types de requêtes de production.
Couverture et évaluation continue
- Construisez une matrice de couverture : intention de requête × type de document × branche de l’ontologie. Visez au moins une requête par cellule. Les cellules vides sont des zones non surveillées où les régressions peuvent se cacher.
- Exécutez un sous-ensemble de régression rapide et borné sur chaque PR, et la suite complète selon un calendrier plus lent.
- Planifiez l’évaluation complète du golden set en fonction de la cadence de release et du coût de l’évaluation ; les release candidates constituent un gate naturel.
- Planifiez l’évaluation du drift en fonction du volume de trafic, des changements attendus et du risque. Utilisez un échantillon de production glissant et stratifiez-le selon le feedback, plutôt que de modifier silencieusement la distribution cible.
Partie 10 : monitoring en production
La suite d’évaluation que vous livrez décrit le système au lancement. Le trafic de production évolue ensuite.
Feedback implicite et explicite
- Taux de clics / d’ouverture sur les sources citées (si votre UI les expose).
- Temps passé sur la réponse.
- Taux de régénération : pourcentage de réponses que l’utilisateur redemande ou fait refaire au système. Considérez-le comme un signal d’insatisfaction parmi d’autres et calibrez-le sur des conversations revues.
- Taux de copie / partage / export — signal positif fort.
- Patterns de suivi : des formulations comme « Êtes-vous sûr ? » ou « Et concernant X ? » suggèrent une défiance.
- Pouces haut/bas avec catégories de raisons facultatives (incorrect, incomplet, hors sujet, nuisible, lent). Les éditions inline, lorsque l’UI les permet, sont généralement le signal de feedback le plus informatif.
Détection du drift
- Drift des requêtes : suivez la distribution des embeddings de requêtes par rapport à une fenêtre de référence avec la divergence KL, la MMD ou un détecteur fondé sur un modèle. Alertez en cas de shift, puis déboguez par segment.
- Drift des embeddings : verrouillez un ensemble de sondes constitué de documents fixes ; ré-embeddez-les périodiquement et mesurez le cosinus par rapport aux embeddings d’origine. Même un léger drift entre versions d’un modèle fournisseur peut dégrader silencieusement le retrieval. Un stockage versionné des embeddings (snapshots immuables par version) est l’atténuation la moins coûteuse.
- Drift des performances : suivez dans le temps des métriques équivalentes à celles de la production (taux de régénération par intention). Les hausses soudaines indiquent une rupture ; les dérives lentes indiquent que le monde a changé.
Évaluation shadow et human-in-the-loop
Exécutez le système candidat en parallèle de la production, comparez les sorties offline et ne les servez pas aux utilisateurs. Cela détecte les régressions avant le lancement. Le coût d’inférence augmente, mais il n’y a aucun impact client.
Pour la revue human-in-the-loop (HITL) :
- Échantillonnez les sorties à faible confiance et placez-les dans une file de revue.
- Incluez un échantillon aléatoire du trafic de production pour une revue en aveugle ; définissez son taux selon le volume de trafic, le risque et la capacité des reviewers.
- Donnez davantage de poids aux sorties ayant reçu un pouce vers le bas.
- Utilisez les sorties revues pour enrichir le golden set.
Le minimum de guardrails
Déclenchez des alertes sur les signaux suivants, par ordre de priorité :
- Score de faithfulness/HHEM inférieur au seuil sur un échantillon de production glissant.
- Latence p95 supérieure au SLO.
- Taux de fausse exclusion par filtre supérieur au seuil (fondé sur un échantillon).
- Taux de régénération en dehors d’une bande de contrôle calibrée localement, tenant compte de la taille de la fenêtre, du trafic, de la saisonnalité et du budget de fausses alertes.
- Coût/requête supérieur au budget.
Si une alerte se déclenche sans changement correspondant du code ou du modèle, vous êtes probablement face à du drift. Si elle survient après une modification, il s’agit probablement d’une régression. Dans les deux cas, vous obtenez un signal avant l’arrivée des tickets au support.
Réserves
- Les objectifs sont locaux, pas universels. Tout nombre qualifié d’illustratif dans ce guide est un exemple de configuration ou un résultat détaillé, pas un seuil de release. Calibrez les seuils sur votre domaine, les enjeux, l’incertitude du jeu d’évaluation et les attentes des utilisateurs.
- L’écosystème des frameworks évolue rapidement. Les versions de HHEM, les noms des métriques RAGAS, les model cards et l’ordre des leaderboards peuvent changer après publication. Revérifiez les sources liées et refaites les benchmarks avant de vous engager.
- Les chiffres d’accord du LLM-as-judge comportent des astérisques. La valeur de 80 % entre GPT-4 et les humains provient des conditions de MT-Bench / Chatbot Arena. Sur des domaines de niche et des cas adversariaux, l’accord chute fortement. Utilisez les judges comme multiplicateurs de force, pas comme remplacement des contrôles ponctuels.
- Les uplifts de benchmarks fournisseurs sont souvent difficiles à reproduire indépendamment. Reproduisez-les sur vos propres données avant de croire un chiffre, notamment pour les rerankers et systèmes OCR récents.
- Aucune métrique ne remplace l’examen des sorties. Planifiez une revue en aveugle d’un échantillon aléatoire de production selon le trafic, le risque et la capacité de revue. Les métriques rendent cette pratique scalable ; elles ne la remplacent pas.
À suivre dans cette série
C’était l’index. Voici les suites que je prévois :
- Soft Boosts vs. Hard Filters : analyse approfondie du taux de fausse exclusion par filtre, avec du code, de vrais exemples de production et un framework de décision.
- Chunking Is the Hidden Variable : expérience contrôlée comparant le chunking récursif, sémantique, late et structurel sur trois corpus.
- Reranker Selection in 2026 : BGE vs. Cohere vs. ZeRank vs. modèles cross-encoder actuels, comparés sur le coût, la latence et l’uplift.
- Ontology-Grounded RAG: An End-to-End Walkthrough : construction du harness d’évaluation complet d’un système de retrieval fondé sur les entités.
- LLM-as-Judge Without the Self-Preference Trap : recettes pratiques pour une évaluation automatisée non biaisée.
- Online Evaluation in Production : patterns d’instrumentation, politiques d’alerte et dashboards capables de détecter les vraies régressions.
Références
Frameworks et benchmarks
- Es et al., Ragas: Automated Evaluation of Retrieval Augmented Generation, 2023.
- Documentation RAGAS et GitHub.
- Saad-Falcon et al., ARES: An Automated Evaluation Framework for Retrieval-Augmented Generation Systems, NAACL 2024.
- TruLens, DeepEval, Arize Phoenix.
- Thakur et al., BEIR: A Heterogenous Benchmark for Zero-shot Evaluation of Information Retrieval Models, NeurIPS 2021.
- MTEB Leaderboard.
- TREC 2024 RAG Track.
- Pradeep et al., Initial Nugget Evaluation Results for the TREC 2024 RAG Track with the AutoNuggetizer Framework, 2024.
Retrieval et ranking
- Cormack, Clarke, Buettcher, Reciprocal Rank Fusion Outperforms Condorcet and Individual Rank Learning Methods, SIGIR 2009.
- Gao et al., Precise Zero-Shot Dense Retrieval Without Relevance Labels (HyDE), 2022.
- Jeong et al., Adaptive-RAG: Learning to Adapt Retrieval-Augmented Large Language Models through Question Complexity, NAACL 2024.
- Anthropic, Introducing Contextual Retrieval, septembre 2024.
- Günther et al., Late Chunking: Contextual Chunk Embeddings Using Long-Context Embedding Models, 2024.
Génération, faithfulness et judges
- Min et al., FActScore: Fine-grained Atomic Evaluation of Factual Precision in Long Form Text Generation, EMNLP 2023.
- Liu et al., Lost in the Middle: How Language Models Use Long Contexts, TACL 2023.
- Chen et al., Benchmarking Large Language Models in Retrieval-Augmented Generation (RGB), AAAI 2024.
- Vectara, HHEM-2.1-Open hallucination evaluation model.
- Zheng et al., Judging LLM-as-a-Judge with MT-Bench and Chatbot Arena, NeurIPS 2023.
- Thakur et al., Support Evaluation for the TREC 2024 RAG Track: Comparing Human versus LLM Judges, SIGIR 2025.
- Thakur et al., NoMIRACL: Knowing When You Don’t Know for Robust Multilingual Retrieval-Augmented Generation, 2023.
- Geng et al., JSONSchemaBench: A Rigorous Benchmark of Structured Outputs for Language Models, 2025.
- Kosmopoulos et al., Evaluation Measures for Hierarchical Classification: a unified view and novel approaches, 2015.
Drift et production
- Evidently, Embedding drift detection methods compared.
Code compagnon
slavadubrov/rag-evals-demo— harness exécutable pour chaque métrique de cet article sur le corpus SciFact, ainsi qu’un sweep de benchmark chunking × embedding × LLM. Notebooks 00–09, tests unitaires qui figent les exemples détaillés ci-dessus et index Qdrant embarqué pour une exécution sans Docker.