Un agent IA sans mémoire long terme ne connaît que ce qu’on a écrit dans son prompt — vite saturé, vite périmé. Le RAG (Retrieval-Augmented Generation) change la donne : il donne à l’agent une mémoire consultable, où vivent vos documents métier — catalogue, tarifs, conditions, FAQ. Quand l’agent a besoin d’une information, il va la chercher par le sens, pas par mot-clé. Ce tutoriel monte cette mémoire entièrement en local, avec deux briques open-source : Ollama pour les embeddings et Qdrant pour le stockage vectoriel. Toutes les commandes sont vérifiées sur les documentations officielles.
C’est un satellite du cluster Agents IA pour PME : il construit la mémoire long terme dont les agents se serviront. Il prolonge aussi le cluster héberger ses LLM soi-même.
Le principe du RAG en cinq temps
Le RAG tient en une boucle simple. Découper : on fractionne ses documents en petits passages (chunks). Vectoriser : chaque passage est transformé en vecteur — une liste de nombres qui capture son sens — par un modèle d’embeddings. Stocker : ces vecteurs vont dans une base vectorielle (Qdrant). Récupérer : à une question, on vectorise la question et on cherche les passages les plus proches par le sens. Augmenter : on glisse ces passages dans le prompt du modèle, qui répond en s’appuyant dessus. L’agent « connaît » ainsi votre entreprise sans que tout soit réécrit dans son prompt — et la connaissance se met à jour en réindexant, pas en réentraînant.
Vectoriel ou mots-clés : la vraie différence
Pourquoi ne pas simplement chercher des mots-clés, comme un moteur classique ? Parce que le langage est plus riche que ses mots. Un client qui demande « c’est combien pour faire venir mes achats chez moi ? » ne tape ni « livraison », ni « tarif », ni « frais de port » — pourtant c’est exactement ce qu’il veut. Une recherche par mots-clés passerait à côté ; la recherche vectorielle, qui compare le sens, retrouve le bon passage. Les embeddings capturent cette proximité sémantique : deux formulations différentes d’une même idée produisent des vecteurs voisins. C’est ce qui rend un agent RAG capable de comprendre des questions formulées avec les mots du client, et non ceux de votre documentation.
Installer Qdrant
Qdrant se lance en un conteneur Docker, en exposant ses deux ports et un volume pour la persistance :
docker run -p 6333:6333 -p 6334:6334 -v "$(pwd)/qdrant_storage:/qdrant/storage:z" qdrant/qdrant
Le port 6333 sert l’API REST, le 6334 l’API gRPC. Une interface web de contrôle est disponible sur http://localhost:6333/dashboard — pratique pour inspecter ses collections. Le volume garantit que les vecteurs survivent à un redémarrage.
Générer des embeddings avec Ollama
Côté vectorisation, on tire d’abord un modèle d’embeddings dédié :
ollama pull nomic-embed-text
Ce modèle transforme un texte en vecteur via l’endpoint /api/embed :
curl http://localhost:11434/api/embed -d '{
"model": "nomic-embed-text",
"input": "Livraison gratuite à Dakar dès 50 000 FCFA."
}'
La réponse contient un champ embeddings : la représentation vectorielle du texte. Le modèle nomic-embed-text produit des vecteurs de 768 dimensions — un chiffre qu’il faudra redonner à Qdrant à la création de la collection. En Python, on encapsule l’appel :
import requests
def embed(textes):
r = requests.post(
"http://localhost:11434/api/embed",
json={"model": "nomic-embed-text", "input": textes},
)
return r.json()["embeddings"]
Créer une collection Qdrant
Une collection stocke des vecteurs de taille fixe, comparés selon une métrique de distance. Pour des embeddings, la similarité cosinus est le choix standard :
from qdrant_client import QdrantClient
from qdrant_client.models import VectorParams, Distance, PointStruct
client = QdrantClient(url="http://localhost:6333")
client.create_collection(
collection_name="connaissances",
vectors_config=VectorParams(size=768, distance=Distance.COSINE),
)
La taille 768 doit correspondre exactement à la dimension du modèle d’embeddings : un décalage ici, et Qdrant refusera les vecteurs. C’est l’erreur de débutant la plus fréquente.
Indexer ses documents
On vectorise ses passages et on les enregistre comme des « points », chacun avec son vecteur et une charge utile (payload) qui conserve le texte d’origine — indispensable pour relire le passage récupéré :
fragments = [
"Nos horaires : 9h-18h du lundi au samedi.",
"La livraison à Dakar est gratuite dès 50 000 FCFA.",
"Les retours sont acceptés sous 7 jours avec le reçu.",
]
vecteurs = embed(fragments)
points = [
PointStruct(id=i, vector=vecteurs[i], payload={"texte": fragments[i]})
for i in range(len(fragments))
]
client.upsert(collection_name="connaissances", points=points)
En vrai, on ne code pas ses fragments à la main : on lit ses fichiers (PDF, pages, notes), on les découpe, et on les indexe en lot. Mais le mécanisme est exactement celui-ci.
De vrais documents à de vrais fragments
En production, l’indexation part de fichiers réels : des PDF de catalogue, des pages de conditions, des notes internes. La chaîne est toujours la même : on extrait le texte de chaque fichier, on le découpe en fragments d’une taille raisonnable, on vectorise chaque fragment, on enregistre. Le découpage mérite attention : couper en plein milieu d’une phrase ou d’un tableau abîme le sens. On découpe plutôt par paragraphes ou par sections, avec un léger chevauchement entre fragments voisins pour qu’une information à cheval ne soit pas perdue. La qualité du RAG dépend autant de ce découpage que du modèle : un bon fragment est autonome, compréhensible seul.
Interroger la mémoire
Pour répondre à une question, on la vectorise avec le même modèle, puis on cherche les points les plus proches :
question = "Livrez-vous gratuitement à Dakar ?"
vecteur_question = embed([question])[0]
resultats = client.query_points(
collection_name="connaissances",
query=vecteur_question,
limit=3,
).points
contexte = "
".join(r.payload["texte"] for r in resultats)
Qdrant renvoie les passages triés par proximité de sens. Remarquez la force du procédé : la question parle de « gratuitement », le document de « gratuite dès 50 000 FCFA » — aucun mot-clé identique, mais la recherche vectorielle les rapproche parce qu’ils parlent de la même chose. C’est tout l’intérêt par rapport à une recherche textuelle classique.
Le score : ne garder que le pertinent
Chaque résultat de query_points porte un score de similarité. Tout l’enjeu est de ne pas injecter n’importe quoi dans le prompt : si la meilleure correspondance a un score faible, c’est probablement que la réponse n’existe pas dans votre base. On fixe alors un seuil en dessous duquel on considère qu’« on ne sait pas », plutôt que de nourrir le modèle de passages hors-sujet qui le pousseraient à inventer. Récupérer les trois meilleurs passages au-dessus d’un seuil de pertinence vaut mieux que d’en récupérer dix au hasard. Ce filtrage par score sépare un RAG fiable d’un RAG qui répond à côté.
Augmenter le prompt du modèle
Dernière étape : donner ce contexte au modèle de langage pour qu’il formule la réponse. On construit un prompt qui encadre la consigne et injecte les passages récupérés :
prompt = (
"Réponds à la question en t'appuyant uniquement sur le contexte.
"
"Contexte :
" + contexte + "
"
"Question : " + question
)
# ... envoyer ce prompt au modèle (Ollama) pour générer la réponse
Le modèle répond « Oui, la livraison est gratuite à Dakar à partir de 50 000 FCFA » — une réponse fondée sur vos données, pas sur ce que le modèle « croit » savoir. La consigne « uniquement sur le contexte » limite les hallucinations : si l’information n’est pas dans les passages, le modèle doit le dire plutôt qu’inventer.
Un mot sur la langue : nomic-embed-text gère le français correctement, mais pour un corpus exclusivement francophone, il vaut la peine de tester aussi des modèles d’embeddings multilingues et de comparer la pertinence sur vos propres questions. Le bon modèle d’embeddings est celui qui rapproche le mieux les formulations de vos clients de vos documents — cela se mesure, sur vos cas, pas en théorie.
Brancher le RAG sur un agent n8n
Dans un agent n8n, ce montage se reproduit sans tout coder : n8n propose un nœud de magasin vectoriel (Vector Store) compatible Qdrant et un Vector Store Question Answer Tool qu’on attache au nœud AI Agent. L’agent gagne alors un outil « consulter la base de connaissances » qu’il invoque de lui-même quand une question l’exige. On garde la même logique — embeddings Ollama, stockage Qdrant — mais l’orchestration devient visuelle, et la mémoire RAG se met au service de l’agent décrit dans le pilier.
Filtrer par métadonnées
Le payload ne sert pas qu’à conserver le texte : on y range aussi des métadonnées — la source du fragment, sa catégorie, sa date, sa langue. Qdrant sait alors restreindre la recherche à un sous-ensemble : ne chercher que dans les tarifs « en vigueur », ou que dans la documentation « française ». Cette combinaison de recherche sémantique et de filtre exact est puissante : elle évite qu’un agent ressorte un ancien tarif archivé au lieu du prix actuel. Plus le payload est riche, plus on cadre finement ce que l’agent a le droit de retrouver.
Garder la mémoire à jour
Une base de connaissances qui ment est pire que pas de base du tout. Quand un document change — un tarif, une condition, une procédure — il faut réindexer les fragments concernés. En pratique, on identifie les points à remplacer (par leur identifiant ou une métadonnée de source), on les supprime, et on réinsère les nouveaux. Pour des mises à jour fréquentes, on automatise ce cycle : un workflow qui réindexe la portion concernée à chaque modification d’un document source. Ainsi l’agent répond toujours sur la base de l’information courante, sans qu’on touche jamais au modèle.
Performance et dimensionnement
Bonne nouvelle pour une PME : Qdrant est sobre. Il indexe les vecteurs avec une structure (HNSW) qui rend la recherche rapide même sur de grandes collections, et tourne sans peine sur un serveur modeste — le gros du travail, c’est la génération des embeddings, assurée par Ollama. Pour quelques milliers à quelques dizaines de milliers de fragments — l’ordre de grandeur d’une base de connaissances d’entreprise — un VPS correct suffit largement. On ne se préoccupe d’optimisations avancées qu’à des volumes qu’une PME atteint rarement. Commencez simple ; Qdrant suivra.
Bonnes pratiques
- Soigner le découpage. Des fragments trop gros noient l’information ; trop petits, ils perdent le contexte. Quelques centaines de mots par passage, avec un léger chevauchement entre fragments voisins, est un bon point de départ.
- Garder le texte en payload. Sans le texte d’origine stocké à côté du vecteur, on récupère un identifiant sans contenu. Le payload est ce qu’on réinjecte dans le prompt.
- Réindexer, ne pas réentraîner. Une information change ? On met à jour les fragments concernés et on réindexe. La mémoire RAG est vivante, contrairement à un modèle figé.
- Filtrer par métadonnées. En enrichissant le payload (catégorie, date, langue), on peut restreindre la recherche — par exemple, ne chercher que dans les tarifs en vigueur.
- Un seul modèle d’embeddings. Questions et documents doivent être vectorisés par le même modèle, sinon les vecteurs ne sont pas comparables.
Pièges courants
- Dimension qui ne correspond pas. La taille de la collection doit égaler exactement celle du modèle d’embeddings (768 pour nomic-embed-text), sinon Qdrant rejette les vecteurs.
- Modèles d’embeddings mélangés. Indexer avec un modèle et interroger avec un autre produit des résultats incohérents : un seul modèle, pour tout.
- Payload oublié. Stocker le vecteur sans le texte, c’est récupérer des identifiants vides. Le texte voyage avec le vecteur.
- Fragments mal découpés. Des coupures brutales détruisent le sens ; on découpe par unités cohérentes avec chevauchement.
- Base jamais mise à jour. Un RAG figé ressort des informations périmées avec aplomb. La fraîcheur se maintient.
En résumé
- Le RAG donne à un agent une mémoire long terme : découper, vectoriser, stocker, récupérer, augmenter.
- Ollama génère les embeddings (
POST /api/embed, modèlenomic-embed-text, 768 dimensions) ; Qdrant les stocke et les recherche (create_collection,upsert,query_points). - On garde le texte d’origine en payload pour le réinjecter dans le prompt du modèle, avec la consigne de s’appuyer uniquement sur le contexte.
- Dans n8n, un Vector Store QA Tool branche cette mémoire sur l’agent, sans tout recoder.
Aucun commentaire pour l'instant — lancez la discussion !