← Retour
Module 03

RAG — Retrieval-Augmented Generation

Donner de la memoire aux LLMs. Embeddings, vector stores, chunking, similarity search — pour que ton agent ait toujours les bonnes infos.

01

Le probleme : les LLMs ne savent pas tout

Au Module 1, on a vu que le LLM a une connaissance fige — il ne connait que ce qu'il a vu pendant son entrainement. Il ne connait pas les tarifs Selectra d'aujourd'hui, ni les documents internes de ta boite, ni le contenu d'un PDF que tu viens de recevoir.

Concept cle

Le probleme des connaissances

Les LLMs ont 3 limites majeures : 1) leurs connaissances ont une date de coupure, 2)ils n'ont pas acces a tes donnees privees, 3) ils hallucinent quand ils ne savent pas. Le RAG resout les trois.

Analogie

Imagine un expert qui repond a tes questions les yeux fermes, de memoire. Parfois il a raison, parfois il invente. Le RAG, c'est lui ouvrir les yeux et lui donner un dossier de documents pertinents AVANT qu'il reponde. Il peut maintenant citer ses sources.

RAG = Retrieval-Augmented Generation. En francais : generation augmentee par la recuperation. Le principe est simple :

Pipeline RAG


  Question de l'utilisateur
           |
  [ 1. Retrieval ]
  Chercher les documents pertinents
  dans une base de connaissances
           |
  [ 2. Augmentation ]
  Injecter ces documents dans le
  prompt du LLM comme contexte
           |
  [ 3. Generation ]
  Le LLM repond en se basant sur
  les documents fournis
           |
  Reponse sourcee et fiable
ApprochePrincipeQuand l'utiliser
Sans RAGLe LLM repond de memoireQuestions de culture generale, code, raisonnement
RAGOn donne des documents au LLM avant qu'il repondeDonnees privees, info a jour, besoin de sources
Fine-tuningOn re-entraine le modele sur tes donneesChanger le style/comportement du modele, pas pour des faits
Long contextOn met tout dans le prompt (si ca rentre)Petit corpus (<100 pages), pas besoin de systeme complexe

Attention

RAG vs Fine-tuning :c'est la confusion la plus courante. Le fine-tuning change le comportement du modele (ton, style, format). Le RAG lui donne des faits. Pour que ton agent Selectra connaisse les tarifs actuels, c'est du RAG qu'il faut — pas du fine-tuning.

02

Les embeddings — la base du RAG

Au Module 1, on a brievement vu les embeddings. Ici on va plus loin car c'est le moteur du RAG.

Concept cle

Embedding = position dans un espace semantique

Un embedding convertit un texte (mot, phrase, paragraphe) en un vecteur de nombres. Deux textes qui parlent du meme sujet auront des vecteurs proches, meme s'ils utilisent des mots differents.

exemple
embed("Comment reduire ma facture d'electricite ?")
→ [0.23, -0.41, 0.87, 0.12, ...]  (1536 dimensions)

embed("Je paye trop cher mon courant, que faire ?")
→ [0.21, -0.39, 0.85, 0.14, ...]  (tres proche ! meme sens)

embed("Quelle est la capitale de la France ?")
→ [0.78, 0.33, -0.21, 0.67, ...]  (tres loin — sujet different)

distance("facture", "courant") = 0.05  → tres similaire
distance("facture", "capitale") = 1.42 → tres different

La entre deux embeddings mesure la similarite semantique. C'est ca qui permet de trouver "les documents qui parlent du meme sujet que la question".

typescript — generer des embeddings
import { embed, embedMany } from "ai";

// Embedder un seul texte
const { embedding } = await embed({
  model: "openai/text-embedding-3-small",
  value: "Comment changer de fournisseur d'electricite ?",
});
// embedding = number[] (1536 dimensions)

// Embedder plusieurs textes d'un coup (plus efficace)
const { embeddings } = await embedMany({
  model: "openai/text-embedding-3-small",
  values: [
    "Guide pour changer de fournisseur",
    "Tarifs electricite 2026",
    "Comment lire son compteur Linky",
  ],
});
// embeddings = number[][] (3 vecteurs)
Modele d'embeddingDimensionsUsage
text-embedding-3-small (OpenAI)1536Bon rapport qualite/prix, usage general
text-embedding-3-large (OpenAI)3072Plus precis, plus cher
voyage-3 (Voyage AI)1024Excellent pour le francais
Mistral Embed1024Francais natif, bon marche
python — embeddings et similarite avec numpy
import numpy as np
from sklearn.metrics.pairwise import cosine_similarity

