7. OptimizationAvancé

Tester un Systeme RAG : La Methodologie en 5 Etapes que Google Utilise (Et Vous Devriez Aussi)

28 août 2026
22 min de lecture
Equipe Ailog

Methodologie complete pour tester un systeme RAG en 5 etapes : golden dataset, tests unitaires retrieval, tests generation, evaluation end-to-end RAGAS, A/B testing production.

TL;DR

Tester un systeme RAG ne se resume pas a "est-ce que la reponse a l'air correcte". Google, Anthropic et les meilleures equipes IA suivent une methodologie en 5 etapes : (1) construire un golden dataset, (2) tester le retrieval unitairement, (3) tester la generation unitairement, (4) evaluation end-to-end avec RAGAS, (5) A/B testing en production. Ce guide detaille chaque etape avec du code, des metriques concretes et les pieges a eviter.


Pourquoi 90% des systemes RAG echouent en production

La plupart des equipes testent leur RAG en posant quelques questions a la main. C'est comme tester une voiture en regardant si elle demarre. Les vrais problemes apparaissent apres le deploiement :

ProblemeFrequenceImpact
Hallucinations non detectees15-30% des reponsesPerte de confiance utilisateur
Retrieval hors-sujet20-40% des requetesReponses non pertinentes
Regression apres mise a jourA chaque deploiementDegradation silencieuse
Biais de confirmationPermanentFaux sentiment de qualite

La solution : une methodologie de test systematique, automatisee et reproductible.


Etape 1 : Construire un Golden Dataset

Le golden dataset est votre source de verite. C'est un ensemble de paires question-reponse-contexte valide par des experts humains.

Structure du golden dataset

DEVELOPERjson
{ "id": "GD-001", "question": "Quels sont les delais de livraison en France ?", "expected_answer": "Les delais de livraison en France metropolitaine sont de 2 a 5 jours ouvrables pour la livraison standard et 24h pour la livraison express.", "expected_contexts": [ "doc_livraison_france.md#section-delais", "faq_expedition.md#question-12" ], "category": "logistics", "difficulty": "easy", "metadata": { "created_by": "support_team", "created_at": "2026-01-15", "last_validated": "2026-03-01" } }

Combien de paires faut-il ?

Taille du corpusGolden dataset recommandeCouverture
< 100 documents50 paires1 paire / 2 docs
100-1000 docs100-200 pairesPrincipales categories
1000-10000 docs200-500 pairesEchantillon stratifie
> 10000 docs500-1000 pairesCategories + edge cases

Generer un golden dataset semi-automatiquement

