Créer un agent RAG intelligent avec LangChain et Python : le guide pas à pas 2026

Un LLM sans mémoire contextuelle, c’est comme un stagiaire brillant mais amnésique : il a réponse à tout, mais il invente quand il ne sait pas. Le Retrieval-Augmented Generation (RAG) résout ce problème en donnant au modèle une base de connaissances privée dans laquelle piocher avant de répondre. Résultat : des réponses plus précises, moins d’hallucinations, et une expertise sur vos données métier.

Dans ce tutoriel, on va construire un agent RAG de A à Z. Prérequis : Python 3.10+, une clé API OpenAI, et 30 minutes devant vous.

1. Architecture d’un agent RAG en 2026

Avant de coder, posons le schéma. Un pipeline RAG moderne se décompose en cinq étages :

  1. Ingestion : chargement des documents (PDF, Markdown, HTML, CSV)
  2. Chunking : découpage intelligent en segments sémantiques
  3. Embedding : transformation des chunks en vecteurs numériques
  4. Stockage : indexation dans une base vectorielle (ChromaDB)
  5. Retrieval + Génération : recherche des chunks pertinents + réponse du LLM

En 2026, l’architecture agentic RAG va plus loin : plutôt qu’une simple chaîne linéaire, on utilise un agent LangGraph capable de raisonner, d’itérer, et d’appeler des outils externes (recherche web, calculatrices, APIs) si la base documentaire ne suffit pas.

2. Mise en place de l’environnement

On commence par créer un environnement virtuel et installer les dépendances :

# Créer et activer l'environnement
python3.11 -m venv venv
source venv/bin/activate  # Linux/macOS
# venvScriptsactivate   # Windows

# Dépendances principales
pip install langchain langchain-openai langchain-community
pip install chromadb pypdf tiktoken
pip install ragas  # pour l'évaluation (optionnel)

Créez un fichier .env à la racine du projet :

OPENAI_API_KEY=sk-votre-cle-ici
LANGCHAIN_TRACING_V2=false

3. Chargement et chunking intelligent des documents

Le chunking est l’étape la plus sous-estimée du RAG. Un mauvais découpage = des réponses incohérentes. Voici une approche robuste avec RecursiveCharacterTextSplitter :

from langchain_community.document_loaders import PyPDFLoader, TextLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.schema import Document
from typing import List
import os

def load_and_chunk_documents(docs_path: str) -> List[Document]:
    """Charge tous les documents d'un dossier et les découpe en chunks."""
    documents = []
    
    for filename in os.listdir(docs_path):
        filepath = os.path.join(docs_path, filename)
        
        if filename.endswith('.pdf'):
            loader = PyPDFLoader(filepath)
            documents.extend(loader.load())
        elif filename.endswith(('.txt', '.md')):
            loader = TextLoader(filepath, encoding='utf-8')
            documents.extend(loader.load())
    
    # Splitter avec overlap pour préserver le contexte
    text_splitter = RecursiveCharacterTextSplitter(
        chunk_size=1000,        # ~1000 caractères par chunk
        chunk_overlap=200,      # 20% d'overlap pour la continuité
        separators=["nn", "n", ". ", " ", ""],
        length_function=len,
    )
    
    chunks = text_splitter.split_documents(documents)
    print(f"✅ {len(documents)} documents → {len(chunks)} chunks")
    return chunks

# Usage
mes_chunks = load_and_chunk_documents("./data/entreprise/")

💡 Astuce de pro : Pour des documents techniques, réduisez le chunk_size à 500-800 et augmentez l’overlap à 25 %. Pour des récits ou de la documentation narrative, montez à 1500 avec 10 % d’overlap.

4. Embeddings et stockage vectoriel avec ChromaDB

On transforme chaque chunk en vecteur avec le modèle d’embedding d’OpenAI (text-embedding-3-large, le meilleur rapport qualité/prix en 2026) et on stocke dans ChromaDB, une base vectorielle légère et performante :

from langchain_openai import OpenAIEmbeddings
from langchain_chroma import Chroma