# Simulons des embeddings (en vrai, ils viennent d'un modele)
# Ici on utilise sentence-transformers (pip install sentence-transformers)
from sentence_transformers import SentenceTransformer

model = SentenceTransformer("all-MiniLM-L6-v2")  # leger, 384 dims

phrases = [
    "Comment reduire ma facture d'electricite ?",
    "Je paye trop cher mon courant, que faire ?",
    "Quelle est la capitale de la France ?",
]
embeddings = model.encode(phrases)  # → np.array shape (3, 384)

# Matrice de similarite
sim_matrix = cosine_similarity(embeddings)
print(f"facture ↔ courant  : {sim_matrix[0][1]:.3f}")  # → 0.82 (tres proche !)
print(f"facture ↔ capitale : {sim_matrix[0][2]:.3f}")  # → 0.13 (rien a voir)

# Trouver le document le plus proche d'une question
question = model.encode(["Je veux payer moins cher"])
scores = cosine_similarity(question, embeddings)[0]
best = np.argmax(scores)
print(f"Meilleur match : '{phrases[best]}' (score: {scores[best]:.3f})")

Quiz

Pourquoi les embeddings sont-ils meilleurs que la recherche par mots-cles pour le RAG ?

03

Vector stores — la memoire du RAG

OK, on sait convertir du texte en vecteurs. Mais ou les stocker, et comment chercher dedans rapidement ?

Concept cle

Vector store

Un vector store (ou base de donnees vectorielle) stocke des embeddings et permet de faire des recherches de similarite en millisecondes, meme sur des millions de vecteurs.

1

Indexation (une seule fois)

Tu prends tes documents, tu les decoupes en chunks, tu calcules l'embedding de chaque chunk, et tu les stockes dans le vector store avec les metadonnees (source, page, date...).

2

Recherche (a chaque requete)

La question de l'utilisateur est embedee, puis le vector store trouve les K chunks les plus proches (Approximate Nearest Neighbors — ANN).

3

Generation

Les chunks recuperes sont injectes dans le prompt du LLM, qui genere sa reponse en se basant dessus.

Architecture RAG complete


   INDEXATION (offline, une fois)
   ─────────────────────────────
   Documents PDF/HTML/TXT
          |
   [ Chunking ]  → morceaux de ~500 tokens
          |
   [ Embedding model ]  → vecteurs
          |
   [ Vector Store ]  → stockage indexe


   REQUETE (a chaque question)
   ──────────────────────────
   "Comment changer de fournisseur ?"
          |
   [ Embedding model ]  → vecteur de la question
          |
   [ Vector Store: top-K search ]
          |
   3-5 chunks pertinents retrouves
          |
   [ Prompt = system + chunks + question ]
          |
   [ LLM ]
          |
   "Pour changer de fournisseur, voici les etapes..."
   (avec citations des sources)
Vector storeTypeAvantageUsage
PineconeCloud (managed)Simple, scalable, pas de maintenanceProduction, SaaS
ChromaLocal / embeddeGratuit, tourne en local, parfait pour prototyperDev, petits projets
WeaviateCloud ou self-hostedHybrid search (vecteurs + mots-cles)Production avancee
pgvectorExtension PostgreSQLPas de nouvelle DB a gerer si tu as deja PostgresSi tu utilises deja Neon/Supabase
En memoire (array)Juste du JSZero dependance, quelques lignes de codePrototypage rapide, <1000 docs

Attention

Pour ton stage :commence avec un vector store en memoire (simple array + cosine similarity) pour prototyper. Passe a Chroma ou pgvector quand ca marche. Ne commence pas par Pinecone — c'est overkill pour une demo.

04

Le chunking — decouper intelligemment

Tu ne peux pas embedder un PDF de 200 pages d'un coup — le modele d'embedding a une limite de tokens, et un vecteur de 200 pages serait trop generique pour etre utile. Il faut decouper.

Concept cle

Le chunking

Decouper tes documents en morceaux (chunks) de taille optimale. Trop petit = manque de contexte. Trop gros = trop generique, le vector store ne trouvera pas le bon passage.

