Le RAG, en partant de zéro

Faire répondre un modèle sur tes documents, sans le réentraîner.

Prérequis

Ce cours suppose que tu sais ce qu'est un token, un embedding et une fenêtre de contexte. Si ces mots ne te disent rien, commence par comment fonctionne un LLM, puis reviens ici.

Sommaire du cours
  1. 0Le problème qu'on résout
  2. 1Le pipeline complet
  3. 2Les réglages qui comptent
  4. 3Les limites, honnêtement
  5. 4Pour aller plus loin

Chapitre 0Le problème qu'on essaie de résoudre

Imagine la situation. Tu as 300 PDF : des procédures internes, de la documentation technique, des comptes-rendus. Tu voudrais poser une question en français, du genre « quelle est la procédure si le capteur X sature ? », et obtenir une réponse exacte tirée de ces documents, avec la page d'où elle vient.

Tu ouvres ChatGPT. Premier problème : il ne connaît pas tes documents, il a appris sur du texte public jusqu'à une certaine date. Second problème, plus vicieux : il ne te dit pas qu'il ne sait pas. Un modèle de langage est entraîné à produire une suite de mots plausible, pas à vérifier. Face à une question dont il ignore la réponse, il en fabrique une qui ressemble à une bonne réponse. C'est ce qu'on appelle une hallucination.

Comment donner ses propres documents à un modèle qui ne les a jamais vus ?

Il existe trois réponses possibles. Les deux premières ne conviennent pas ici, et voir où elles échouent aide à comprendre la troisième.

Option 1 : le réentraîner sur tes documents

C'est le fine-tuning. On prolonge l'entraînement du modèle avec tes données. Ça semble logique, et c'est presque toujours le mauvais outil ici :

Pourquoi ça ne marche pas bien Le fine-tuning apprend surtout un style, un format, un ton, plutôt que des faits précis. Un modèle « fine-tuné » sur tes procédures écrira comme tes procédures, sans pour autant en restituer les chiffres exacts. Il coûte cher et prend des heures. Il faut tout recommencer à chaque document modifié. Et surtout : il ne peut pas citer sa source, puisque l'information est diluée dans des milliards de poids.

Option 2 : tout coller dans le prompt

Puisque le modèle lit ce qu'on lui écrit, pourquoi ne pas lui coller les 300 PDF avant la question ? Parce qu'il existe une fenêtre de contexte : la quantité de texte qu'un modèle peut lire d'un coup. Elle est limitée, et elle est facturée. 300 PDF n'y rentrent pas. Et même si c'était le cas, tu paierais l'intégralité de ta documentation à chaque question, pour une précision qui baisse à mesure qu'on noie l'information utile dans le bruit.

Option 3 : ne lui donner que les bons passages, le RAG

L'idée est simple une fois posée : on ne donne pas tout, on donne juste ce qu'il faut. Avant de poser la question au modèle, on va chercher automatiquement les 3 à 5 passages pertinents dans les 300 PDF, et on les colle dans le prompt avec la question. Le modèle n'a plus qu'à rédiger à partir de ce qu'il a sous les yeux.

L'analogie qui marche

C'est un examen à livre ouvert.
Le fine-tuning, c'est apprendre le livre par cœur (long, imprécis, à refaire à chaque édition).
Tout coller dans le prompt, c'est relire la bibliothèque entière à chaque question.
Le RAG, c'est ouvrir le livre à la bonne page, puis répondre avec.

D'où le nom, RAG, pour Retrieval-Augmented Generation :

  • Retrieval · retrouver les passages pertinents parmi tous tes documents.
  • Augmented · augmenter le prompt : on y ajoute ces passages.
  • Generation · générer : le modèle rédige la réponse à partir d'eux.
Attention à ça

Le modèle ne fouille pas ta base de documents. Il n'y a aucun accès. C'est ton code qui cherche, trouve les passages, et les écrit dans le prompt. Le modèle, lui, ne voit qu'un texte un peu plus long que d'habitude. Tout le travail intéressant du RAG est avant l'appel au modèle.

