01
📖 Introduction
Ce tutoriel vous guide dans la création d'un système RAG (Retrieval-Augmented Generation) complet. Nous allons construire ensemble un assistant capable de répondre à des questions sur un corpus de documents en utilisant la recherche vectorielle et un LLM.
💡 Prérequis :
- Python 3.9 ou supérieur
- Connaissances de base en Python
- Une clé API OpenAI (ou alternative locale)
📊 Architecture RAG :
[Documents] → [Chunking] → [Embeddings] → [Vector Store]
[Question] → [Embedding] → [Search] → [Contexte] → [LLM] → [Réponse]
03
🔢 Étape 2 : Génération des embeddings
from sentence_transformers import SentenceTransformer
# Modèle d'embedding (multilingue)
model = SentenceTransformer('paraphrase-multilingual-MiniLM-L12-v2')
# Générer les embeddings pour tous les chunks
texts = [chunk.page_content for chunk in chunks]
embeddings = model.encode(texts, show_progress_bar=True)
print(f"Shape des embeddings : {embeddings.shape}") # (nb_chunks, 384)
04
🗄️ Étape 3 : Création de la base vectorielle
Avec FAISS
import faiss
import numpy as np
# Créer l'index FAISS
dimension = 384
index = faiss.IndexFlatIP(dimension)
# Normaliser les embeddings pour similarité cosinus
faiss.normalize_L2(embeddings)
index.add(embeddings)
# Sauvegarder l'index
faiss.write_index(index, "faiss_index.bin")
Avec Chroma (plus simple)
from langchain.vectorstores import Chroma
vectorstore = Chroma.from_documents(
documents=chunks,
embedding=embeddings,
persist_directory="./chroma_db"
)
# Persistance automatique
vectorstore.persist()
💡 Choix de la base vectorielle :
- FAISS : Ultra-rapide, pour production (recommandé)
- Chroma : Simple, pour prototypage
- Pinecone : Cloud managé
- Qdrant : Open source, performances élevées
05
🤖 Étape 4 : Configuration du LLM
Avec OpenAI
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(
model="gpt-4",
temperature=0.2, # Faible pour des réponses précises
api_key="votre_clé_api"
)
Avec un modèle local (Mistral via Ollama)
from langchain_community.llms import Ollama
llm = Ollama(
model="mistral",
temperature=0.2,
base_url="http://localhost:11434"
)
06
🔗 Étape 5 : Construction de la chaîne RAG
from langchain.chains import RetrievalQA
from langchain.prompts import PromptTemplate
# Template de prompt
template = """Tu es un assistant expert. Utilise le contexte suivant pour répondre à la question. Si la réponse n'est pas dans le contexte, dis que tu ne sais pas.
Contexte: {context}
Question: {question}
Réponse:"""
prompt = PromptTemplate(
template=template,
input_variables=["context", "question"]
)
# Créer le retriever
retriever = vectorstore.as_retriever(
search_type="similarity",
search_kwargs={"k": 4}
)
# Chaîne RAG complète
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff",
retriever=retriever,
chain_type_kwargs={"prompt": prompt},
return_source_documents=True
)
📝 Test de la chaîne :
response = qa_chain.invoke({"query": "Quels sont les discours de Charles de Gaulle sur la résistance ?"})
print(f"Réponse: {response['result']}")
print(f"Sources: {[doc.metadata for doc in response['source_documents']]}")
07
🧪 Étape 6 : Test et optimisation
Métriques d'évaluation
def evaluate_rag(chain, questions, ground_truth):
results = []
for q, gt in zip(questions, ground_truth):
response = chain.invoke({"query": q})
results.append({
"question": q,
"expected": gt,
"actual": response["result"],
"sources": len(response["source_documents"])
})
return results
Optimisations possibles
- ✅ Chunk size : Ajuster pour capturer plus de contexte
- ✅ k (nombre de chunks) : 4-10 chunks pour un bon équilibre
- ✅ Re-ranking : Utiliser un second modèle pour réordonner les résultats
- ✅ Hybrid search : Combiner vectoriel + mots-clés (BM25)
- ✅ Prompt engineering : Instructions plus précises
💡 Astuce : Utilisez LangSmith ou LangFuse pour tracer et debugger vos chaînes RAG.
08
🚀 Étape 7 : Déploiement
API FastAPI simple
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Query(BaseModel):
question: str
k: int = 4
@app.post("/rag")
async def rag_endpoint(query: Query):
response = qa_chain.invoke({"query": query.question})
return {
"answer": response["result"],
"sources": [doc.metadata for doc in response["source_documents"]]
}
# Lancer : uvicorn main:app --reload