Les Large Language Models (LLMs) comme GPT-4 et Claude sont extraordinairement puissants, mais ils souffrent d'une limitation fondamentale : leurs connaissances sont figées au moment de l'entraßnement. Ils ne peuvent pas accéder à vos documents internes, à votre base de données ou à des informations en temps réel. La Retrieval-Augmented Generation (RAG) résout exactement ce problÚme en combinant la puissance générative des LLMs avec la capacité de récupérer des informations à partir de sources externes.
Le ProblĂšme : Les Limites des LLMs
Avant de parler de RAG, il est important de comprendre pourquoi nous en avons besoin.
- Connaissances statiques : Un LLM ne sait que ce qu'il a vu pendant l'entraßnement. Si vous lui demandez un événement survenu aprÚs sa date de coupure, il ne peut pas répondre.
- Hallucinations : Quand un LLM ne connaßt pas la réponse, il a tendance à en inventer une, générant des informations plausibles mais complÚtement fausses.
- Pas d'accÚs aux données privées : Un LLM générique n'a pas accÚs à la documentation interne de votre entreprise, aux tickets ou à votre base de code.
La RAG rĂ©sout ces trois problĂšmes en fournissant au modĂšle un contexte pertinent rĂ©cupĂ©rĂ© depuis des sources externes au moment de la requĂȘte.
Qu'est-ce que la RAG ?
La Retrieval-Augmented Generation est une architecture qui enrichit le prompt envoyé à un LLM avec des informations récupérées depuis une base de connaissances externe. Au lieu de s'appuyer uniquement sur les connaissances paramétriques du modÚle, la RAG recherche d'abord les informations pertinentes puis les injecte dans le prompt, permettant au modÚle de générer des réponses précises et fondées.
Comment la RAG Fonctionne en Détail
L'architecture RAG se compose de deux phases principales : l'Indexation (hors ligne) et la Récupération + Génération (en ligne).
Phase 1 : Indexation (Ingestion des Documents)
La phase d'indexation prépare vos documents pour la recherche sémantique. Elle se compose de quatre étapes.
1. Chargement des Documents
Les documents peuvent provenir de n'importe quelle source : fichiers PDF, pages web, bases de données, fichiers Markdown, APIs. Le Document Loader lit ces documents et les convertit en texte structuré.
2. Découpage du Texte (Chunking)
Les LLMs ont une fenĂȘtre de contexte limitĂ©e, et les documents peuvent ĂȘtre trĂšs longs. Le Text Splitter divise les documents en fragments plus petits appelĂ©s chunks. La qualitĂ© du chunking est critique : des chunks trop petits perdent le contexte, des chunks trop grands diluent la pertinence.
Les stratégies les plus courantes sont :
- Recursive Character Splitting : Divise le texte récursivement en utilisant des séparateurs comme
\n\n,\n,., en respectant la structure du document. - Semantic Splitting : Utilise les embeddings pour trouver les points de rupture naturels dans le texte.
- Chunk Overlap : Inclut un chevauchement entre les chunks consécutifs pour préserver le contexte aux frontiÚres.
3. Embedding
Chaque chunk est transformé en un vecteur numérique (embedding) via un modÚle d'embedding (comme text-embedding-3-small d'OpenAI). Ces vecteurs capturent le sens sémantique du texte : des phrases avec des significations similaires auront des vecteurs proches dans l'espace multidimensionnel.
4. Vector Store
Les vecteurs sont sauvegardĂ©s dans un Vector Store (ou base de donnĂ©es vectorielle), comme ChromaDB, Pinecone, Weaviate ou FAISS. Cette base de donnĂ©es est optimisĂ©e pour la recherche de similaritĂ© : Ă©tant donnĂ© une requĂȘte, elle trouve les vecteurs (et donc les chunks de texte) les plus similaires.
Phase 2 : Récupération + Génération
Quand l'utilisateur pose une question :
- La question est transformĂ©e en embedding en utilisant le mĂȘme modĂšle d'embedding.
- Le Vector Store trouve les chunks les plus similaires via la recherche de similarité (typiquement la similarité cosinus ou la distance euclidienne).
- Les chunks récupérés sont insérés dans le prompt comme contexte.
- Le LLM génÚre la réponse basée sur le contexte fourni.
Construire une Pipeline RAG avec LangChain
LangChain est le framework Python (et JavaScript) le plus populaire pour construire des applications basées sur les LLMs. Il fournit des abstractions de haut niveau pour chaque composant de la pipeline RAG.
Installation
pip install langchain langchain-openai langchain-community chromadb
Ătape 1 : Charger les Documents
LangChain fournit des dizaines de Document Loaders pour différentes sources de données.
from langchain_community.document_loaders import ( PyPDFLoader, WebBaseLoader, DirectoryLoader, TextLoader, ) # Charger un PDF pdf_loader = PyPDFLoader("docs/manuel.pdf") pdf_docs = pdf_loader.load() # Charger une page web web_loader = WebBaseLoader("https://docs.example.com/guide") web_docs = web_loader.load() # Charger tous les fichiers .md d'un répertoire dir_loader = DirectoryLoader("./knowledge_base", glob="**/*.md", loader_cls=TextLoader) md_docs = dir_loader.load() all_docs = pdf_docs + web_docs + md_docs
Ătape 2 : DĂ©couper les Documents en Chunks
from langchain.text_splitter import RecursiveCharacterTextSplitter text_splitter = RecursiveCharacterTextSplitter( chunk_size=1000, chunk_overlap=200, separators=["\n\n", "\n", ". ", " ", ""], ) chunks = text_splitter.split_documents(all_docs) print(f"Documents originaux : {len(all_docs)}, Chunks : {len(chunks)}")
Le paramÚtre chunk_overlap est crucial : il crée un chevauchement entre les chunks consécutifs pour que le contexte ne soit pas perdu aux frontiÚres.
Ătape 3 : CrĂ©er les Embeddings et le Vector Store
from langchain_openai import OpenAIEmbeddings from langchain_community.vectorstores import Chroma embedding_model = OpenAIEmbeddings(model="text-embedding-3-small") vectorstore = Chroma.from_documents( documents=chunks, embedding=embedding_model, persist_directory="./chroma_db", )
Ătape 4 : CrĂ©er le Retriever
Le retriever est le composant qui, Ă©tant donnĂ© une requĂȘte, rĂ©cupĂšre les chunks les plus pertinents du vector store.
retriever = vectorstore.as_retriever( search_type="similarity", search_kwargs={"k": 4}, ) relevant_docs = retriever.invoke("Comment fonctionne l'authentification ?") for doc in relevant_docs: print(doc.page_content[:200]) print("---")
Ătape 5 : Construire la Chain RAG
Maintenant, assemblons le tout avec un LLM et un template de prompt.
from langchain_openai import ChatOpenAI from langchain_core.prompts import ChatPromptTemplate from langchain_core.runnables import RunnablePassthrough from langchain_core.output_parsers import StrOutputParser llm = ChatOpenAI(model="gpt-4o", temperature=0) prompt = ChatPromptTemplate.from_template(""" Réponds à la question en te basant uniquement sur le contexte fourni. Si le contexte ne contient pas assez d'informations, dis que tu ne sais pas. Contexte : {context} Question : {question} Réponse : """) def format_docs(docs): return "\n\n".join(doc.page_content for doc in docs) rag_chain = ( {"context": retriever | format_docs, "question": RunnablePassthrough()} | prompt | llm | StrOutputParser() ) response = rag_chain.invoke("Comment fonctionne l'authentification dans le systÚme ?") print(response)
Techniques RAG Avancées
La pipeline de base fonctionne bien, mais il existe plusieurs techniques pour améliorer significativement la qualité des réponses.
Multi-Query Retrieval
Parfois, la requĂȘte de l'utilisateur est ambiguĂ« ou n'est pas alignĂ©e avec le langage utilisĂ© dans les documents. Le Multi-Query Retriever gĂ©nĂšre automatiquement des variantes de la question originale pour capturer plusieurs perspectives.
from langchain.retrievers import MultiQueryRetriever multi_retriever = MultiQueryRetriever.from_llm( retriever=vectorstore.as_retriever(), llm=llm, ) docs = multi_retriever.invoke("Quelles sont les bonnes pratiques de sécurité ?")
Contextual Compression
Tout le contenu d'un chunk n'est pas pertinent pour la requĂȘte. Le Contextual Compression Retriever utilise un LLM pour extraire uniquement les parties pertinentes de chaque chunk rĂ©cupĂ©rĂ©.
from langchain.retrievers import ContextualCompressionRetriever from langchain.retrievers.document_compressors import LLMChainExtractor compressor = LLMChainExtractor.from_llm(llm) compression_retriever = ContextualCompressionRetriever( base_compressor=compressor, base_retriever=retriever, )
Hybrid Search
La recherche purement sémantique n'est pas toujours optimale. L'Hybrid Search combine la recherche sémantique (embeddings) avec la recherche lexicale (BM25, correspondance de mots-clés) pour obtenir de meilleurs résultats.
from langchain.retrievers import EnsembleRetriever from langchain_community.retrievers import BM25Retriever bm25_retriever = BM25Retriever.from_documents(chunks) bm25_retriever.k = 4 semantic_retriever = vectorstore.as_retriever(search_kwargs={"k": 4}) hybrid_retriever = EnsembleRetriever( retrievers=[bm25_retriever, semantic_retriever], weights=[0.4, 0.6], )
RAG Conversationnel (avec Mémoire)
Pour construire un chatbot RAG qui se souvient du contexte de la conversation, il faut ajouter une mémoire qui reformule les questions de l'utilisateur en tenant compte de l'historique.
from langchain.chains import create_history_aware_retriever from langchain_core.prompts import MessagesPlaceholder contextualize_prompt = ChatPromptTemplate.from_messages([ ("system", "Ătant donnĂ© l'historique du chat et la derniĂšre question de l'utilisateur, " "reformule la question pour qu'elle soit comprĂ©hensible sans l'historique."), MessagesPlaceholder("chat_history"), ("human", "{input}"), ]) history_aware_retriever = create_history_aware_retriever( llm, retriever, contextualize_prompt )
Bonnes Pratiques
- Choisir la bonne taille de chunk : Expérimentez avec différentes tailles (500-1500 tokens). Des chunks plus petits pour des réponses précises, plus grands pour un contexte élargi.
- Utiliser les métadonnées des documents : Ajoutez source, date et catégorie comme métadonnées aux chunks. Cela permet de filtrer les résultats lors de la récupération.
- Ăvaluer la qualitĂ© : Utilisez des frameworks comme RAGAS pour mesurer des mĂ©triques telles que la faithfulness, la relevancy et la context precision.
- Gérer les mises à jour des documents : Implémentez un pipeline de ré-ingestion pour maintenir le vector store synchronisé avec vos sources de données.
- Ajouter un re-ranker : AprÚs la récupération initiale, utilisez un modÚle de re-ranking (comme Cohere Rerank) pour réordonner les résultats selon la pertinence réelle.
Conclusion
La RAG est devenue l'architecture standard pour construire des applications IA qui nécessitent un accÚs à des connaissances spécifiques et à jour. LangChain simplifie énormément l'implémentation en fournissant des abstractions pour chaque composant de la pipeline.
Prochaines étapes :
- Expérimenter en local : Commencez avec ChromaDB et quelques documents pour vous familiariser avec la pipeline.
- Explorer LangSmith : Utilisez LangSmith pour surveiller et déboguer vos chains en production.
- Essayer différents modÚles d'embedding : Comparez des modÚles comme
text-embedding-3-small,text-embedding-3-largeet des modĂšles open-source de Sentence Transformers. - Consulter la documentation : La documentation LangChain est une ressource excellente et constamment mise Ă jour.