Apprends Ă  connecter un LLM Ă  tes propres documents

Tu as dĂ©jĂ  utilisĂ© ChatGPT ou Claude pour poser des questions gĂ©nĂ©rales. Mais que faire quand tu veux interroger tes propres documents — un PDF de cours, une base de connaissances interne, ou ta collection de notes ?

C’est exactement le problĂšme que rĂ©sout le RAG (Retrieval-Augmented Generation). Dans cet article, tu vas construire pas Ă  pas un moteur de RAG complet avec Python, en utilisant des modĂšles open-source et une base vectorielle. À la fin, tu pourras poser des questions en langage naturel Ă  n’importe lequel de tes documents.

Qu’est-ce que le RAG ?

Le RAG, c’est un pattern qui combine recherche documentaire et gĂ©nĂ©ration de texte. ConcrĂštement :

  • Tu dĂ©coupes tes documents en petits morceaux (segments)
  • Tu convertis chaque morceau en un vecteur (une reprĂ©sentation mathĂ©matique du sens)
  • Tu stockes ces vecteurs dans une base vectorielle
  • Quand un utilisateur pose une question, tu cherches les vecteurs les plus proches
  • Tu envoies les morceaux trouvĂ©s + la question Ă  un LLM, qui gĂ©nĂšre une rĂ©ponse contextuelle

L’avantage ? Pas besoin de fine-tuner un modĂšle. Tu changes les documents, le comportement change. C’est modulaire, rapide Ă  mettre en place, et trĂšs efficace.

Prérequis

Pour suivre ce tutoriel, installe les bibliothĂšques suivantes :

pip install chromadb sentence-transformers langchain langchain-community pypdf

Si tu dĂ©butes avec Python et que tout ça te semble flou, dĂ©couvre notre formation Python — on part de zĂ©ro, jusqu’Ă  coder tes propres projets IA.

Étape 1 : Charger et dĂ©couper un document PDF

Commençons par charger un fichier PDF. Pour l’exemple, prenons un rapport ou un article que tu souhaites interroger.

from langchain_community.document_loaders import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter

# Charge le PDF
loader = PyPDFLoader("mon_document.pdf")
documents = loader.load()

# Découpe en segments de 1000 caractÚres
splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000,
    chunk_overlap=200,
    separators=["\n\n", "\n", ". ", " ", ""]
)

chunks = splitter.split_documents(documents)
print(f"Nombre de segments : {len(chunks)}")
print(f"Premier segment : {chunks[0].page_content[:100]}...")

Le chunk_overlap de 200 caractĂšres garantit qu’un morceau d’information coupĂ© en deux ne soit pas perdu — la fin d’un segment chevauche le dĂ©but du suivant.

Étape 2 : CrĂ©er les embeddings avec Sentence Transformers

Les embeddings sont le cƓur du RAG. Ce sont des vecteurs (listes de nombres) qui capturent le sens des phrases. Deux phrases similaires auront des vecteurs proches.

On utilise sentence-transformers, un framework open-source avec des modÚles pré-entraßnés :

from sentence_transformers import SentenceTransformer

# ModÚle français performant et léger
model = SentenceTransformer("dangvantuan/sentence-camembert-large")

# Convertir un texte en vecteur
texte = "Qu'est-ce que le RAG en intelligence artificielle ?"
vecteur = model.encode(texte)

print(f"Dimension du vecteur : {len(vecteur)}")
print(f"PremiĂšres valeurs : {vecteur[:5]}")

💡 Astuce : Tu peux utiliser un modùle multilingue (comme intfloat/multilingual-e5-small) si tes documents sont en anglais ET en français.

Étape 3 : Stocker les vecteurs dans ChromaDB

Maintenant qu’on a les vecteurs, il faut les stocker pour pouvoir les chercher rapidement. On utilise ChromaDB, une base vectorielle lĂ©gĂšre qui tourne en local :

import chromadb
from chromadb.config import Settings

# Initialise la base vectorielle
client = chromadb.Client(Settings(
    chroma_db_impl="duckdb+parquet",
    persist_directory="./rag_db"
))

# Crée une collection
collection = client.create_collection(name="mes_documents")