StrategiePrincipeTaille typiqueQuand l'utiliser
Fixed-sizeCouper tous les N tokens200-500 tokensSimple, rapide, bon par defaut
Par paragrapheCouper aux sauts de ligne doublesVariableDocuments bien structures (articles, docs)
RecursiveCouper en hierarchie (section → paragraphe → phrase)200-1000 tokensLe plus polyvalent, recommande
SemantiqueCouper quand le sujet change (via embeddings)VariableDocuments longs sans structure claire
chunking recursif — concept
Document original (2000 tokens):
"## Changer de fournisseur
Pour changer de fournisseur d'electricite, il suffit de souscrire
une nouvelle offre. L'ancien fournisseur sera resilie automatiquement...
[500 tokens]

## Les tarifs reglementes
Le tarif reglemente de vente (TRV) est fixe par les pouvoirs publics...
[600 tokens]

## Le compteur Linky
Le compteur Linky permet de relever votre consommation a distance...
[900 tokens]"

Apres chunking recursif (max 500 tokens, overlap 50):
→ Chunk 1: "## Changer de fournisseur
Pour changer..." (480 tokens)
→ Chunk 2: "## Les tarifs reglementes
Le tarif..." (520 tokens → re-decoupe)
  → Chunk 2a: "## Les tarifs reglementes
Le tarif..." (300 tokens)
  → Chunk 2b: "...fixe par les pouvoirs publics..." (270 tokens)
→ Chunk 3: "## Le compteur Linky
Le compteur..." (450 tokens)
→ Chunk 4: "...relever votre consommation a distance..." (500 tokens)
python — chunking recursif avec LangChain
from langchain.text_splitter import RecursiveCharacterTextSplitter

splitter = RecursiveCharacterTextSplitter(
    chunk_size=500,       # max 500 caracteres par chunk
    chunk_overlap=50,     # 50 caracteres de chevauchement
    separators=["\n## ", "\n\n", "\n", ". ", " "],  # priorite de coupure
)

texte = """## Changer de fournisseur
Pour changer de fournisseur d'electricite, il suffit de souscrire
une nouvelle offre. La resiliation est automatique et gratuite.

## Les tarifs reglementes
Le tarif reglemente de vente (TRV) est fixe par les pouvoirs publics.
Il change deux fois par an, en fevrier et en aout."""

chunks = splitter.split_text(texte)
for i, chunk in enumerate(chunks):
    print(f"Chunk {i} ({len(chunk)} chars): {chunk[:60]}...")

Concept cle

L'overlap (chevauchement)

On fait chevaucher les chunks de 10-20% pour ne pas couper une phrase en deux. Si un chunk finit a "Le tarif est de" et le suivant commence a "15 centimes", on a perdu l'information. L'overlap evite ca.

Quiz

Pour une FAQ Selectra avec des questions-reponses courtes, quelle strategie de chunking ?

06

Pipeline RAG complet en code

Assemblons tout en un pipeline fonctionnel. Voici un RAG complet sur une FAQ Selectra.

typescript — pipeline RAG complet
import { embed, embedMany, generateText } from "ai";

// ── 1. Donnees (en prod, ca viendrait d'une DB ou de fichiers) ──

const faq = [
  {
    question: "Comment changer de fournisseur d'electricite ?",
    answer: "Il suffit de souscrire une offre chez un nouveau fournisseur. La resiliation de l'ancien contrat est automatique et gratuite. Le changement prend environ 3 semaines. Vous n'aurez aucune coupure d'electricite pendant la transition.",
  },
  {
    question: "Qu'est-ce que le numero PDL ?",
    answer: "Le PDL (Point de Livraison) est un numero a 14 chiffres qui identifie votre compteur electrique. Vous le trouvez sur votre facture d'electricite, sur votre compteur Linky, ou en appelant Enedis au 09 72 67 50 XX (XX = votre departement).",
  },
  {
    question: "Quels documents faut-il pour souscrire ?",
    answer: "Pour souscrire un contrat d'electricite, vous avez besoin de : votre adresse exacte, votre numero PDL (ou PCE pour le gaz), un RIB pour le prelevement, et une estimation de votre consommation annuelle en kWh.",
  },
  // ... 200 autres Q/R
];

// ── 2. Indexation (une seule fois au demarrage) ──

const texts = faq.map(f => f.question + " " + f.answer);
const { embeddings } = await embedMany({
  model: "openai/text-embedding-3-small",
  values: texts,
});

const index = faq.map((f, i) => ({
  text: f.question + "\n" + f.answer,
  embedding: embeddings[i],
}));

// ── 3. Recherche (a chaque question utilisateur) ──

