Aller au contenu principal

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égieDescriptionCas d'utilisation
SimilaritéPure similarité vectorielleUsage général
MMRMaximum Marginal Relevance (diversité)Éviter la redondance
HybrideCombiner mot-clé + sémantiqueMeilleur 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​

PhaseIndicateurObjectif
IndexationDébit100+ docs/min
IndexationTaille des fragments500-1500 caractères
RécupérationLatence<100ms
RécupérationTop-K3-10 fragments
GénérationLatence<3s
GénérationAjustement du contexte<75% de la limite

Résumé​

PhaseQuandQuoiSortie
IndexationHors ligne (une fois)Traiter & stocker les documentsBase de données de vecteurs
RécupérationEn ligne (par requête)Trouver des fragments pertinentsDocuments de contexte
GénérationEn ligne (par requête)Créer une réponseRéponse finale

Points Clés​

  1. Trois phases distinctes avec des timings et des objectifs différents
  2. L'indexation est hors ligne - effectuée une fois ou périodiquement
  3. Récupération + Génération sont en ligne - se produisent par requête
  4. La taille des fragments est importante - affecte la qualité et le coût de la récupération
  5. Le pipeline est modulaire - chaque composant peut être optimisé séparément

Prochaines Étapes​

Testez vos connaissances avec le Quiz du Module 1 !