Ce que ça change en pratique : la réponse devient vérifiable, puisqu'on sait de quel document et de quelle page elle vient. Mettre à jour la connaissance prend quelques secondes, le temps de réindexer un fichier. Et le modèle reste générique, donc remplaçable par un autre.

Le RAG en une image : deux temps bien distincts

Une seule fois · préparer
Tes documents
Découpésen passages
Transformésen nombres
Rangésdans une base
À chaque question · répondre
Question
On chercheles passages proches
On assemblele prompt
Le modèle rédige
Toute la suite du cours consiste à remplir ces cases, une par une.

Reste la partie difficile : comment une machine fait-elle pour trouver les bons passages ? Chercher les mots de la question ne suffit pas. Si le document dit « le capteur est en saturation » et que tu demandes « que faire quand X déborde ? », aucun mot ne correspond. Il faut chercher par le sens. C'est le rôle des embeddings, expliqués dans le cours sur le fonctionnement d'un LLM.

Chapitre 1Le pipeline complet

Tu as maintenant les deux briques. Assemblons la machine. Elle fonctionne en deux temps qu'il ne faut jamais confondre : une préparation faite une seule fois, et une boucle rejouée à chaque question.

Temps 1 · l'indexation, une seule fois
  • Extraire le texte. Un modèle d'embedding ne lit que du texte : on sort le contenu des PDF, des .docx, des pages web, et on jette la mise en forme.
  • Découper en chunks. On coupe en morceaux de 200 à 500 tokens. Deux raisons : un document entier dépasse la fenêtre du modèle d'embedding (souvent 512 tokens), et un seul vecteur pour 50 pages serait beaucoup trop vague pour retrouver une information précise.
  • Prévoir un chevauchement. On répète quelques phrases de la fin d'un chunk au début du suivant, pour qu'une idée tombant pile sur une frontière reste entière quelque part.
  • Vectoriser chaque chunk. Chaque morceau passe dans le modèle d'embedding et ressort sous forme d'un vecteur, son adresse dans l'espace du sens.
  • Ranger le tout. Dans une base vectorielle, on stocke trois choses par chunk : le vecteur (pour chercher), le texte original (pour répondre), et les métadonnées de fichier et de page (pour citer).

Le découpage et son chevauchement

le texte extrait, en entier chunk 1 chunk 2 chunk 3 en ambre : les zones répétées pour ne jamais couper une idée en deux
Un réglage typique : chunks de 300 tokens, chevauchement de 15 %.
La règle à retenir

On cherche sur les vecteurs, on répond avec le texte. Le vecteur sert à trouver, jamais à rédiger. C'est pour ça qu'on stocke les deux.

Du passage au vecteur unique

Pour comparer des textes entiers, on ne veut pas un vecteur par mot : on veut un seul vecteur pour un paragraphe entier. Le modèle d'embedding lit tout le passage, contextualise chaque mot, puis fait la moyenne. Il en sort un seul point qui résume l'ensemble.

Détail qui surprend toujours

La taille de ce vecteur est fixée par le modèle (384, 768, 1024, 3072 nombres), jamais par la longueur du texte. Une phrase de 5 mots et un paragraphe de 300 mots donnent tous deux un vecteur de la même taille.

À retenir dès maintenant

Il existe deux familles de modèles bien distinctes : les modèles d'embedding, petits et rapides, qui transforment du texte en vecteurs pour le comparer, et les LLM, bien plus gros, qui rédigent. Même socle technique, deux métiers, et on ne les confond jamais.

Les deux tokenizers d'un RAG

La clé

Un RAG manipule deux tokenizers différents. C'est le piège qui surprend tout le monde.

Deux voies, deux tokenizers

Indexation
Documents
Tokenizerembedding
Vecteurs → index
Génération
Question + chunks
TokenizerLLM
Réponse
L'un découpe pour la recherche, l'autre budgète la génération.
À faire concrètement

① Chunker par tokens, pas par caractères → respecter la fenêtre du modèle d'embedding.

② Compter les tokens (tiktoken) → ne pas dépasser le contexte du LLM.

③ Se souvenir de la taxe : un corpus français remplit les chunks plus vite, donc on en case moins.

Pour ton RAG

C'est l'embedding contextuel qui compte, lui seul sait distinguer les sens. Il est calculé une fois par chunk à l'indexation, puis rangé dans ta base.