DEVELOPERpython
from openai import OpenAI import json client = OpenAI() def generate_golden_pairs(documents: list[dict], n_pairs: int = 5) -> list[dict]: """Genere des paires Q&A a partir de documents reels.""" golden_pairs = [] for doc in documents: prompt = f"""A partir du document suivant, genere {n_pairs} paires question-reponse. Document: {doc['content'][:3000]} Regles: - Questions variees (factuelles, comparatives, oui/non) - Reponses basees UNIQUEMENT sur le document - Inclure la section source exacte Format JSON: [{{"question": "...", "answer": "...", "source_section": "..."}}]""" response = client.chat.completions.create( model="gpt-4o", messages=[{"role": "user", "content": prompt}], response_format={"type": "json_object"} ) pairs = json.loads(response.choices[0].message.content) for pair in pairs.get("pairs", []): golden_pairs.append({ "question": pair["question"], "expected_answer": pair["answer"], "expected_contexts": [f"{doc['id']}#{pair['source_section']}"], "category": doc.get("category", "general"), "auto_generated": True, "validated": False # A valider par un humain }) return golden_pairs # Utilisation documents = [ {"id": "doc_001", "content": "...", "category": "product"}, {"id": "doc_002", "content": "...", "category": "support"}, ] golden = generate_golden_pairs(documents, n_pairs=5) print(f"Generated {len(golden)} pairs - REVIEW REQUIRED")

Validation humaine : la regle des 3

Chaque paire du golden dataset doit etre validee par au moins 1 personne. Pour les systemes critiques (medical, juridique), utilisez la regle des 3 : 3 validateurs independants, majorite requise.


Etape 2 : Tester le Retrieval unitairement

Le retrieval est le composant le plus critique. Si les mauvais documents sont recuperes, aucun LLM ne pourra generer une bonne reponse.

Metriques cles

MetriqueFormuleObjectifInterpretation
Recall@kDocuments pertinents dans top-k / Total pertinents> 0.85"On trouve bien les bons docs"
Precision@kDocuments pertinents dans top-k / k> 0.60"Les docs trouves sont bons"
MRR1 / rang du premier doc pertinent> 0.70"Le bon doc est en haut"
nDCG@kScore DCG normalise> 0.75"Le classement est bon"
Hit RateRequetes avec au moins 1 doc pertinent / Total> 0.90"On trouve toujours quelque chose"

Code de test retrieval

DEVELOPERpython
import numpy as np from dataclasses import dataclass @dataclass class RetrievalResult: query: str retrieved_docs: list[str] relevant_docs: list[str] def recall_at_k(result: RetrievalResult, k: int = 10) -> float: """Recall@k - proportion de documents pertinents retrouves.""" retrieved_set = set(result.retrieved_docs[:k]) relevant_set = set(result.relevant_docs) if not relevant_set: return 1.0 return len(retrieved_set & relevant_set) / len(relevant_set) def precision_at_k(result: RetrievalResult, k: int = 10) -> float: """Precision@k - proportion de documents retrouves qui sont pertinents.""" retrieved = result.retrieved_docs[:k] relevant_set = set(result.relevant_docs) if not retrieved: return 0.0 return sum(1 for doc in retrieved if doc in relevant_set) / len(retrieved) def mrr(result: RetrievalResult) -> float: """Mean Reciprocal Rank - inverse du rang du premier resultat pertinent.""" relevant_set = set(result.relevant_docs) for i, doc in enumerate(result.retrieved_docs): if doc in relevant_set: return 1.0 / (i + 1) return 0.0 def evaluate_retrieval(golden_dataset: list[dict], retriever) -> dict: """Evalue le retrieval sur le golden dataset complet.""" metrics = {"recall@5": [], "recall@10": [], "precision@5": [], "mrr": [], "hit_rate": []} for item in golden_dataset: retrieved = retriever.search(item["question"], top_k=10) result = RetrievalResult( query=item["question"], retrieved_docs=[doc.id for doc in retrieved], relevant_docs=item["expected_contexts"] ) metrics["recall@5"].append(recall_at_k(result, 5)) metrics["recall@10"].append(recall_at_k(result, 10)) metrics["precision@5"].append(precision_at_k(result, 5)) metrics["mrr"].append(mrr(result)) metrics["hit_rate"].append(1.0 if recall_at_k(result, 10) > 0 else 0.0) return {k: np.mean(v) for k, v in metrics.items()}

Seuils de qualite

MetriqueMinimum acceptableBonExcellent
Recall@100.750.850.95+
Precision@50.500.650.80+
MRR0.600.750.85+
Hit Rate0.800.900.95+

Etape 3 : Tester la Generation unitairement

Meme avec un retrieval parfait, le LLM peut halluciner, ignorer le contexte, ou generer des reponses hors-sujet.

Metriques de generation

MetriqueMesureMethode
FaithfulnessLa reponse est fidele au contexteLLM-as-judge
Answer RelevancyLa reponse repond a la questionLLM-as-judge
Answer CorrectnessLa reponse correspond au goldenCosine similarity + LLM
HarmfulnessLa reponse est-elle dangereuseClassification

Test de faithfulness avec LLM-as-judge

DEVELOPERpython
from openai import OpenAI client = OpenAI() def evaluate_faithfulness(context: str, answer: str) -> dict: """Evalue si la reponse est fidele au contexte fourni.""" prompt = f"""Evalue si la reponse suivante est fidele au contexte fourni. CONTEXTE: {context} REPONSE: {answer} Analyse chaque affirmation de la reponse : 1. L'affirmation est-elle supportee par le contexte ? (supported/unsupported) 2. Y a-t-il des informations inventees ? (hallucination: yes/no) Score de fidelite (0.0 a 1.0) = affirmations supportees / total affirmations Reponds en JSON : {{"claims": [{{"text": "...", "supported": true/false}}], "faithfulness_score": 0.X, "hallucinations": ["..."]}}""" response = client.chat.completions.create( model="gpt-4o", messages=[{"role": "user", "content": prompt}], response_format={"type": "json_object"} ) return json.loads(response.choices[0].message.content) # Exemple result = evaluate_faithfulness( context="Ailog est une plateforme RAG-as-a-Service francaise, hebergee en France.", answer="Ailog est une plateforme RAG americaine hebergee aux Etats-Unis." ) # faithfulness_score: 0.0 (hallucination detectee)

Test de relevance

DEVELOPERpython
def evaluate_relevancy(question: str, answer: str) -> dict: """Evalue si la reponse est pertinente par rapport a la question.""" prompt = f"""Evalue la pertinence de cette reponse par rapport a la question. QUESTION: {question} REPONSE: {answer} Criteres : 1. La reponse repond-elle directement a la question ? (0-1) 2. La reponse contient-elle des informations hors-sujet ? (0-1) 3. La reponse est-elle complete ? (0-1) Score de pertinence (0.0 a 1.0) = moyenne des criteres. Reponds en JSON : {{"directness": 0.X, "focus": 0.X, "completeness": 0.X, "relevancy_score": 0.X, "explanation": "..."}}""" response = client.chat.completions.create( model="gpt-4o", messages=[{"role": "user", "content": prompt}], response_format={"type": "json_object"} ) return json.loads(response.choices[0].message.content)

Etape 4 : Evaluation End-to-End avec RAGAS

RAGAS (Retrieval Augmented Generation Assessment) est le framework de reference pour evaluer un pipeline RAG complet.

Installation et utilisation

DEVELOPERpython
from ragas import evaluate from ragas.metrics import ( faithfulness, answer_relevancy, context_precision, context_recall, answer_correctness ) from datasets import Dataset # Preparer les donnees data = { "question": [], "answer": [], "contexts": [], "ground_truth": [] } for item in golden_dataset: # Executer le pipeline RAG rag_result = rag_pipeline.query(item["question"]) data["question"].append(item["question"]) data["answer"].append(rag_result.answer) data["contexts"].append(rag_result.contexts) data["ground_truth"].append(item["expected_answer"]) dataset = Dataset.from_dict(data) # Evaluation RAGAS results = evaluate( dataset, metrics=[ faithfulness, answer_relevancy, context_precision, context_recall, answer_correctness ] ) print(results) # {'faithfulness': 0.87, 'answer_relevancy': 0.91, # 'context_precision': 0.78, 'context_recall': 0.85, # 'answer_correctness': 0.82}

Comparaison des frameworks d'evaluation

FrameworkMetriquesLLM-as-JudgeOpen-SourceFaciliteProduction-ready
RAGAS8+OuiOuiFacileOui
DeepEval14+OuiOuiMoyenOui
TruLens6+OuiOuiFacileOui
Phoenix (Arize)10+OuiOuiMoyenOui
LangSmithCustomOuiNonFacileOui
BraintrustCustomOuiNonFacileOui

RAGAS vs DeepEval : comparaison detaillee

CritereRAGASDeepEval
Metriques RAGExcellentesExcellentes
Metriques customLimiteesFlexibles
CI/CD integrationVia PythonPlugin pytest natif
DashboardNon (export JSON)Oui (Confident AI)
Cout LLM~$0.05/evaluation~$0.08/evaluation
CommunauteLargeCroissante

DeepEval : alternative solide

DEVELOPERpython
from deepeval import evaluate from deepeval.metrics import ( FaithfulnessMetric, AnswerRelevancyMetric, ContextualRelevancyMetric ) from deepeval.test_case import LLMTestCase # Creer les cas de test test_cases = [] for item in golden_dataset: rag_result = rag_pipeline.query(item["question"]) test_cases.append( LLMTestCase( input=item["question"], actual_output=rag_result.answer, expected_output=item["expected_answer"], retrieval_context=rag_result.contexts ) ) # Metriques faithfulness = FaithfulnessMetric(threshold=0.7) relevancy = AnswerRelevancyMetric(threshold=0.7) context = ContextualRelevancyMetric(threshold=0.7) # Evaluation results = evaluate(test_cases, [faithfulness, relevancy, context])

Etape 5 : A/B Testing en Production

Les metriques offline ne suffisent pas. Le vrai test, c'est la production.

Metriques de production

MetriqueSourceObjectif
Taux de clic sourceFrontend> 30%
Thumbs up/down ratioFrontend> 80% up
Taux d'escalade humainBackend< 20%
Temps de session moyenAnalyticsStable ou croissant
Taux de retourAnalytics> 40%
Requetes sans reponseBackend< 5%

Implementation A/B testing

DEVELOPERpython
import random import hashlib from datetime import datetime class RAGABTest: def __init__(self, variant_a, variant_b, traffic_split: float = 0.5): self.variant_a = variant_a # Pipeline RAG v1 self.variant_b = variant_b # Pipeline RAG v2 self.traffic_split = traffic_split self.results = {"a": [], "b": []} def get_variant(self, user_id: str) -> str: """Attribution deterministe basee sur le user_id.""" hash_val = int(hashlib.md5(user_id.encode()).hexdigest(), 16) return "a" if (hash_val % 100) < (self.traffic_split * 100) else "b" async def query(self, question: str, user_id: str) -> dict: variant = self.get_variant(user_id) pipeline = self.variant_a if variant == "a" else self.variant_b start_time = datetime.now() result = await pipeline.query(question) latency = (datetime.now() - start_time).total_seconds() # Log pour analyse self.results[variant].append({ "question": question, "answer": result.answer, "latency": latency, "variant": variant, "timestamp": datetime.now().isoformat() }) return { "answer": result.answer, "sources": result.sources, "variant": variant # Pour le tracking frontend } def get_stats(self) -> dict: """Statistiques comparatives.""" stats = {} for variant in ["a", "b"]: data = self.results[variant] if data: latencies = [d["latency"] for d in data] stats[variant] = { "count": len(data), "avg_latency": sum(latencies) / len(latencies), "p95_latency": sorted(latencies)[int(len(latencies) * 0.95)] } return stats

Duree minimale d'un A/B test

Volume de requetes/jourDuree minimaleEchantillon necessaire
< 1004 semaines~2800 requetes
100-10002 semaines~7000 requetes
1000-100001 semaine~7000 requetes
> 100003-5 jours~15000 requetes

Integration CI/CD

GitHub Actions pour RAG testing

DEVELOPERyaml
# .github/workflows/rag-tests.yml name: RAG Quality Tests on: push: branches: [develop, main] paths: - 'backend/rag/**' - 'backend/prompts/**' - 'tests/golden_dataset.json' jobs: rag-evaluation: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Python uses: actions/setup-python@v5 with: python-version: '3.11' - name: Install dependencies run: pip install ragas deepeval openai - name: Run retrieval tests run: python tests/test_retrieval.py env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} QDRANT_URL: ${{ secrets.QDRANT_TEST_URL }} - name: Run RAGAS evaluation run: python tests/test_ragas.py --threshold 0.80 - name: Upload results uses: actions/upload-artifact@v4 with: name: rag-evaluation-results path: tests/results/ - name: Check thresholds run: | python -c " import json with open('tests/results/ragas_scores.json') as f: scores = json.load(f) assert scores['faithfulness'] >= 0.80, f'Faithfulness too low: {scores[\"faithfulness\"]}' assert scores['answer_relevancy'] >= 0.75, f'Relevancy too low: {scores[\"answer_relevancy\"]}' assert scores['context_recall'] >= 0.80, f'Context recall too low: {scores[\"context_recall\"]}' print('All RAG quality thresholds passed') "

Alertes de regression

DEVELOPERpython
def check_regression(current_scores: dict, baseline_scores: dict, tolerance: float = 0.05) -> list[str]: """Detecte les regressions par rapport au baseline.""" alerts = [] for metric, current in current_scores.items(): baseline = baseline_scores.get(metric, 0) if current < baseline - tolerance: drop = baseline - current alerts.append( f"REGRESSION: {metric} dropped by {drop:.3f} " f"(baseline: {baseline:.3f}, current: {current:.3f})" ) return alerts

Les 7 pieges les plus courants

PiegeImpactSolution
Golden dataset trop petitFaux sentiment de qualiteMinimum 100 paires, categories equilibrees
Pas de test de regressionDegradation silencieuseCI/CD avec seuils automatiques
Evaluer uniquement la generationManque le retrieval defaillantTester retrieval ET generation
Utiliser le meme LLM pour evaluerBiais de confirmationUtiliser un LLM different pour le judge
Ignorer les edge casesEchecs en productionInclure 20% d'edge cases dans le golden
Metriques sans contexte businessOptimiser le mauvais objectifLier aux KPI business (satisfaction, escalade)
Test one-shotVariabilite ignoreeExecuter 3x, prendre la mediane

Notre approche chez Ailog

Chez Ailog, chaque pipeline RAG client est teste automatiquement :

  1. Golden dataset : genere semi-automatiquement a partir des documents du client, valide par notre equipe
  2. Tests retrieval : recall@10 > 0.85 obligatoire avant deploiement
  3. Tests RAGAS : faithfulness > 0.80, relevancy > 0.75
  4. Monitoring production : alertes automatiques si thumbs-down > 20%

Decouvrez comment nous evaluons nos systemes RAG et comment nous monitorons en production.

FAQ

L'evaluation avec LLM-as-judge (RAGAS ou DeepEval) coute environ $0.05-0.10 par paire de test avec GPT-4o. Pour un golden dataset de 200 paires, comptez $10-20 par run. En CI/CD avec 3 runs par semaine, le cout mensuel est d'environ $120-240. C'est negligeable par rapport au cout d'un RAG defaillant en production.
Oui, mais avec prudence. Les modeles open-source comme Qwen 3 235B ou DeepSeek-R1 peuvent servir de judges, mais ils sont generalement moins fiables que GPT-4o pour detecter les hallucinations subtiles. Notre recommandation : utilisez GPT-4o pour les evaluations critiques, et un modele open-source pour les tests de regression rapides.
Le golden dataset doit etre mis a jour a chaque modification majeure du corpus documentaire. En pratique, revoyez-le tous les mois pour les corpus dynamiques (FAQ, support) et tous les trimestres pour les corpus stables (documentation technique). Ajoutez systematiquement les requetes qui ont echoue en production.
Pour debuter, **RAGAS** est plus simple et plus rapide a mettre en place. Pour une integration CI/CD mature avec pytest, **DeepEval** est superieur grace a son plugin natif. Les deux sont open-source et de qualite equivalente. Chez Ailog, nous utilisons RAGAS pour les evaluations ponctuelles et DeepEval pour le CI/CD.
Les hallucinations se testent avec la metrique de faithfulness : chaque affirmation de la reponse est verifiee contre le contexte fourni. Pour aller plus loin, ajoutez des "trap questions" dans votre golden dataset : des questions auxquelles les documents ne peuvent PAS repondre. Le systeme doit repondre "je ne sais pas" plutot que d'inventer. Consultez notre guide sur la [detection d'hallucinations](/blog/guides/hallucination-detection). ---

Conclusion

Tester un systeme RAG n'est pas optionnel - c'est ce qui separe un prototype d'un produit. La methodologie en 5 etapes fonctionne quelle que soit l'echelle :

  1. Golden dataset : votre source de verite
  2. Tests retrieval : le composant le plus critique
  3. Tests generation : faithfulness et relevance
  4. RAGAS end-to-end : le bilan complet
  5. A/B testing : la validation finale

Commencez petit (50 paires, RAGAS basique), puis automatisez. L'investissement se rentabilise des le premier bug detecte avant la production.

Vous voulez un systeme RAG teste et monitore automatiquement ? Essayez Ailog - nos pipelines incluent l'evaluation automatique et le monitoring en temps reel.

Tags

RAGtestingevaluationRAGASqualiteCI/CDgolden dataset

Articles connexes

Ailog Assistant

Ici pour vous aider

Salut ! Pose-moi des questions sur Ailog et comment intégrer votre RAG dans vos projets !