
Les guides précédents ont posé chaque brique séparément : extraction, nettoyage, chunking, embeddings, base vectorielle. Ce guide les assemble en un RAG complet — un assistant qui répond à des questions sur une documentation, en citant ses sources. La base vectorielle est Qdrant : un vrai serveur, capable de filtrage et de montée en charge. La génération tourne sur Ollama, en local. Vous indexerez un corpus, récupérerez les passages pertinents d'une question, et construirez la réponse sourcée. À la fin, vous aurez un pipeline RAG de bout en bout, fonctionnel et testé. Public visé : développeur ayant suivi les étapes du parcours et prêt à les réunir.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Indexer un corpus dans Qdrant : découpe, vectorisation, stockage.
- Rechercher les passages pertinents d'une question.
- Construire un prompt RAG qui cadre le modèle.
- Générer une réponse sourcée avec un modèle local.
- Tester un pipeline RAG de bout en bout.
Prérequis
Section intitulée « Prérequis »- Python 3.10+.
- Qdrant en service :
docker run -d -p 6333:6333 qdrant/qdrant. - Une instance Ollama avec
qwen2.5etnomic-embed-text. - Avoir parcouru les étapes : chunking, embeddings.
Pourquoi Qdrant pour un RAG qui dure
Section intitulée « Pourquoi Qdrant pour un RAG qui dure »FAISS et Chroma sont parfaits pour prototyper — mais ils vivent dans le processus. Dès qu'un RAG doit être interrogé par plusieurs applications, monter en charge ou filtrer finement, il faut une base vectorielle serveur.
Qdrant est cette base. Elle tourne comme un service — un conteneur Docker —, et le code s'y connecte en réseau. Plusieurs applications partagent le même index ; la base persiste indépendamment du code ; le filtrage par payload et la montée en charge sont natifs. C'est la base vectorielle de référence pour un RAG destiné à durer, et c'est elle qu'on utilise ici et dans les guides suivants.
Le principe ne change pas pour autant : vectoriser, indexer, rechercher. Seul l'outil de stockage gagne en robustesse.
Indexer le corpus dans Qdrant
Section intitulée « Indexer le corpus dans Qdrant »L'indexation transforme un corpus de documents en collection interrogeable. Trois opérations s'enchaînent : découper, vectoriser, stocker.
On crée d'abord la collection, en la typant : la dimension des vecteurs et la distance de comparaison.
from qdrant_client import QdrantClientfrom qdrant_client.models import Distance, PointStruct, VectorParams
DIMENSION = 768 # taille des vecteurs de nomic-embed-textCOLLECTION = "rag_documentation"
client = QdrantClient(host="localhost", port=6333)if client.collection_exists(COLLECTION): client.delete_collection(COLLECTION)client.create_collection( COLLECTION, vectors_config=VectorParams(size=DIMENSION, distance=Distance.COSINE),)On découpe ensuite chaque document en chunks, on les vectorise en un lot, et on les range dans Qdrant sous forme de points.
chunks = []for doc in DOCUMENTS: for chunk in decouper(doc["texte"]): chunks.append({"texte": chunk, "source": doc["titre"]})
vecteurs = vectoriser([c["texte"] for c in chunks])points = [ PointStruct(id=i, vector=vecteurs[i], payload=chunks[i]) for i in range(len(chunks))]client.upsert(COLLECTION, points=points)Chaque PointStruct réunit trois choses : un identifiant, un vecteur, et un payload — des données libres attachées au point. Ici, le payload porte le texte du chunk et sa source. C'est lui qui rendra la réponse traçable.
Rechercher les passages pertinents
Section intitulée « Rechercher les passages pertinents »À chaque question, on cherche les chunks les plus proches. On vectorise la question avec le même modèle qu'à l'indexation, puis on interroge Qdrant.
def rechercher(question: str, k: int = 3) -> list[dict]: """Renvoie les k chunks les plus proches de la question.""" v_question = vectoriser([question])[0] reponse = client.query_points( COLLECTION, query=v_question, limit=k, with_payload=True ) return [ {"texte": p.payload["texte"], "source": p.payload["source"], "score": p.score} for p in reponse.points ]query_points renvoie les points classés par similarité décroissante. Chaque résultat ramène son score et son payload — le texte du chunk et sa source, qu'on avait rangés à l'indexation. Le paramètre k fixe combien de chunks alimenteront la réponse.
Construire le prompt RAG
Section intitulée « Construire le prompt RAG »Le cœur du RAG : injecter les chunks récupérés dans le prompt, pour que le modèle réponde à partir d'eux et non de ses connaissances générales.
def construire_prompt(question: str, chunks: list[dict]) -> str: """Assemble le contexte récupéré et la question en un prompt.""" contexte = "\n".join(f"[{c['source']}] {c['texte']}" for c in chunks) return f"Contexte :\n{contexte}\n\nQuestion : {question}"Deux détails comptent. Chaque chunk est préfixé de sa source — [volumes.md] — pour que le modèle puisse, s'il le faut, attribuer l'information. Et le contexte précède la question : le modèle lit d'abord ce sur quoi il doit s'appuyer.
Générer la réponse sourcée
Section intitulée « Générer la réponse sourcée »Reste à appeler le modèle. Le prompt système est décisif : c'est lui qui interdit l'hallucination en cadrant strictement le modèle.
def repondre(question: str, k: int = 3) -> dict: """Récupère le contexte, génère une réponse fondée sur lui.""" chunks = rechercher(question, k) prompt = construire_prompt(question, chunks) reponse = CLIENT_OLLAMA.chat.completions.create( model="qwen2.5", messages=[ {"role": "system", "content": "Tu réponds en français, uniquement à partir du contexte " "fourni. Si le contexte ne suffit pas, dis-le."}, {"role": "user", "content": prompt}, ], ) return { "reponse": reponse.choices[0].message.content, "sources": sorted({c["source"] for c in chunks}), }La consigne « uniquement à partir du contexte » est la ligne la plus importante du RAG. Sans elle, le modèle complète avec ce qu'il « sait » — et invente. Avec elle, il s'en tient aux passages fournis, et reconnaît quand ils ne suffisent pas. On renvoie aussi la liste des sources : l'utilisateur peut remonter aux documents d'origine.
Question : Comment conserver les données d'un conteneur Docker ?Réponse : Pour conserver les données d'un conteneur Docker, on peututiliser un volume. Un volume est un stockage géré par Docker quipersiste indépendamment de la vie du conteneur...Sources : reseau.md, volumes.mdTester le pipeline de bout en bout
Section intitulée « Tester le pipeline de bout en bout »Un RAG se teste à deux niveaux. Les fonctions déterministes d'abord — le découpage, la construction du prompt — se vérifient sans modèle : un prompt doit bien contenir le contexte et la question. L'intégration ensuite : on indexe le corpus, on pose une question, et on vérifie que la réponse contient l'information attendue et cite la bonne source.
Ce double niveau est la bonne pratique : la logique pure est testée vite et de façon stable, l'enchaînement complet est validé par quelques tests d'intégration. Le lab de ce guide suit exactement ce découpage — deux tests déterministes, deux tests d'intégration.
Dépannage
Section intitulée « Dépannage »| Symptôme | Cause probable | Solution |
|---|---|---|
Connection refused sur 6333 | Qdrant non démarré | Lancer le conteneur qdrant/qdrant |
Erreur de dimension à l'upsert | Collection et modèle d'embedding divergent | Recréer la collection à la bonne dimension |
| Réponse hors contexte | Prompt système trop permissif | Imposer « uniquement à partir du contexte » |
| Le modèle ne trouve rien | k trop petit ou chunks mal découpés | Augmenter k, revoir le chunking |
| Réponse sans source | Payload non renvoyé | Passer with_payload=True à query_points |
À retenir
Section intitulée « À retenir »- Un RAG assemble les briques du parcours : découpe, embeddings, base vectorielle, génération.
- Qdrant est une base vectorielle serveur — partage, filtrage et montée en charge natifs.
- Un point Qdrant réunit un identifiant, un vecteur et un payload — ici le texte et sa source.
- On vectorise question et documents avec le même modèle ; les chunks alimentent le prompt.
- Le prompt système « uniquement à partir du contexte » est ce qui bride l'hallucination.
- Un RAG se teste à deux niveaux : logique pure déterministe, puis intégration complète.
Prochaines étapes
Section intitulée « Prochaines étapes »Pour aller plus loin
Section intitulée « Pour aller plus loin »- Documentation Qdrant — la référence de la base vectorielle.