Temps 2 · la requête, à chaque question
  • Vectoriser la question. Avec exactement le même modèle d'embedding que les chunks. Sinon les vecteurs vivent dans deux espaces différents et ne sont pas comparables.
  • Chercher les plus proches. On demande à la base les k chunks dont le vecteur est le plus proche de celui de la question (k vaut typiquement 3 à 10).
  • Assembler le prompt. Trois blocs : une consigne (« réponds en t'appuyant sur le contexte ci-dessous »), le texte des k chunks, puis la question.
  • Générer. Le LLM lit ce prompt et rédige. Il a la matière sous les yeux, donc il invente beaucoup moins.
  • Afficher les sources. Grâce aux métadonnées gardées à l'étape 5, on indique de quels documents et de quelles pages vient la réponse.

« Proche », ça se mesure comment ?

Par l'angle entre deux vecteurs, ce qu'on appelle la similarité cosinus. L'intuition : dans cet espace, c'est la direction qui porte le sens, pas la longueur de la flèche. Deux textes qui pointent dans la même direction parlent de la même chose.

Petit angle = même sujet

la question chunk A · 0,95 chunk B · 0,2 1 · identique 0 · sans rapport −1 · opposé
Score entre 0 et 1 en pratique. On garde les plus élevés. Au-delà de 0,8, c'est en général pertinent.
Bonne nouvelle

Tu n'implémentes rien de tout ça. La base vectorielle fait le calcul et l'optimisation : tu choisis la métrique (cosinus) et le k, tu appelles search(vecteur, k), tu reçois les passages triés.

→ Le détail complet (formule du cosinus, exemple chiffré, force brute vs HNSW, late chunking) est dans la fiche de cette page, onglet « Fiches ».

Chapitre 2Les réglages qui comptent

Un RAG qui répond mal, c'est presque toujours l'un de ces six points.

taille de chunk200 à 500 tokens. Trop petit : le passage perd son contexte. Trop grand : le vecteur devient vague et retrouve mal.
chevauchement10 à 15 % de la taille du chunk. Assez pour ne pas couper une idée, pas assez pour dupliquer toute la base.
k3 à 10 passages. Trop peu : il manque l'info. Trop : on noie le modèle et on paie des tokens pour rien.
seuil de scoreIgnorer les chunks sous ~0,8 de cosinus, plutôt que d'envoyer du hors-sujet au LLM « parce qu'il fallait bien k résultats ».
même modèleLe même modèle d'embedding pour les documents et pour les questions, sans exception.
compter en tokensDécouper par tokens, pas par caractères, et se souvenir qu'un texte français en consomme environ 1,5× plus.

Ce qui se croise avec le reste

Le payoff

Tu stockes tes chunks en vecteurs. Pour une question : tu la transformes en vecteur, tu récupères les vecteurs les plus proches.

Trois rappels qui se croisent

Deux modèles, pas un : le modèle d'embedding (chercher) ≠ le LLM (répondre).

Fenêtre du modèle d'embedding souvent 512 tokens → d'où le chunking par tokens.

Taxe linguistique : un chunk français remplit la fenêtre plus vite → moins de mots par vecteur.

Les vecteurs vivent dans une base vectorielle. Trouver les plus proches parmi des millions se fait par recherche approximative (ANN).

Chapitre 3Les limites, honnêtement

Le RAG n'est pas magique. Savoir où il casse évite de perdre des semaines.

L'extraction est le maillon faible Un PDF en deux colonnes, un tableau, un scan : le texte extrait sort mélangé, et tout le reste du pipeline hérite de cette bouillie. La plus grosse perte de qualité vient de là, bien avant le choix du modèle.
Le contexte se perd aux frontières Chaque chunk est encodé seul : si un « il » renvoie au paragraphe précédent, le vecteur l'ignore. Le chevauchement atténue, sans résoudre.
Les questions globales échouent « Combien de documents parlent de X ? », « fais-moi la synthèse de tout » : le RAG ramène k passages, il ne lit jamais l'ensemble. Ce n'est pas l'outil pour ça.
Une mauvaise question trouve mal Une question vague donne un vecteur vague, donc des voisins au hasard. D'où les techniques de reformulation automatique de la question.
La recherche est approximative Au-delà de quelques centaines de milliers de vecteurs, on n'explore plus tout : l'index sacrifie une chance infime d'exactitude contre beaucoup de vitesse.
Et surtout

Le RAG réduit les hallucinations, il ne les supprime pas. Si les passages fournis sont hors sujet, le modèle rédigera quand même quelque chose. D'où l'importance d'afficher les sources : c'est ce qui rend l'erreur visible.

Chapitre 4Pour aller plus loin

Une fois le RAG de base en place, voici l'ordre dans lequel il vaut le coup de l'améliorer.

  • La recherche hybride. Combiner la recherche par sens (vecteurs) et la recherche par mots exacts (BM25). Indispensable dès qu'il y a des références, des codes produits, des noms propres, que les vecteurs gèrent mal.
  • Le re-ranking. Récupérer 30 candidats, puis les faire trier finement par un petit modèle spécialisé qui lit la question et le passage ensemble. C'est souvent le meilleur gain pour l'effort investi.
  • Un meilleur découpage. Le late chunking (encoder le document entier avant de découper) ou le contextual retrieval (préfixer chaque chunk d'un résumé du document) récupèrent le contexte perdu aux frontières.
  • L'évaluation. Se constituer une trentaine de questions dont on connaît la réponse et la source attendue, puis mesurer. Sans ça, on optimise à l'aveugle.
La fin du cours

Tu as maintenant le modèle mental complet : découper → vectoriser → ranger → retrouver par proximité → coller dans le prompt → rédiger → citer.

Bascule sur l'onglet Fiches pour la version condensée, avec les détails techniques, les formules et les schémas de référence.

Le pipeline RAG

De tes documents bruts à une réponse sourcée. Étape par étape.

Plan de la fiche
  1. §1Les sources
  2. §2Extraction du texte
  3. §3Découpage en chunks
  4. §4Embedding de chaque chunk
  5. §5Stockage : la base vectorielle
  6. §6La question et son embedding
  7. §7Recherche par similarité (cosinus)
  8. §8Augmentation : le prompt
  9. §9Génération
  10. §10La réponse, sourcée

Vue d'ensemble : deux phases

Phase 1 · indexation, une seule fois
Documents
Extraction
Chunking
Embedding
Base vect.
↑ la base est lue par la recherche ci-dessous ↓
Phase 2 · requête, à chaque question
Question
Embedding
Recherche
Prompt
LLM
Réponse
On range la connaissance une fois, on l'interroge à chaque question.
Le modèle mental, en une fois

Le LLM ne fouille pas ta base. C'est toi qui cherches, puis tu lui passes le texte trouvé dans le prompt. Il n'a aucun accès à ta base.

L'essentiel en 8 points

  1. Deux temps : indexer une fois, interroger à chaque question.
  2. Le LLM ne cherche pas. Tu cherches, tu lui donnes le texte.
  3. Deux modèles : embedding (chercher) + LLM (répondre).
  4. Le même modèle d'embedding pour les chunks ET la question.
  5. On cherche sur les vecteurs, on répond avec le texte.
  6. Chunking par tokens + overlap.
  7. k = nombre de chunks récupérés · « proche » = similarité cosinus.
  8. Les sources viennent des métadonnées stockées avec chaque chunk.
Partie I · Indexation

§1Les sources

PDF, Word (.docx), pages web, code source… la connaissance que tu veux rendre interrogeable. À ce stade, rien n'est fait : ce sont des fichiers sur un disque.

§2Extraction du texte

La clé

Un modèle d'embedding ne lit que du texte. On jette la mise en forme (polices, colonnes, images), on garde le contenu.

document.pdf
rapport.docx
script.py
texte brut
Sans cette étape, impossible de vectoriser quoi que ce soit.

§3Découpage en chunks

Pourquoi découper : 2 raisons

① Un document entier dépasse la fenêtre du modèle d'embedding (souvent 512 tokens).
② Un seul vecteur pour 50 pages serait trop vague pour retrouver une info précise.

Taille typique : 200 à 500 tokens. On découpe par tokens, pas par caractères.

Chunks + chevauchement (overlap)

texte extrait (long) découpé par tokens chunk 1 chunk 2 chunk 3 zones ambre = chevauchement on répète quelques phrases pour ne pas couper une idée
Si une info tombe pile sur une frontière, l'overlap garantit qu'elle est entière dans au moins un chunk.

§4Embedding de chaque chunk

La clé

Le vecteur du chunk, c'est son adresse dans l'espace du sens. Deux chunks au sens proche = vecteurs proches, même s'ils n'emploient pas les mêmes mots.

Le mécanisme interne

Le chunktexte
tokeniser1 vecteur / token
poolingla moyenne
[0.07, 0.5, …]1 vecteur · taille fixe
En pratique : tu passes le texte, tu reçois un tableau de nombres.
Point crucial

L'attention ne relie que les tokens du chunk lui-même. Chaque chunk est encodé isolément : le vecteur du chunk 2 ne sait rien du chunk 1 ni du 3.

L'attention ne traverse pas les frontières

chunk 1• • •
chunk 2• • •
chunk 3• • •
↓ chacun produit son vecteur, seul ↓
vecteur 1
vecteur 2
vecteur 3
✕ = l'attention ne passe pas d'un chunk à l'autre.
Points forts Rapide : chunks encodés en parallèle, et tu peux réencoder un seul document sans toucher aux autres. Précis : un chunk = une idée ciblée = recherche nette.
Limite Contexte perdu aux frontières : si un « il » renvoie au chunk précédent, le vecteur ne le sait pas. L'overlap atténue, sans résoudre.
Les variantes : late chunking, contextual retrieval, sentence-window

Late chunking : on inverse l'ordre, on embed tout le document d'abord (chaque token voit ses voisins), et on ne découpe les vecteurs qu'ensuite, juste avant le pooling. Chaque vecteur de chunk a donc « vu » ses voisins. Coût : un modèle à long contexte et plus de calcul.

tout le doc
encodage completattention globale
découpe + poolingAPRÈS

Contextual retrieval : coller un court résumé du document en tête de chaque chunk avant de l'encoder.

Sentence-window : encoder de petits chunks, mais renvoyer au LLM le chunk plus ses voisins au moment de répondre.

Le chunking isolé reste le défaut : plus simple, et un vecteur trop chargé de contexte retrouve moins nettement.

§5Stockage : la base vectorielle

3 choses par chunk

① le vecteur (la clé de recherche) · ② le texte original (c'est lui qu'on enverra au LLM) · ③ les métadonnées (source, page → pour citer).

Ce que contient la base

IDVecteur (clé)Texte du chunkSource
1[0.07, 0.5, …]« La cuisson basse… »guide.pdf · p.3
2[−0.2, 0.1, …]« Préchauffer le four… »guide.pdf · p.4
3[0.4, −0.3, …]« def cuire(temp): … »script.py
Ambre = on cherche là-dessus · vert = c'est ça qu'on envoie au LLM.
À ne pas confondre

Le vecteur sert à trouver. Le texte sert à répondre. Toute cette phase (§1→§5) est faite une seule fois.

Partie II · Requête

§6La question et son embedding

Règle absolue

La question est encodée par exactement le même modèle d'embedding que les chunks. Sinon les vecteurs vivent dans des espaces différents et ne sont pas comparables.

question (texte)
le MÊME modèle d'embedding
[0.05, 0.48, …]

§7Recherche par similarité

D'abord : un vecteur, c'est UN point

Le tableau donne les coordonnées d'un seul point. Chaque case = un axe. 768 nombres = 1 point dans un espace à 768 axes, et non 768 points.

Le tableau = les coordonnées d'un point

tableau 2 1 axe x axe y = x y 2 1 O (0,0) (2, 1) ancré à l'origine : direction + longueur
On ne peut pas dessiner 768D, mais l'angle entre deux flèches se calcule quand même.
La similarité cosinus

Le sens est porté par la direction. Donc pour comparer deux vecteurs, on mesure l'angle entre eux.

Petit angle = sens proche

origine question chunk A · cos ≈ 0,95 chunk B · cos ≈ 0,2 1 · identique 0 · sans rapport −1 · opposé cos
En pratique pour des embeddings : entre 0 et 1. On garde les cos les plus hauts.
La formule

cos(θ) = (A·B) / (‖A‖ × ‖B‖)

Le produit scalaire divisé par les longueurs, ce qui annule l'effet de taille : seule la direction compte.

Si les vecteurs sont normalisés (longueur 1) : cosinus = simple produit scalaire. D'où sa rapidité.

Le calcul pas à pas + un exemple chiffré

1. Produit scalaire : A·B = a₁b₁ + a₂b₂ + … + aₙbₙ (multiplier terme à terme, additionner).

2. Longueur (Pythagore) : ‖A‖ = √(a₁² + a₂² + … + aₙ²).

3. Diviser. (Si on veut l'angle : θ = arccos(cos θ), mais pour la recherche on s'arrête au cosinus, qui est le score.)

Exemple. A = [2, 1], B = [1, 3]
A·B = 2×1 + 1×3 = 5
‖A‖ = √5 ≈ 2,24 · ‖B‖ = √10 ≈ 3,16
cos θ = 5 / (2,24 × 3,16) ≈ 0,71 → environ 45°.

0,71 c'est « assez proche ». En pratique on retient surtout les scores > ~0,8.

Mémo cercle trigo : lire un cosinus

Le cosinus d'un angle = la coordonnée horizontale du point sur le cercle. Plus l'angle est petit, plus on est à droite, plus le cosinus est proche de 1.

cos 0 π/2 π θ cos θ un vecteur angle ↑ (0→π/2→π) ⇒ cosinus ↓ (1→0→−1)

Pour le RAG : on vise cos ≈ 1 → angle ≈ 0 → même sens.

Comment trouver les k plus proches ?

Force brute = calculer le cosinus avec chaque vecteur, trier, garder le top-k. Exact, mais N calculs par question.
ANN (approximatif) = un index ne compare qu'une petite fraction. Chance infime de rater le tout-à-fait-plus-proche.

Force brute vs HNSW

Force brute (exact) question compare aux N vecteurs lent si N est grand ANN · HNSW départ question saute de proche en proche visite une poignée de nœuds
HNSW relie chaque vecteur à ses voisins, puis « marche » vers la question.
En pratique

C'est la base vectorielle qui fait tout. Tu choisis la métrique (cosinus) et le k, tu appelles search(vecteur, k), tu reçois les k chunks triés. Tu n'implémentes ni le cosinus ni HNSW.

§8Augmentation : le prompt

C'est le « augmented » de RAG

Un prompt en 3 parties : consigne + contexte (le texte des k chunks) + question.

Assemblage du prompt

1 · Consigne« appuie-toi sur le contexte »
2 · Contextele texte des k chunks
3 · Questioncelle de l'utilisateur
PROMPT ENVOYÉ AU LLM
Le LLM reçoit les passages utiles dans son prompt. On lui apporte l'information.

§9Génération

Promptcontexte + question
LLMgénération
Réponse+ sources
LLM de génération ≠ modèle d'embedding : chacun son rôle, son tokenizer, sa fenêtre.
Pourquoi ça marche

La réponse est fondée sur des passages précis et identifiés → moins d'hallucinations. Le modèle a la matière sous les yeux au lieu de répondre de mémoire.

§10La réponse, sourcée

Grâce aux métadonnées gardées au stockage (§5), on affiche les sources : quels documents, quelles pages ont servi. La réponse devient vérifiable.

La boucle

Seules les étapes §6 → §10 sont rejouées à chaque question.
L'indexation (§1 → §5) ne se refait que si tes documents changent.

Récap fiche 01

  1. Deux temps : indexer une fois · interroger à chaque question.
  2. Le LLM ne fouille pas la base. Tu cherches, tu lui passes le texte.
  3. Deux modèles distincts : embedding (chercher) + LLM (répondre).
  4. Le même modèle d'embedding pour les chunks ET la question.
  5. On cherche sur les vecteurs, on répond avec le texte.
  6. Chunking par tokens + overlap.
  7. k chunks récupérés · proximité = similarité cosinus (viser > 0,8).
  8. Métadonnées → sources → réponse vérifiable.