On va être honnête deux secondes. La plupart des assistants IA, quand tu les branches “comme ça”, ils inventent, ils brodent, ils répondent avec assurance même quand ils ne savent pas. Et si tu leur colles tes documents en vrac dans un prompt, ça marche… jusqu’à ce que ça ne marche plus. Trop long, trop lent, trop cher, et surtout pas fiable.
Le RAG, c’est juste une manière pragmatique de résoudre ça. Sans magie.
RAG = tu poses une question, le système va chercher dans tes docs les bons passages, puis il demande au modèle de répondre en s’appuyant dessus. Point.
Et dans cet article, on va construire un assistant RAG sur tes documents, sans jargon inutile. En mode “blog tech pro” comme j’aime sur Le Blog Tech Pro de Samyn-Antoy ABASSE (si tu veux d’autres guides du même style, pense à passer sur https://monblog-sa-abasse.blogspot.com, j’y poste une veille et des méthodes assez pratico-pratiques).
Le problème que le RAG règle vraiment
Un LLM “pur” (ChatGPT, Claude, etc.) a deux limites qui nous embêtent en production :
- Il ne connaît pas tes docs privées (procédures internes, specs, notes Notion, wiki, PDF clients).
- Il hallucine quand l’info manque ou quand la question est ambiguë.
Le RAG ajoute une couche simple :
- on indexe les documents
- au moment de la question, on récupère les passages pertinents
- on répond en citant ces passages (ou au moins en s’y limitant)
Résultat attendu : réponses plus justes, plus traçables, et beaucoup moins de “oui oui je confirme” inventé.
La vue d’ensemble, sans prise de tête
Voici le pipeline en français normal :
- Ingestion : tu collectes tes docs (PDF, Markdown, pages web, tickets, etc.)
- Découpage : tu coupes en petits morceaux lisibles (les “chunks”)
- Embeddings : tu transformes chaque chunk en vecteur (une signature numérique)
- Base vectorielle : tu stockes ces vecteurs (FAISS, Chroma, Pinecone, etc.)
- Recherche : à la question, tu récupères les chunks les plus proches
- Génération : tu donnes ces chunks au modèle pour répondre
Tu vois, il n’y a pas besoin de sortir une thèse.
Image mentale rapide (et utile)
Oui, ce n’est pas un schéma “RAG officiel”, mais l’idée est la même. Une boucle recherche → réponse.
Ce qu’il te faut (stack minimale)
On peut faire ça en local, sans SaaS, sans envoyer tes docs n’importe où.
- Python 3.10+
- Une lib d’orchestration : optionnel (LangChain, LlamaIndex). On peut aussi coder à la main.
- Une base vectorielle : FAISS (local), Chroma (local), ou un service managé.
- Un modèle d’embeddings : local (sentence-transformers) ou via API.
- Un LLM : local (Ollama) ou via API.
Dans cet article je te donne un chemin simple, reproductible :
- embeddings : sentence-transformers (gratuit, local)
- vecteurs : FAISS (local)
- génération : au choix Ollama (local) ou OpenAI (API)
Étape 1 : préparer un mini corpus de documents
Fais simple au début.
Crée un dossier :
rag-docs/ docs/ procedure-deploiement.md runbook-incidents.md faq-outil-x.txt
Si tu as des PDF, commence par les convertir en texte propre (sinon tu vas indexer du bruit). Pour une première version, Markdown et TXT, c’est royal.
Exemple de doc Markdown (très court) :
md
- Vérifier la branche release.
- Lancer la CI.
- Déployer sur staging.
- Valider avec les tests smoke.
- Déployer en production.
Étape 2 : découper tes docs correctement (c’est là que beaucoup échouent)
Le découpage, c’est sous-estimé. Pourtant, si tu découpes mal, ta recherche ramènera des bouts inutiles, et le modèle répondra à côté.
Règle simple :
- chunks de 200 à 500 mots (ou 800 à 1500 caractères)
- avec chevauchement de 10 à 20 % pour garder le contexte
Pourquoi le chevauchement ? Parce qu’une réponse peut dépendre d’une phrase juste avant.
Étape 3 : indexer avec embeddings + FAISS (code complet)
Installe les dépendances :
bash pip install faiss-cpu sentence-transformers numpy
Puis un script build_index.py :
python import os import glob import numpy as np import faiss from sentence_transformers import SentenceTransformer
DOCS_DIR = "docs" INDEX_DIR = "index" MODEL_NAME = "sentence-transformers/all-MiniLM-L6-v2"
def read_files(): paths = glob.glob(os.path.join(DOCS_DIR, "**/*"), recursive=True) texts = [] metadatas = [] for p in paths: if os.path.isdir(p): continue if not p.lower().endswith((".txt", ".md")): continue with open(p, "r", encoding="utf-8", errors="ignore") as f: t = f.read().strip() if not t: continue texts.append(t) metadatas.append({"path": p}) return texts, metadatas
def chunk_text(text, max_chars=1200, overlap=200): chunks = [] start = 0 while start < len(text): end = min(len(text), start + max_chars) chunk = text[start:end].strip() if chunk: chunks.append(chunk) start = end - overlap if start < 0: start = 0 if end == len(text): break return chunks
def main(): os.makedirs(INDEX_DIR, exist_ok=True)
texts, metas = read_files()
all_chunks = []
all_meta = []
for t, m in zip(texts, metas):
chunks = chunk_text(t)
for i, c in enumerate(chunks):
all_chunks.append(c)
all_meta.append({"path": m["path"], "chunk_id": i})
model = SentenceTransformer(MODEL_NAME)
emb = model.encode(all_chunks, convert_to_numpy=True, show_progress_bar=True)
emb = emb.astype("float32")
dim = emb.shape[1]
index = faiss.IndexFlatIP(dim)
faiss.normalize_L2(emb)
index.add(emb)
faiss.write_index(index, os.path.join(INDEX_DIR, "docs.faiss"))
np.save(os.path.join(INDEX_DIR, "chunks.npy"), np.array(all_chunks, dtype=object))
np.save(os.path.join(INDEX_DIR, "meta.npy"), np.array(all_meta, dtype=object))
print(f"Index construit : {len(all_chunks)} chunks")
if name == "main": main()
Tu lances :
bash python build_index.py
Voilà. Tu as un index local.
Étape 4 : poser une question et récupérer les passages pertinents
Crée search.py :
python import os import numpy as np import faiss from sentence_transformers import SentenceTransformer
INDEX_DIR = "index" MODEL_NAME = "sentence-transformers/all-MiniLM-L6-v2"
def load(): index = faiss.read_index(os.path.join(INDEX_DIR, "docs.faiss")) chunks = np.load(os.path.join(INDEX_DIR, "chunks.npy"), allow_pickle=True) meta = np.load(os.path.join(INDEX_DIR, "meta.npy"), allow_pickle=True) return index, chunks, meta
def search(query, k=5): index, chunks, meta = load() model = SentenceTransformer(MODEL_NAME)
q = model.encode([query], convert_to_numpy=True).astype("float32")
faiss.normalize_L2(q)
scores, ids = index.search(q, k)
results = []
for score, idx in zip(scores[0], ids[0]):
results.append({
"score": float(score),
"text": str(chunks[idx]),
"meta": dict(meta[idx]),
})
return results
if name == "main": q = input("Question : ").strip() hits = search(q, k=5) for h in hits: print("\n---") print(h["meta"], "score=", round(h["score"], 4)) print(h["text"][:500])
Tu testes :
bash python search.py
À ce stade, tu as déjà un “moteur de recherche sémantique” sur tes docs. Même sans LLM, c’est utile.
Étape 5 : générer une réponse avec contexte (le vrai RAG)
Maintenant, on prend les meilleurs passages et on demande au modèle de répondre uniquement avec ça.
Option A : avec Ollama (local)
Installe Ollama et un modèle (ex. llama3). Puis :
bash pip install requests
Crée rag_ollama.py :
python import requests from search import search
OLLAMA_URL = "http://localhost:11434/api/generate" MODEL = "llama3"
def build_prompt(question, contexts): joined = "\n\n".join([f"[source {i+1}]\n{c}" for i, c in enumerate(contexts)]) return f"""Tu es un assistant. Réponds uniquement à partir des sources fournies. Si l'information n'est pas dans les sources, dis : « je ne sais pas à partir des documents fournis ».
Sources : {joined}
Question : {question}
Réponse (en français, concise, structurée) : """
def ask(question): hits = search(question, k=5) contexts = [h["text"] for h in hits]
prompt = build_prompt(question, contexts)
payload = {
"model": MODEL,
"prompt": prompt,
"stream": False,
"options": {"temperature": 0.2}
}
r = requests.post(OLLAMA_URL, json=payload, timeout=120)
r.raise_for_status()
return r.json()["response"], hits
if name == "main": q = input("Question : ").strip() answer, hits = ask(q) print("\nRéponse :\n", answer) print("\nSources utilisées :") for i, h in enumerate(hits, 1): print(f"{i}. {h['meta']['path']} (score={h['score']:.3f})")
Tu lances et tu obtiens une réponse + la liste des sources. Déjà, c’est une énorme différence en usage réel.
Option B : avec API OpenAI (si tu préfères)
Même principe. Ce qui change : l’appel HTTP et le modèle. Le reste, c’est identique.
Image : à quoi ressemble “le contexte” envoyé au modèle
C’est ça le RAG en pratique. Tu empiles des extraits courts, pertinents. Et tu forces le modèle à rester dans les clous.
Les règles simples pour éviter un assistant “bizarre”
Tu peux avoir le meilleur code du monde, si tu ignores ces points, tu vas être déçu.
1) Dis au modèle quand il doit dire « je ne sais pas »
C’est non négociable. Mets-le dans le prompt, clairement, comme je l’ai fait plus haut.
2) Ne récupère pas trop de passages
k=5 ou k=8 suffit souvent. Si tu balances 40 chunks, tu dilues tout, et le modèle se perd.
3) Ajoute les chemins des fichiers dans la réponse
Même si tu ne fais pas une citation mot à mot, affiche les sources. En entreprise, c’est ce qui rend l’outil “acceptable”.
4) Nettoie tes docs
Le RAG n’invente pas, mais il amplifie tes défauts. Si ton doc est obsolète, l’assistant va répondre obsolète. Normal.
Et si tu veux un truc vraiment “assistant” (pas juste un script)
À partir de là, tu peux packager en :
- une petite API (FastAPI)
- une UI simple (Streamlit)
- un bot Slack ou Telegram
- une extension interne
Et tu peux ajouter des features “pro” :
- cache des questions fréquentes
- gestion des droits (ne pas exposer des docs RH à tout le monde)
- logs et observabilité (quelles questions, quelles sources)
- feedback utilisateur (utile ou non)
C’est exactement le genre de checklists que je partage parfois sur monblog-sa-abasse.blogspot.com dans les posts productivité et build de systèmes.
Les erreurs classiques (et comment les corriger vite)
« Il ne trouve rien alors que c’est dans le document »
Souvent c’est :
- chunk trop gros
- chunk trop petit et sans titre
- doc mal encodé
- tu utilises un embedding médiocre
Fix rapide : découpe avec titres, garde des chunks cohérents, et teste all-MiniLM-L6-v2 ou un modèle d’embeddings plus récent.
« Il répond à côté, mais les sources ont l’air bonnes »
Ça arrive si :
- le prompt n’est pas strict
- température trop élevée
- trop de contexte
Fix rapide : température à 0.2, prompt “répond uniquement avec sources”, et diminue k.
« Ça marche, mais c’est lent »
Fix rapide :
- précharge le modèle d’embeddings une fois (au lieu de le recharger à chaque requête)
- passe sur une base vectorielle plus adaptée à ton volume
- mets en cache les embeddings des questions fréquentes
Conclusion : le RAG, c’est juste du bon sens appliqué
Si tu retiens une seule idée : un assistant fiable, ce n’est pas un modèle plus “intelligent”, c’est un modèle mieux alimenté, au bon moment, avec des règles claires.
Tu peux démarrer petit, en local, avec trois fichiers Markdown et FAISS. Puis scaler doucement. C’est même préférable, tu apprends plus vite.
Si tu veux, je peux aussi te proposer une version “packagée” pour ton usage : structure de projet, API FastAPI, UI Streamlit, et une checklist de déploiement Linux. C’est le genre de choses que je documente et que je vends parfois en service autour de l’automatisation et des systèmes. Dans tous les cas, tu peux suivre les prochains articles sur Le Blog Tech Pro de Samyn-Antoy ABASSE : https://monblog-sa-abasse.blogspot.com
Questions fréquemment posées
Qu'est-ce que le RAG et pourquoi est-il important pour les assistants IA ?
Le RAG (Retrieval-Augmented Generation) est une méthode pragmatique qui permet de répondre aux limites des assistants IA classiques, notamment leur tendance à inventer des réponses et leur manque de connaissance des documents privés. Le RAG fonctionne en indexant vos documents, en recherchant les passages pertinents lors d'une question, puis en générant une réponse basée sur ces passages, ce qui améliore la précision et la fiabilité.
Quels sont les principaux problèmes des modèles de langage pur comme ChatGPT en production ?
Les modèles de langage pur ont deux limites majeures : ils ne connaissent pas vos documents privés (comme procédures internes, notes, PDF clients) et ils peuvent halluciner en inventant des informations lorsque les données manquent ou que la question est ambiguë. Le RAG aide à surmonter ces problèmes en intégrant une couche de recherche documentaire.
Comment fonctionne le pipeline RAG pour construire un assistant sur ses propres documents ?
Le pipeline RAG comprend plusieurs étapes simples : 1) Ingestion des documents (PDF, Markdown, tickets...), 2) Découpage en petits morceaux appelés chunks, 3) Transformation de chaque chunk en vecteur via embeddings, 4) Stockage dans une base vectorielle (FAISS, Chroma...), 5) Recherche des chunks pertinents à la question posée, 6) Génération de la réponse par le modèle en s'appuyant sur ces chunks.
Quels outils et technologies sont nécessaires pour mettre en place un système RAG local ?
Pour un stack minimal local sans SaaS, il faut : Python 3.10+, une base vectorielle locale comme FAISS ou Chroma, un modèle d'embeddings local comme sentence-transformers (gratuit), et un LLM local (Ollama) ou via API (OpenAI). Une librairie d'orchestration comme LangChain est optionnelle mais peut faciliter l'implémentation.
Pourquoi le découpage des documents est-il crucial dans un système RAG ?
Un bon découpage est essentiel car il garantit que la recherche renvoie des passages utiles et cohérents. Il faut créer des chunks de 200 à 500 mots avec un chevauchement de 10 à 20 % pour conserver le contexte entre les morceaux. Un mauvais découpage entraîne des réponses hors sujet ou incomplètes du modèle.
Comment préparer un corpus simple pour commencer avec le RAG ?
Commencez par créer un dossier contenant vos documents au format Markdown ou TXT (par exemple procedure-deploiement.md, runbook-incidents.md). Si vous avez des PDF, convertissez-les proprement en texte avant l'indexation pour éviter le bruit. Un exemple simple de doc Markdown peut contenir une liste claire d'étapes ou d'instructions.
0 Commentaires