def create_vector_store(chunks: List[Document], persist_dir: str = "./chroma_db"):
    """Crée (ou charge) un vector store persistant."""
    embeddings = OpenAIEmbeddings(
        model="text-embedding-3-large",
        dimensions=1024  # Réduit pour la vitesse ; 3072 pour la qualité max
    )
    
    vector_store = Chroma.from_documents(
        documents=chunks,
        embedding=embeddings,
        persist_directory=persist_dir,
        collection_name="docs_entreprise"
    )
    
    print(f"✅ Vector store créé : {vector_store._collection.count()} vecteurs")
    return vector_store

# Premier run : création
vector_store = create_vector_store(mes_chunks)

# Runs suivants : chargement
# vector_store = Chroma(
#     persist_directory="./chroma_db",
#     embedding_function=OpenAIEmbeddings(model="text-embedding-3-large"),
#     collection_name="docs_entreprise"
# )

5. Construction de la chaîne RAG avec retrieval

C’est le cœur du système. On utilise LangChain Expression Language (LCEL) pour construire une chaîne de retrieval propre et typée :

Le prompt système : la clé de la fiabilité

Un bon prompt RAG doit imposer des règles strictes au LLM. Sans cela, le modèle ignorera le contexte et puisera dans ses connaissances générales — l’exact opposé de ce qu’on veut. Notre prompt ci-dessous inclut quatre garde-fous : réponse basée uniquement sur le contexte, citation obligatoire des sources, concision, et proposition de clarification en cas d’ambiguïté.

from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser

def build_rag_chain(vector_store):
    """Construit la chaîne RAG complète."""
    
    # Modèle de langage
    llm = ChatOpenAI(
        model="gpt-4o",  # ou "claude-3-5-sonnet" selon votre fournisseur
        temperature=0.2,  # Bas pour des réponses factuelles
    )
    
    # Retriever : top 5 chunks les plus pertinents
    retriever = vector_store.as_retriever(
        search_type="similarity",
        search_kwargs={"k": 5}
    )
    
    # Prompt système avec instructions strictes
    prompt = ChatPromptTemplate.from_messages([
        ("system", """Tu es un assistant expert qui répond UNIQUEMENT à partir 
du contexte fourni ci-dessous. 
Règles strictes :
1. Si la réponse n'est PAS dans le contexte, réponds : 
   "Je ne trouve pas cette information dans ma base documentaire."
2. Cite TOUJOURS la source (nom du document) quand tu réponds.
3. Sois concis mais complet.
4. Propose des questions de clarification si le contexte est ambigu.

Contexte :
{context}"""),
        ("human", "{question}")
    ])
    
    # Chaîne LCEL
    rag_chain = (
        {"context": retriever | _format_docs, "question": RunnablePassthrough()}
        | prompt
        | llm
        | StrOutputParser()
    )
    
    return rag_chain

def _format_docs(docs):
    """Formate les documents retrouvés pour le prompt."""
    return "nn---nn".join(
        f"[Source: {doc.metadata.get('source', 'inconnu')}]n{doc.page_content}"
        for doc in docs
    )

# Construction
rag = build_rag_chain(vector_store)

# Test
reponse = rag.invoke("Quelle est la politique de congés de l'entreprise ?")
print(reponse)

Retriever avancé : MMR pour la diversité

Par défaut, le retriever utilise la similarité cosinus pure, ce qui peut renvoyer cinq chunks quasi identiques. Pour des réponses plus riches, privilégiez le Maximum Marginal Relevance (MMR) qui équilibre pertinence et diversité :

retriever = vector_store.as_retriever(
    search_type="mmr",
    search_kwargs={"k": 5, "fetch_k": 20, "lambda_mult": 0.7}
)

lambda_mult=0.7 donne 70 % de poids à la similarité et 30 % à la diversité. Ajustez selon vos besoins : 0.9 pour de la documentation technique très spécifique, 0.5 pour de la recherche exploratoire.

6. Évolution vers un agent RAG avec LangGraph

La chaîne ci-dessus fonctionne bien pour des questions simples. Mais pour un assistant vraiment intelligent, on passe à LangGraph — un graphe d’état qui permet à l’agent de raisonner en plusieurs étapes :