# Ajoute les segments avec leurs vecteurs
for i, chunk in enumerate(chunks):
    vecteur = model.encode(chunk.page_content).tolist()
    collection.add(
        embeddings=[vecteur],
        documents=[chunk.page_content],
        metadatas=[{"source": chunk.metadata.get("source", "inconnu")}],
        ids=[f"chunk_{i}"]
    )

print("✅ Base vectorielle créée avec succĂšs !")

Chaque segment est stocké avec :

  • Son embedding (le vecteur)
  • Le texte original
  • Des mĂ©tadonnĂ©es (source, date, etc.)

Étape 4 : Rechercher les documents pertinents

Quand un utilisateur pose une question, on la convertit en vecteur, puis on cherche les N segments les plus proches dans la base :

def recherche_documents(question, k=3):
    """Trouve les k segments les plus pertinents pour une question."""
    vecteur_question = model.encode(question).tolist()
    
    resultats = collection.query(
        query_embeddings=[vecteur_question],
        n_results=k
    )
    
    return resultats["documents"][0]

question = "Quels sont les avantages du RAG ?"
docs = recherche_documents(question)

print("\n📄 Documents trouvĂ©s :")
for i, doc in enumerate(docs):
    print(f"\n--- Résultat {i+1} ---")
    print(doc[:200])

La fonction collection.query() calcule automatiquement la similaritĂ© cosinus entre le vecteur de la question et tous les vecteurs stockĂ©s. Elle retourne les K plus proches voisins — c’est ultra-rapide, mĂȘme avec des millions de segments.

Étape 5 : GĂ©nĂ©rer une rĂ©ponse avec un LLM

DerniÚre étape : envoyer les documents trouvés à un LLM pour générer une réponse contextuelle.

On peut utiliser OpenAI, ou un modĂšle local via Ollama :

from langchain_openai import ChatOpenAI

def repondre_question(question, documents):
    """GénÚre une réponse basée sur les documents trouvés."""
    
    contexte = "\n\n".join(documents[:3])
    
    prompt = f"""Tu es un assistant spécialisé. Réponds à la question 
uniquement en utilisant les informations ci-dessous.
Si les documents ne contiennent pas la réponse, dis-le poliment.

DOCUMENTS :
{contexte}

QUESTION : {question}

RÉPONSE :"""
    
    llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
    reponse = llm.invoke(prompt)
    
    return reponse.content

# Test complet
question = "Qu'est-ce que le RAG et comment ça fonctionne ?"
docs = recherche_documents(question)
reponse = repondre_question(question, docs)

print(f"\nđŸ€– Question : {question}")
print(f"\n💬 RĂ©ponse : {reponse}")

🔑 Le secret : en passant les documents dans le contexte du prompt, on Ă©vite les hallucinations. Le LLM n’invente pas — il s’appuie sur des faits rĂ©els que tu lui fournis.

Aller plus loin

Ce pipeline de base peut ĂȘtre amĂ©liorĂ© de plusieurs façons :

  • Hybrid search : combine recherche vectorielle et recherche par mots-clĂ©s (BM25) pour de meilleurs rĂ©sultats
  • Re-ranking : ajoute un modĂšle de classement entre la recherche et la gĂ©nĂ©ration pour affiner les rĂ©sultats
  • Streaming : affiche la rĂ©ponse mot par mot comme ChatGPT pour une meilleure UX
  • Conversation mĂ©moire : conserve l’historique des Ă©changes pour des questions en follow-up
  • Multi-format : supporte PDF, DOCX, HTML, pages web, et mĂȘme des bases de donnĂ©es SQL

Le RAG est devenu le standard de facto pour tous les projets d’IA documentaire. Pas cher, efficace, sans fine-tuning. Google, Notion, et des centaines de startups l’utilisent en production.

PrĂȘt Ă  maĂźtriser Python pour l’IA ?

Ce tutoriel suppose que tu sais dĂ©jĂ  exĂ©cuter du Python, installer des packages et manipuler des fichiers. Si ce n’est pas encore le cas, pas de panique — notre formation Python : les fondamentaux te donne toutes les bases pour coder en Python, du premier script jusqu’Ă  tes premiers projets IA.

Construis ton avenir dans l’IA, une ligne de code Ă  la fois. 🚀

Photo de AltumCode sur Unsplash.