LYCÉE → PRÉPA · L26

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

Définition (recherche d’information). Étant donné une requête q et un corpus de documents D, renvoyer les k documents les plus pertinents. Pertinence : jugement humain (binaire ou gradué) ; le système renvoie un classement.
Définition (RAG). Pipeline : (1) ingestion : documents → morceaux (chunks) → représentations (index) ; (2) recherche : q → k morceaux ; (3) génération : prompt = consigne + morceaux + question → réponse avec citations. Variante : RAG itératif (plusieurs recherches), agentique (le modèle décide quand chercher).
Définition (recherche lexicale, BM25). Score fondé sur les mots communs entre q et d, pondérés par leur rareté (IDF) et saturés en fréquence (TF). Index inversé : mot → liste des documents. Exact sur les mots, aveugle aux synonymes.
Définition (recherche dense, plongement). Un encodeur (transformer encodeur, L25) envoie textes et requêtes dans ℝd ; score = similarité cosinus. Capte le sens, rate les termes rares (références, codes). Bi-encodeur : q et d encodés séparément (rapide, indexable) ; cross-encodeur : (q, d) encodés ensemble (précis, lent : pour reclasser les k premiers).
Définition (recherche approximative, ANN). Trouver les plus proches voisins parmi N vecteurs en sous-linéaire : HNSW (graphe navigable hiérarchique), IVF (partition en cellules + recherche dans les plus proches), PQ (quantification produit). Compromis rappel / vitesse / mémoire.
Définition (hybride, RRF). Fusionner lexical et dense : Reciprocal Rank Fusion, score(d) = Σlistes 1/(60 + rang(d)).
Définition (métriques de recherche). Rappel@k : fraction des documents pertinents dans les k premiers. Précision@k. MRR : moyenne de 1/rang du premier pertinent. nDCG@k : gain cumulé actualisé normalisé (pertinences graduées, positions pondérées par 1/log₂(rang + 1)).
Définition (métriques de génération en RAG). Fidélité (faithfulness) : la réponse est-elle soutenue par le contexte fourni ? Pertinence de la réponse : répond-elle à la question ? Pertinence du contexte : les morceaux fournis étaient-ils utiles ? Mesurées par juges humains ou par LLM-juge (L32).

Fiche de cours · Formules

Formules à connaître

BM25(q, d) = Σt∈q IDF(t) · tf(t,d)·(k₁ + 1) / (tf(t,d) + k₁·(1 − b + b·|d|/avgdl)), IDF(t) = ln((N − nt + 0,5)/(nt + 0,5) + 1) k₁ ≈ 1,2–2 (saturation), b ≈ 0,75 (normalisation par longueur)
Cosinus : cos(u, v) = uᵀv/(‖u‖‖v‖) ; vecteurs normalisés ⇒ produit scalaire ; distance euclidienne² = 2 − 2cos
Entraînement d’un bi-encodeur (contrastif, InfoNCE) : L = −log [es(q,d⁺)/τ / (es(q,d⁺)/τ + Σd⁻ es(q,d⁻)/τ)] — négatifs « durs » (BM25 proches mais non pertinents) essentiels
Rappel@k = |pertinents ∩ top-k| / |pertinents| ; MRR = (1/|Q|) Σq 1/rangq ; DCG@k = Σi≤k (2reli − 1)/log₂(i + 1), nDCG = DCG/IDCG
RRF(d) = Σlistes ℓ 1/(k + rang(d)), k = 60
Coût d’un contexte : tokensprompt = consigne + k·taillechunk + question ; latence ≈ recherche (ms) + préremplissage (∝ tokensprompt) + génération (∝ tokensréponse)
Recherche exacte : O(N·d) par requête ; HNSW : O(log N) en pratique ; mémoire index ≈ N·d·4 octets (float32) — 10⁷ vecteurs de dimension 768 : 31 Go, 8 Go en int8, 1 Go en PQ
Découpage : taille de chunk c tokens, chevauchement o ; nombre de chunks ≈ (T − o)/(c − o) pour T tokens de corpus

