Module L26 · Partie K · Ingénierie de l’IA
RAG : donner au modèle vos documents, et prouver qu’il s’en sert.
Un LLM ne connaît que son corpus d’entraînement, figé, et invente quand il ne sait pas. La génération augmentée par la recherche (Retrieval-Augmented Generation) recherche d’abord les passages pertinents dans vos documents, puis les fournit au modèle qui répond en les citant. Ce chapitre construit chaque brique — découpage, plongements, index, recherche hybride, reclassement, génération, citations — en code exécutable, avec les mathématiques de la recherche d’information (BM25, similarité cosinus, ANN) et les métriques qui disent si ça marche.
Durée : 4 séances · Prérequis : L08 (bases de données), L13, L15, L25. Objectifs : implémenter un RAG complet ; comprendre BM25 et les plongements denses ; construire un index approximatif (HNSW, idée) ; évaluer recherche et génération séparément ; connaître les modes d’échec et les articles.
Ce que vous saurez faire à la fin
- Découper un corpus, l’indexer (lexical + dense), rechercher, reclasser, générer avec citations.
- Calculer rappel@k, MRR, nDCG pour la recherche ; fidélité et pertinence pour la réponse.
- Diagnostiquer un RAG qui échoue : découpage, rappel, contexte, modèle.
- Lire RAG (Lewis 2020), DPR, ColBERT, HNSW, RAGAS.
Fiche de cours · Définitions
Définitions
Fiche de cours · Formules
Formules à connaître
Fiche de cours · Théorèmes et démonstrations
Démonstrations à savoir refaire
01 / Construire
Ingestion : découper intelligemment, puis indexer lexicalement (BM25 depuis zéro)
Le découpage est le premier levier : trop petit, on perd le contexte (« le rapport cyclique » de quoi ?) ; trop grand, on dilue et on paie des tokens. Chevaucher évite de couper une information en deux. En pratique : 200–500 tokens par morceau, chevauchement 10–20 %, et respecter les frontières (titres, paragraphes, tableaux).
01 / Construire
Plongements denses (jouet mais fidèle), recherche hybride, reclassement
Pipeline standard en production : BM25 + dense → RRF → top-20 → cross-encodeur → top-5 → LLM. Le lexical garantit les termes exacts (références, noms), le dense les reformulations, le reclassement la précision finale.
01 / Construire
Génération avec citations, et le prompt qui contraint le modèle
Trois règles du prompt : périmètre (uniquement les extraits), refus explicite (phrase exacte, testable), citations (vérifiables automatiquement : chaque [n] doit pointer vers un extrait qui contient l’affirmation). Sur PC, le même prompt alimente un modèle local (llama.cpp, Ollama) ou une API.
02 / Mesurer
Évaluer la recherche : rappel@k, MRR, nDCG — sur un jeu de questions annotées
02 / Mesurer
Évaluer la génération : fidélité, pertinence, refus — et les vérifier automatiquement
| Dimension | Question posée | Comment mesurer | Automatisable ? |
|---|---|---|---|
| Fidélité (faithfulness) | Chaque affirmation est-elle soutenue par le contexte ? | Décomposer la réponse en affirmations ; pour chacune, un juge (humain ou LLM) vérifie l’implication par les extraits cités | Oui (LLM-juge, avec validation humaine sur un échantillon) |
| Pertinence de la réponse | Répond-elle à la question posée ? | Juge ; ou similarité entre la question et des questions régénérées à partir de la réponse (RAGAS) | Oui |
| Exactitude | Est-elle vraie par rapport à une référence ? | Correspondance exacte / F1 sur les tokens (QA extractive) ; juge pour le libre | Partiellement |
| Citations | Les [n] existent-ils et soutiennent-ils l’affirmation ? | Vérification syntaxique (regex) + implication (juge) | Oui |
| Refus correct | Sur une question hors corpus, le modèle refuse-t-il ? | Jeu de questions « pièges » ; taux de refus attendu | Oui (phrase exacte) |
| Pertinence du contexte | Quelle fraction des extraits fournis est utile ? | Juge par extrait ; précision du contexte | Oui |
03 / Passer à l’échelle
Index approximatif : l’idée de HNSW et le compromis rappel/vitesse, mesuré
En production : FAISS (Meta), hnswlib, ou une base vectorielle (pgvector, Qdrant, Milvus). Retenez le compromis : rappel de l’index ANN × rappel du modèle de plongement = rappel total ; mesurez les deux.
04 / Diagnostiquer
Les modes d’échec d’un RAG et leur remède
| Symptôme | Cause probable | Diagnostic | Remède |
|---|---|---|---|
| « Je ne trouve pas » alors que l’info existe | Rappel : découpage, vocabulaire, index | Rappel@k sur le jeu annoté ; inspecter les top-k | Hybride, reclassement, chunks plus grands ou par section, réécriture de requête (HyDE, multi-requêtes) |
| Réponse fausse mais confiante | Contexte non pertinent fourni, ou modèle qui ignore le contexte | Fidélité ; tester avec contexte contradictoire volontaire | Prompt plus strict, reclassement, modèle plus grand, citations obligatoires |
| Réponse partielle | Info répartie sur plusieurs morceaux, k trop petit | Rappel@k croît avec k ? | k plus grand, fusion parent-enfant (chercher petit, fournir grand), RAG itératif |
| Réponse obsolète | Index non rafraîchi ; doublons de versions | Date des sources dans les métadonnées | Ré-indexation incrémentale, filtre par date, dédoublonnage |
| Lent / cher | Trop de contexte, index exact | Profil : recherche vs préremplissage vs génération | ANN, cache des plongements, k plus petit après reclassement, modèle plus petit |
| Fuite de données | Pas de contrôle d’accès à la recherche | Tester avec un utilisateur non autorisé | Filtrer les morceaux par droits avant la génération |
| Injection de prompt via les documents | Un document contient des instructions | Documents pièges dans le corpus de test | Séparer clairement données et consignes, modèle robuste, filtrage |
05 / Articles
Les articles à lire
| Article | Contribution | À retenir |
|---|---|---|
| Robertson & Zaragoza, The Probabilistic Relevance Framework: BM25 and Beyond, 2009 | Fondements probabilistes de BM25 | Toujours une baseline redoutable ; le lexical n’est pas mort |
| Karpukhin et al., Dense Passage Retrieval, EMNLP 2020 — 2004.04906 | Bi-encodeur BERT entraîné par contraste avec négatifs durs | Bat BM25 en QA ouverte ; l’entraînement contrastif est la clé |
| Lewis et al., Retrieval-Augmented Generation, NeurIPS 2020 — 2005.11401 | Le terme RAG ; recherche dense + générateur entraînés ensemble | Le RAG moderne est « frozen » (sans entraînement conjoint) : plus simple, presque aussi bon |
| Khattab & Zaharia, ColBERT, SIGIR 2020 — 2004.12832 | Interaction tardive : un vecteur par token, MaxSim | Précision du cross-encodeur à un coût proche du bi-encodeur |
| Malkov & Yashunin, HNSW, 2016 — 1603.09320 | Graphe hiérarchique navigable pour ANN | Standard de fait ; paramètres M, efConstruction, efSearch |
| Johnson et al., Billion-scale similarity search with GPUs (FAISS), 2017 — 1702.08734 | IVF + PQ à l’échelle du milliard | Compression des vecteurs sans perte de rappel majeure |
| Gao et al., HyDE, 2022 — 2212.10496 | Générer une réponse hypothétique puis chercher avec son plongement | Réécriture de requête : gain fort sans entraînement |
| Es et al., RAGAS, 2023 — 2309.15217 | Métriques automatiques : fidélité, pertinence réponse/contexte | LLM-juge à valider par des humains (L32) |
| Liu et al., Lost in the Middle, 2023 — 2307.03172 | Les LLM utilisent mal l’information au milieu d’un long contexte | Mettre les extraits les plus pertinents au début et à la fin ; k modéré |
| Asai et al., Self-RAG, 2023 — 2310.11511 | Le modèle décide quand chercher et critique ses réponses | Vers le RAG agentique |
| Gao et al., Retrieval-Augmented Generation for LLMs: A Survey, 2023 — 2312.10997 | Panorama : naïf, avancé, modulaire | Carte du domaine pour choisir ses briques |
TP guidé
TP — Un RAG complet sur vos documents, mesuré (6 h)
- Installer.
pip install sentence-transformers rank-bm25 faiss-cpu pypdf; un LLM local via Ollama (ollama pull llama3.1:8b) ou une API. - Corpus. 20–50 documents que vous connaissez (cours, manuels, notes). Extraction texte (pypdf), découpage par sections puis par ~300 tokens avec chevauchement ; métadonnées (source, page, date).
- Jeu d’évaluation. 50 questions avec, pour chacune, les morceaux pertinents (ids) et une réponse de référence ; 10 questions hors corpus. C’est l’étape la plus longue et la plus importante.
- Recherche. BM25 (rank-bm25), dense (all-MiniLM-L6-v2 puis bge-m3), hybride RRF, reclassement (cross-encoder/ms-marco-MiniLM). Tableau rappel@1/3/5, MRR, nDCG pour chaque configuration. Un graphique rappel@k en fonction de k.
- Génération. Prompt avec citations et refus ; 3 modèles (8B local, 70B ou API). Fidélité et pertinence par LLM-juge sur les 50 questions, validées à la main sur 15 (accord juge/humain ?). Taux de refus correct sur les 10 pièges.
- Ablations. Taille des chunks (150/300/600), k (2/5/10), avec/sans reclassement, position des extraits. Un tableau : configuration → rappel, fidélité, latence, coût en tokens.
- Livrable. Dépôt (ingestion, index, évaluation reproductible), rapport avec les tableaux, et une page « les 5 échecs les plus instructifs et ce que j’ai changé ».
Exercices
Exercices auto-corrigés
Exercice 1 — nDCG avec pertinences graduées
Implémentez ndcg(classement, pertinence, k) où pertinence est un dict id → gain ∈ {0, 1, 2, 3} (gain 2rel − 1), et vérifiez les propriétés : classement idéal → 1 ; inverser deux documents de gains différents baisse le score ; un document non pertinent en tête coûte plus qu’en position 10.
Correction
def ndcg(classement, pertinence, k):
dcg = sum((2 ** pertinence.get(d, 0) - 1) / np.log2(r + 2) for r, d in enumerate(classement[:k]))
ideal = sorted(pertinence.values(), reverse=True)[:k]
idcg = sum((2 ** g - 1) / np.log2(r + 2) for r, g in enumerate(ideal))
return dcg / idcg if idcg > 0 else 0.0Exercice 2 — Découpage qui respecte les phrases
Écrivez decouper_phrases(texte, max_mots, chevauchement_phrases) : regroupe des phrases entières (jamais coupées) jusqu’à ~max_mots, avec un chevauchement d’un nombre donné de phrases entre morceaux consécutifs. Aucune phrase ne doit être perdue.
Correction
def decouper_phrases(texte, max_mots=30, chevauchement_phrases=1):
phrases = re.split(r"(?<=[.!?])\s+", texte.strip()); morceaux = []; i = 0
while i < len(phrases):
j, n = i, 0
while j < len(phrases) and (n + len(phrases[j].split()) <= max_mots or j == i): n += len(phrases[j].split()); j += 1
morceaux.append(" ".join(phrases[i:j]))
if j >= len(phrases): break
i = max(i + 1, j - chevauchement_phrases)
return morceauxExercices
Exercices auto-corrigés (suite)
Exercice 3 — Fusion RRF et sa propriété
Implémentez rrf_fusion(listes, k=60) et vérifiez : invariance par transformation monotone des scores (on ne vous donne que des rangs), et qu’un document présent dans deux listes en position 3 dépasse un document présent en position 1 dans une seule.
Correction
def rrf_fusion(listes, k=60):
from collections import Counter
sc = Counter()
for l in listes:
for r, d in enumerate(l): sc[d] += 1 / (k + r + 1)
return [d for d, _ in sc.most_common()]Exercice 4 — Borne du Théorème 3
On mesure rappel@5 = 0,7 ; exactitude du modèle quand le contexte contient la réponse : 0,9 ; sans contexte : 0,2. Complétez exactitude_attendue et rappel_necessaire(cible) (rappel minimal pour atteindre une exactitude cible, ou None si impossible même avec rappel 1).
Correction
def exactitude_attendue(rappel, p_avec, p_sans): return rappel * p_avec + (1 - rappel) * p_sans
def rappel_necessaire(cible, p_avec, p_sans):
if cible > p_avec: return None
return (cible - p_sans) / (p_avec - p_sans)Fiche de cours · Exercices corrigés
Exercices corrigés (rédaction)
Vérification
Votre RAG donne 55 % de bonnes réponses. Le rappel@5 de la recherche est de 60 %. Quel est le levier prioritaire ?
Deux questions supplémentaires
1. Pourquoi combiner BM25 et dense ? Le lexical trouve les termes exacts (codes, noms), le dense les reformulations ; RRF fusionne sans normaliser les scores.
2. Qu’est-ce que la fidélité ? La proportion des affirmations de la réponse soutenues par les extraits fournis — indépendamment de leur vérité absolue.
Référence
Les mots à retenir
| Mot | Définition |
|---|---|
| Chunk | Morceau de document indexé ; sa taille et ses frontières conditionnent le rappel. |
| BM25 | Score lexical probabiliste : IDF × TF saturée × normalisation de longueur. |
| Bi-encodeur / cross-encodeur | Plongements séparés (indexable) / paire encodée ensemble (reclassement précis). |
| ANN, HNSW | Recherche approximative des plus proches voisins ; graphe navigable hiérarchique. |
| RRF | Fusion de classements par somme de 1/(60 + rang). |
| Rappel@k, MRR, nDCG | Métriques de recherche ; le rappel plafonne le RAG. |
| Fidélité | Réponse soutenue par le contexte ; mesurée par juge. |
| Injection via documents | Instructions cachées dans le corpus ; séparer données et consignes. |
Suite
Vous savez alimenter un modèle. Il reste à comprendre le modèle lui-même.
Le chapitre suivant ouvre les LLM : pré-entraînement, alignement (SFT, RLHF, DPO), tokenisation, décodage, capacités et limites, coûts — et comment les faire tourner chez vous.