Chapitre 3 : Aperçu de l'Architecture RAG
Introduction
Un système RAG se compose de trois phases principales : Indexation, Récupération et Génération. Comprendre chaque phase est crucial pour construire des applications RAG efficaces.
Les Trois Phases
Phase 1 : Indexation (Hors ligne)
La phase d'indexation prépare vos documents pour une récupération efficace. Cela se passe avant toute requête utilisateur.
Étape 1 : Chargement des Documents
Chargez des documents à partir de diverses sources :
from langchain.document_loaders import (
PyPDFLoader,
TextLoader,
CSVLoader,
WebBaseLoader
)
# Charger un PDF
pdf_loader = PyPDFLoader("company_handbook.pdf")
pdf_docs = pdf_loader.load()
# Charger depuis le web
web_loader = WebBaseLoader("https://example.com/docs")
web_docs = web_loader.load()
# Combiner tous les documents
all_docs = pdf_docs + web_docs
Étape 2 : Division du Texte (Fragmentation)
Les documents sont divisés en fragments plus petits pour une meilleure récupération :
from langchain.text_splitter import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter(
chunk_size=1000, # Caractères par fragment
chunk_overlap=200, # Chevauchement entre les fragments
separators=["\n\n", "\n", ".", " "]
)
chunks = splitter.split_documents(all_docs)
# Résultat : Liste de fragments de texte plus petits
Pourquoi fragmenter ?
- Unités de texte plus petites = récupération plus précise
- Évite de récupérer du contenu non pertinent
- S'adapte aux fenêtres de contexte des LLM
Étape 3 : Génération d'Embeddings
Convertissez les fragments de texte en vecteurs numériques :
from langchain.embeddings import OpenAIEmbeddings
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
# Chaque fragment devient un vecteur
# "La politique de retour stipule..." → [0.023, -0.891, 0.156, ...]
Qu'est-ce que les embeddings ?
- Représentations vectorielles denses du texte
- Capturent le sens sémantique
- Les textes similaires ont des vecteurs similaires
- Typiquement 384-3072 dimensions
Étape 4 : Stockage des Vecteurs
Stockez les vecteurs dans une base de données spécialisée :
from langchain.vectorstores import Chroma
# Créer une base de données de vecteurs
vectorstore = Chroma.from_documents(
documents=chunks,
embedding=embeddings,
persist_directory="./chroma_db"
)
# Nous avons maintenant une base de connaissances indexée et consultable
Résumé du Pipeline d'Indexation
Documents Bruts
↓ (Charger)
Objets Document
↓ (Diviser)
Fragments de Texte
↓ (Embed)
Vecteurs
↓ (Stocker)
Base de Données de Vecteurs ✓
Phase 2 : Récupération (En ligne)
Lorsqu'un utilisateur pose une question, nous récupérons les fragments pertinents de notre base de connaissances indexée.
Étape 1 : Embedding de la Requête
La requête utilisateur est convertie dans le même espace vectoriel :
user_query = "Quelle est la politique de retour ?"
# Convertir en embedding en utilisant le même modèle
query_vector = embeddings.embed_query(user_query)
# Résultat : [0.034, -0.782, 0.234, ...]
Étape 2 : Recherche de Similarité
Trouvez les fragments avec des vecteurs similaires à la requête :
# Rechercher les 5 fragments les plus similaires
retriever = vectorstore.as_retriever(
search_type="similarity",
search_kwargs={"k": 5}
)
relevant_chunks = retriever.get_relevant_documents(user_query)
Comment fonctionne la similarité :
Vecteur de Requête : [0.034, -0.782, 0.234, ...]
Fragment 1 : [0.038, -0.791, 0.228, ...] → 0.98 similarité ✓
Fragment 2 : [0.890, 0.123, -0.456, ...] → 0.23 similarité ✗
Fragment 3 : [0.041, -0.768, 0.241, ...] → 0.95 similarité ✓
Stratégies de Récupération
| Stratégie | Description | Cas d'utilisation |
|---|---|---|
| Similarité | Pure similarité vectorielle | Usage général |
| MMR | Maximum Marginal Relevance (diversité) | Éviter la redondance |
| Hybride | Combiner mot-clé + sémantique | Meilleur rappel |
# MMR - équilibre pertinence et diversité
retriever_mmr = vectorstore.as_retriever(
search_type="mmr",
search_kwargs={
"k": 5,
"fetch_k": 20,
"lambda_mult": 0.7
}
)
Phase 3 : Génération (En ligne)
Combinez la requête utilisateur avec le contexte récupéré pour générer une réponse.
Étape 1 : Construction du Prompt
Construisez un prompt qui inclut le contexte :
def build_prompt(query, retrieved_chunks):
context = "\n\n".join([
chunk.page_content for chunk in retrieved_chunks
])
prompt = f"""Vous êtes un assistant utile. Utilisez le contexte suivant
pour répondre à la question. Si vous ne connaissez pas la réponse basée sur le
contexte, dites "Je n'ai pas assez d'informations."
Contexte :
{context}
Question : {query}
Réponse :"""
return prompt
Étape 2 : Génération LLM
Envoyez le prompt au LLM :
from langchain.chat_models import ChatOpenAI
llm = ChatOpenAI(model="gpt-4", temperature=0)
prompt = build_prompt(user_query, relevant_chunks)
response = llm.invoke(prompt)
print(response.content)
Étape 3 : Livraison de la Réponse
La réponse finale est ancrée dans les documents récupérés :
Utilisateur : "Quelle est la politique de retour ?"
Contexte Récupéré :
"Notre politique de retour permet des retours dans les 30 jours suivant l'achat.
Les articles doivent être non utilisés et dans leur emballage d'origine. Les remboursements sont
traités dans un délai de 5 à 7 jours ouvrables."
Réponse Générée :
"Selon la politique de l'entreprise, vous pouvez retourner les articles dans
les 30 jours suivant l'achat, à condition qu'ils soient non utilisés et dans leur
emballage d'origine. Les remboursements prennent généralement 5 à 7 jours ouvrables
pour être traités."
Code Complet du Pipeline RAG
from langchain.document_loaders import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import Chroma
from langchain.chat_models import ChatOpenAI
from langchain.chains import RetrievalQA
# Phase 1 : Indexation
loader = PyPDFLoader("documents/handbook.pdf")
documents = loader.load()
splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200
)
chunks = splitter.split_documents(documents)
embeddings = OpenAIEmbeddings()
vectorstore = Chroma.from_documents(chunks, embeddings)
# Phase 2 & 3 : Récupération + Génération
llm = ChatOpenAI(model="gpt-4")
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff",
retriever=vectorstore.as_retriever()
)
# Interroger le système
response = qa_chain.invoke("Quelle est la politique de retour ?")
print(response["result"])
Diagramme de l'Architecture
Indicateurs Clés
| Phase | Indicateur | Objectif |
|---|---|---|
| Indexation | Débit | 100+ docs/min |
| Indexation | Taille des fragments | 500-1500 caractères |
| Récupération | Latence | <100ms |
| Récupération | Top-K | 3-10 fragments |
| Génération | Latence | <3s |
| Génération | Ajustement du contexte | <75% de la limite |
Résumé
| Phase | Quand | Quoi | Sortie |
|---|---|---|---|
| Indexation | Hors ligne (une fois) | Traiter & stocker les documents | Base de données de vecteurs |
| Récupération | En ligne (par requête) | Trouver des fragments pertinents | Documents de contexte |
| Génération | En ligne (par requête) | Créer une réponse | Réponse finale |
Points Clés
- Trois phases distinctes avec des timings et des objectifs différents
- L'indexation est hors ligne - effectuée une fois ou périodiquement
- Récupération + Génération sont en ligne - se produisent par requête
- La taille des fragments est importante - affecte la qualité et le coût de la récupération
- Le pipeline est modulaire - chaque composant peut être optimisé séparément
Prochaines Étapes
Testez vos connaissances avec le Quiz du Module 1 !