Fiche de cours · Théorèmes et démonstrations

Démonstrations à savoir refaire

Théorème 1 (BM25 est une vraisemblance sous un modèle probabiliste). Sous le modèle binaire d’indépendance (les termes sont indépendants sachant la pertinence), le score optimal pour classer est Σt∈q∩d log [pt(1 − ut)/(ut(1 − pt))] où pt = P(t ∈ d | pertinent), ut = P(t ∈ d | non pertinent) ; avec pt ≈ ½ et ut ≈ nt/N on obtient l’IDF.
Classer par P(R | d) croissant équivaut à classer par le rapport de vraisemblance P(d | R)/P(d | R̄) (Bayes, les a priori étant communs). Par indépendance, P(d | R) = Πt∈d pt Πt∉d (1 − pt) et de même avec u. Le log du rapport, en ne gardant que les termes qui dépendent de d et sont dans q (les autres sont constants ou ignorés), donne Σt∈q∩d log[pt/ut · (1 − ut)/(1 − pt)]. Sans information sur la pertinence, pt = ½ annule le premier facteur ; ut = nt/N donne log((N − nt)/nt), l’IDF (avec les +0,5 de lissage). La partie TF de BM25 est une extension heuristique (modèle 2-Poisson de Robertson) : le poids d’un terme croît avec sa fréquence mais sature — un mot répété 20 fois ne vaut pas 20 fois plus.
Théorème 2 (la similarité cosinus des plongements normalisés est une recherche de plus proche voisin euclidien). Pour ‖u‖ = ‖v‖ = 1, ‖u − v‖² = 2 − 2uᵀv ; maximiser le cosinus = minimiser la distance euclidienne.
‖u − v‖² = ‖u‖² + ‖v‖² − 2uᵀv = 2 − 2uᵀv. Conséquence pratique : tous les index ANN (conçus pour la distance euclidienne ou le produit scalaire) s’appliquent aux plongements normalisés ; et une recherche exacte est un simple produit matrice-vecteur E·q, que NumPy fait à 10⁶ vecteurs × 768 en ~50 ms.
Théorème 3 (rappel@k borne la qualité du RAG). Si l’information nécessaire n’est pas dans les k morceaux fournis, la réponse correcte ne peut venir que de la mémoire du modèle (ou d’une hallucination). Donc : exactitude(RAG) ≤ rappel@k × P(réponse correcte | contexte présent) + (1 − rappel@k) × P(correcte sans contexte).
Décomposition par probabilités totales sur l’événement « le contexte contient la réponse ». Le second terme est la performance sans RAG (souvent faible sur des données privées) ; le premier est plafonné par le rappel. Conséquence méthodologique : mesurer la recherche seule (rappel@k sur un jeu de questions annotées) avant de toucher au modèle ; un RAG à 60 % de rappel ne dépassera pas ≈ 60–70 % d’exactitude quel que soit le LLM.
Théorème 4 (RRF est robuste aux échelles de scores). RRF ne dépend que des rangs, donc est invariant par toute transformation croissante des scores de chaque liste.
rang(d) est inchangé par une fonction strictement croissante appliquée aux scores de la liste ℓ. BM25 (scores de 0 à ~30) et cosinus (de −1 à 1) ne sont pas comparables ; les combiner linéairement exige une normalisation fragile ; RRF évite le problème. Le k = 60 amortit l’influence des premiers rangs (1/61 vs 1/62 : la différence entre le 1er et le 2e est faible, ce qui favorise les documents présents dans plusieurs listes).

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