from langgraph.graph import StateGraph, END
from typing import TypedDict, List

class AgentState(TypedDict):
    question: str
    documents: List[Document]
    answer: str
    iteration: int

def retrieve(state: AgentState) -> AgentState:
    """Étape 1 : récupérer les documents pertinents."""
    retriever = vector_store.as_retriever(search_kwargs={"k": 5})
    docs = retriever.invoke(state["question"])
    return {"documents": docs, "iteration": state.get("iteration", 0) + 1}

def grade_documents(state: AgentState) -> str:
    """Étape 2 : évaluer si les documents sont suffisants."""
    if len(state["documents"]) == 0:
        return "no_docs"
    # Vérifier la pertinence avec un score simple
    return "sufficient"

def generate(state: AgentState) -> AgentState:
    """Étape 3 : générer la réponse."""
    llm = ChatOpenAI(model="gpt-4o", temperature=0.2)
    context = _format_docs(state["documents"])
    answer = llm.invoke(
        f"Contexte:n{context}nnQuestion: {state['question']}"
    )
    return {"answer": answer.content}

# Construction du graphe
workflow = StateGraph(AgentState)
workflow.add_node("retrieve", retrieve)
workflow.add_node("generate", generate)
workflow.set_entry_point("retrieve")
workflow.add_conditional_edges("retrieve", grade_documents, {
    "sufficient": "generate",
    "no_docs": END
})
workflow.add_edge("generate", END)

agent = workflow.compile()

# Utilisation
result = agent.invoke({"question": "Quels sont les délais de livraison ?"})
print(result["answer"])

7. Évaluer la qualité du RAG avec RAGAS

Un RAG non évalué, c’est un RAG auquel vous ne pouvez pas faire confiance. RAGAS (RAG Assessment) est le standard d’évaluation en 2026 :

from ragas import evaluate
from ragas.metrics import (
    faithfulness, answer_relevancy, context_precision, context_recall
)
from datasets import Dataset

# Préparer les données d'évaluation
eval_data = Dataset.from_dict({
    "question": ["Quelle est la politique RGPD ?", "Délai de rétractation ?"],
    "answer": [reponse_1, reponse_2],
    "contexts": [contexts_1, contexts_2],
    "ground_truth": [verite_1, verite_2]
})

# Évaluer
scores = evaluate(
    eval_data,
    metrics=[faithfulness, answer_relevancy, context_precision, context_recall]
)
print(scores)
# Exemple de résultat :
# {'faithfulness': 0.94, 'answer_relevancy': 0.89, 
#  'context_precision': 0.91, 'context_recall': 0.87}

8. Mise en production : les considérations clés

Passer du prototype à la production exige quelques précautions :

  • Mise à jour incrémentale : plutôt que de reconstruire tout le vector store, utilisez vector_store.add_documents(new_chunks) pour les nouveaux contenus
  • Cache intelligent : les questions fréquentes (FAQ) doivent être cachées pour éviter des appels API inutiles
  • Monitoring : traquez la latence de retrieval, le taux de « réponse non trouvée », et les coûts API
  • Fallback : si le retrieval ne trouve rien, proposez une recherche web ou redirigez vers un humain
  • Mise à l’échelle : pour plus de 100 000 chunks, migrez de ChromaDB vers Pinecone ou Weaviate

Le RAG est la brique fondamentale des applications IA d’entreprise en 2026. Maîtrisez-le, et vous pourrez construire des assistants qui connaissent vraiment votre métier — pas juste des perroquets statistiques.

Sources et références

  1. LangChain — Build a Retrieval Augmented Generation (RAG) App
  2. LangChain — Agentic RAG with LangGraph
  3. ChromaDB — Official Documentation
  4. RAGAS — RAG Assessment Framework
  5. OpenAI — Embeddings API Documentation
  6. Kapa.ai — RAG Best Practices 2026

📖 À lire aussi : Raycast vs Alfred vs Spotlight : quel launcher domine la productivité développeur en 2026 ?

W
WP Admin Lab

Architecte web full-stack. WordPress, performance, data et sécurité. Notes de terrain, tests reproductibles et retours d'expérience.