const userQuestion = "J'ai besoin de quoi pour ouvrir un contrat ?";

const { embedding: queryVec } = await embed({
  model: "openai/text-embedding-3-small",
  value: userQuestion,
});

// Trouver les 3 Q/R les plus pertinentes
const results = index
  .map(item => ({ ...item, score: cosineSimilarity(queryVec, item.embedding) }))
  .sort((a, b) => b.score - a.score)
  .slice(0, 3);

// ── 4. Generation avec contexte ──

const response = await generateText({
  model: "anthropic/claude-sonnet-4.6",
  system: "Tu es l'assistant Selectra. Reponds en te basant UNIQUEMENT sur les documents fournis. Si l'info n'est pas dans les documents, dis-le.",
  prompt: `Documents pertinents :
---
${results.map(r => r.text).join("\n---\n")}
---

Question du client : ${userQuestion}

Reponds en citant les sources.`
});

Attention

Le system prompt RAG est crucial : "Reponds UNIQUEMENT sur les documents fournis" empeche le modele d'halluciner. Sans ca, il completera avec ses connaissances (potentiellement fausses ou perimees). Pour Selectra, tu veux des tarifs d'aujourd'hui, pas ceux de l'entrainement.

07

Application Selectra : RAG pour l'agent vocal

Comment le RAG s'integre dans l'agent vocal Selectra ?

Agent vocal + RAG


  Client : "C'est quoi les tarifs chez TotalEnergies ?"
                    |
         [ Agent vocal (LLM) ]
                    |
         Besoin d'info factuelle → RAG
                    |
     [ Vector search: "tarifs TotalEnergies" ]
                    |
     Documents retrouves :
     - "TotalEnergies Essentielle : 0.19€/kWh, abo 12€/mois"
     - "TotalEnergies Verte : 0.21€/kWh, abo 13€/mois"
                    |
         [ LLM + contexte RAG ]
                    |
  Agent : "TotalEnergies propose deux offres principales :
           l'offre Essentielle a 0.19€ le kilowatt-heure
           et l'offre Verte a 0.21€. Voulez-vous que je
           compare avec d'autres fournisseurs ?"

En pratique, l'agent vocal Selectra utiliserait le RAG pour :

Cas d'usage RAGBase de connaissancesPourquoi RAG et pas de memoire
Tarifs des offresBase Selectra mise a jour quotidiennementLes prix changent tous les mois — le LLM ne peut pas les connaitre
FAQ client200+ questions/reponses SelectraReponses precises et validees juridiquement
Procedures internesScripts de qualification, regles metierL'agent doit suivre la procedure exacte, pas improviser
Info compteurGuide Linky, numeros Enedis par departementInfos techniques precises (numeros de tel, etapes)

Concept cle

RAG en temps reel pour le vocal

En vocal, la latence compte. Le RAG ajoute ~200-500ms (embedding de la question + recherche vectorielle). C'est acceptable — le client ne remarque pas un silence de 500ms entre sa question et la reponse. L'agent peut meme dire "Un instant, je verifie..." pendant la recherche.

08

Quand NE PAS utiliser le RAG

SituationRAG ?Alternative
Petit corpus (<50 pages)NonMet tout dans le context window directement. Plus simple, plus fiable.
Le LLM connait deja la reponseNonPour du code Python, de la culture generale, du raisonnement pur → pas besoin de RAG.
Tu veux changer le style du modeleNonFine-tuning ou system prompt elabore. Le RAG donne des faits, pas un style.
Donnees privees + mise a jour frequenteOuiC'est LE cas d'usage ideal du RAG.

Attention

L'erreur classique :mettre du RAG partout. Si ta FAQ fait 20 questions, tu n'as pas besoin d'un vector store — mets-la dans le system prompt. Le RAG ajoute de la complexite (indexation, maintenance de la base, latence). Utilise-le quand c'est justifie.

09

Quiz final

Quiz

Quel est l'ordre correct du pipeline RAG ?

Quiz

Pour l'agent Selectra, pourquoi utiliser le RAG pour les tarifs plutot que de les mettre dans le system prompt ?

Quiz

Un chunk de 2000 tokens avec des sujets melanges, c'est un probleme. Pourquoi ?

Module 03 termine

Tu sais maintenant comment fonctionne le RAG de bout en bout : embeddings, chunking, vector stores, similarity search, et comment l'integrer dans un agent vocal. Prochain module : Tool Calling & Agents — quand le LLM agit dans le monde reel.