DimensionQuestion poséeComment mesurerAutomatisable ?
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ésOui (LLM-juge, avec validation humaine sur un échantillon)
Pertinence de la réponseRé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
ExactitudeEst-elle vraie par rapport à une référence ?Correspondance exacte / F1 sur les tokens (QA extractive) ; juge pour le librePartiellement
CitationsLes [n] existent-ils et soutiennent-ils l’affirmation ?Vérification syntaxique (regex) + implication (juge)Oui
Refus correctSur une question hors corpus, le modèle refuse-t-il ?Jeu de questions « pièges » ; taux de refus attenduOui (phrase exacte)
Pertinence du contexteQuelle fraction des extraits fournis est utile ?Juge par extrait ; précision du contexteOui

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ômeCause probableDiagnosticRemède
« Je ne trouve pas » alors que l’info existeRappel : découpage, vocabulaire, indexRappel@k sur le jeu annoté ; inspecter les top-kHybride, reclassement, chunks plus grands ou par section, réécriture de requête (HyDE, multi-requêtes)
Réponse fausse mais confianteContexte non pertinent fourni, ou modèle qui ignore le contexteFidélité ; tester avec contexte contradictoire volontairePrompt plus strict, reclassement, modèle plus grand, citations obligatoires
Réponse partielleInfo répartie sur plusieurs morceaux, k trop petitRappel@k croît avec k ?k plus grand, fusion parent-enfant (chercher petit, fournir grand), RAG itératif
Réponse obsolèteIndex non rafraîchi ; doublons de versionsDate des sources dans les métadonnéesRé-indexation incrémentale, filtre par date, dédoublonnage
Lent / cherTrop de contexte, index exactProfil : recherche vs préremplissage vs générationANN, cache des plongements, k plus petit après reclassement, modèle plus petit
Fuite de donnéesPas de contrôle d’accès à la rechercheTester avec un utilisateur non autoriséFiltrer les morceaux par droits avant la génération
Injection de prompt via les documentsUn document contient des instructionsDocuments pièges dans le corpus de testSéparer clairement données et consignes, modèle robuste, filtrage

05 / Articles

Les articles à lire

ArticleContributionÀ retenir
Robertson & Zaragoza, The Probabilistic Relevance Framework: BM25 and Beyond, 2009Fondements probabilistes de BM25Toujours une baseline redoutable ; le lexical n’est pas mort
Karpukhin et al., Dense Passage Retrieval, EMNLP 2020 — 2004.04906Bi-encodeur BERT entraîné par contraste avec négatifs dursBat BM25 en QA ouverte ; l’entraînement contrastif est la clé
Lewis et al., Retrieval-Augmented Generation, NeurIPS 2020 — 2005.11401Le terme RAG ; recherche dense + générateur entraînés ensembleLe RAG moderne est « frozen » (sans entraînement conjoint) : plus simple, presque aussi bon
Khattab & Zaharia, ColBERT, SIGIR 2020 — 2004.12832Interaction tardive : un vecteur par token, MaxSimPrécision du cross-encodeur à un coût proche du bi-encodeur
Malkov & Yashunin, HNSW, 2016 — 1603.09320Graphe hiérarchique navigable pour ANNStandard de fait ; paramètres M, efConstruction, efSearch
Johnson et al., Billion-scale similarity search with GPUs (FAISS), 2017 — 1702.08734IVF + PQ à l’échelle du milliardCompression des vecteurs sans perte de rappel majeure
Gao et al., HyDE, 2022 — 2212.10496Générer une réponse hypothétique puis chercher avec son plongementRéécriture de requête : gain fort sans entraînement
Es et al., RAGAS, 2023 — 2309.15217Métriques automatiques : fidélité, pertinence réponse/contexteLLM-juge à valider par des humains (L32)
Liu et al., Lost in the Middle, 2023 — 2307.03172Les LLM utilisent mal l’information au milieu d’un long contexteMettre les extraits les plus pertinents au début et à la fin ; k modéré
Asai et al., Self-RAG, 2023 — 2310.11511Le modèle décide quand chercher et critique ses réponsesVers le RAG agentique
Gao et al., Retrieval-Augmented Generation for LLMs: A Survey, 2023 — 2312.10997Panorama : naïf, avancé, modulaireCarte du domaine pour choisir ses briques

TP guidé

TP — Un RAG complet sur vos documents, mesuré (6 h)

Exercices

Exercices auto-corrigés

Exercice 1 — nDCG avec pertinences graduées

Implémentez ndcg(classement, pertinence, k)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.0

Exercice 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 morceaux

Exercices

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)

Exercice 1. Un corpus de 10 000 pages (≈ 5 M tokens) est découpé en morceaux de 400 tokens avec chevauchement 50. Nombre de morceaux ? Taille de l’index dense (768 dims, float32) ? Temps d’une recherche exacte en NumPy (≈ 10⁹ multiplications-additions/s) ? Faut-il un index ANN ?
Correction. Morceaux ≈ (5·10⁶ − 50)/(400 − 50) ≈ 14 300. Index : 14 300 × 768 × 4 octets = 44 Mo. Recherche exacte : 14 300 × 768 ≈ 1,1·10⁷ opérations ≈ 11 ms. Un index ANN n’apporte rien ici (il coûterait du rappel pour gagner 10 ms) ; l’ANN devient nécessaire vers 10⁶ vecteurs (1 s par requête en exact). Règle : commencer exact ; passer en ANN quand la latence de recherche dépasse celle de la génération.
Exercice 2. Le RAG répond souvent « je ne trouve pas » sur des questions dont la réponse est dans une table d’un PDF. Pourquoi, et que faire ?
Correction. Causes : l’extraction texte casse les tables (cellules mélangées, en-têtes séparés des valeurs) ; le découpage sépare la ligne de son en-tête ; le plongement d’une ligne de chiffres est peu informatif ; la question en langage naturel (« consommation du moteur ») ne partage aucun mot avec la cellule (« 3 A »). Remèdes : extraction structurée des tables (pdfplumber, Camelot, ou un modèle de layout, L29) et conversion en texte explicite (« Courant maximal par moteur : 3 A ») ; un morceau par ligne avec l’en-tête et le titre de la table ; métadonnées ; recherche hybride (le lexical trouve « moteur ») ; pour les questions numériques, un outil (SQL sur la table extraite) plutôt que la génération.
Exercice 3. Deux équipes comparent leurs RAG : A affiche 78 % de « réponses correctes » selon un LLM-juge, B 71 % selon des annotateurs humains. Peut-on conclure ?
Correction. Non : les juges diffèrent (un LLM-juge peut être plus indulgent, ou biaisé vers les réponses longues et les formulations proches des siennes), les jeux de questions diffèrent, et sans intervalles de confiance ni taille d’échantillon on ne sait rien (L24). Protocole valable : même jeu de 200+ questions, mêmes extraits de référence, mêmes deux juges (humain sur un sous-ensemble pour calibrer le LLM-juge : mesurer l’accord, kappa de Cohen), rapporter fidélité, pertinence et exactitude séparément avec IC, plus le rappel@k de la recherche pour expliquer les écarts.

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

MotDéfinition
ChunkMorceau de document indexé ; sa taille et ses frontières conditionnent le rappel.
BM25Score lexical probabiliste : IDF × TF saturée × normalisation de longueur.
Bi-encodeur / cross-encodeurPlongements séparés (indexable) / paire encodée ensemble (reclassement précis).
ANN, HNSWRecherche approximative des plus proches voisins ; graphe navigable hiérarchique.
RRFFusion de classements par somme de 1/(60 + rang).
Rappel@k, MRR, nDCGMétriques de recherche ; le rappel plafonne le RAG.
FidélitéRéponse soutenue par le contexte ; mesurée par juge.
Injection via documentsInstructions 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.

← L25SommaireL27 : LLM →