RAG en local avec ChromaDB et Ollama : tutoriel Python
Faire du rag chromadb ollama python en local, c'est trois briques qui s'imbriquent : un vector store qui persiste sur disque (ChromaDB), un modèle d'embeddings qui transforme vos chunks en vecteurs (nomic-embed-text via Ollama), et un LLM de chat qui répond en s'appuyant sur les passages retrouvés. Pas de clé d'API, pas de fuite de données. Ce guide vous fait passer d'un PDF brut à un chatbot qui cite ses sources en 22 minutes.
#Pourquoi cette stack pour un RAG local
Beaucoup de tutos RAG commencent par LangChain ou LlamaIndex. Ces frameworks sont puissants mais cachent ce qui se passe sous le capot. Ici, on écrit le pipeline à la main avec trois dépendances seulement. Vous comprendrez chaque étape, et vous saurez quoi optimiser plus tard.
- ChromaDB
- Vector store open source, pur Python, mode persistant intégré (SQLite + index HNSW). Pas de serveur à lancer.
- Ollama
- Sert à la fois le modèle d'embeddings (nomic-embed-text) et le LLM de chat (Mistral, Qwen, Llama). Endpoint HTTP unique sur localhost:11434.
- Python natif
- Quelques fonctions, pas de framework. Vous pourrez brancher LangChain plus tard si besoin, mais ce n'est pas nécessaire pour démarrer.
#Prérequis
- Python 3.10+
- ChromaDB demande 3.10 minimum. Vérifiez avec python --version.
- Ollama installé et lancé
- Le daemon écoute par défaut sur http://localhost:11434. Si vous partez de zéro, suivez d'abord le guide d'installation Ollama.
- 8 Go de RAM
- 16 Go confortables. Le modèle de chat 7B en Q4 prend ~5 Go, le modèle d'embeddings ~300 Mo.
- Un GPU n'est pas obligatoire
- L'inférence CPU fonctionne, juste plus lente. Pour l'ingestion d'un gros corpus, un GPU 6 Go+ accélère beaucoup les embeddings.
#1. Installer ChromaDB et préparer Ollama
On crée un environnement virtuel propre, on installe les trois libs nécessaires, et on télécharge les modèles côté Ollama.
Trois paquets : chromadb pour le vector store, ollama pour le client Python officiel, pypdf pour lire les PDF. C'est tout.
nomic-embed-text est un modèle d'embeddings de 137M paramètres, multilingue, qui produit des vecteurs de dimension 768. Léger, rapide, bon en français. Mistral 7B sert au chat final. Vous pouvez le remplacer par qwen2.5:7b ou llama3.1:8b sans rien changer au code.
#2. Configurer le modèle d'embeddings
Un embedding, c'est un vecteur qui représente le sens d'un texte. Deux textes proches sémantiquement ont des vecteurs proches. C'est le moteur du RAG : on cherche les chunks dont l'embedding ressemble le plus à celui de la question.
Vous devriez voir Dimension du vecteur : 768. Si ça plante avec model not found, c'est que ollama pull nomic-embed-text n'a pas été fait.
#3. Ingestion de PDF en français
L'ingestion fait trois choses : lire les pages d'un PDF, découper le texte en chunks de taille raisonnable, et stocker chaque chunk avec son embedding dans ChromaDB en mode persistant.
Le chunker découpe en blocs de 800 caractères avec 100 de chevauchement. C'est un point de départ : ni trop petit (manque de contexte) ni trop gros (dilue le signal). Pour du contenu juridique très dense, descendez à 500. Pour des manuels techniques aérés, montez à 1200.
Lancez l'ingestion sur un dossier ./pdfs/ contenant vos documents :
#4. Recherche top-k dans ChromaDB
Une fois les chunks indexés, la recherche consiste à embedder la question puis demander à Chroma les k vecteurs les plus proches en distance cosinus. C'est instantané, même sur 100 000 chunks.
k=4 est un bon défaut. Trop petit, vous ratez du contexte pertinent ; trop grand, vous noyez le LLM dans du bruit et explosez la fenêtre de contexte. Pour des questions très précises, k=2 suffit. Pour des questions transversales, montez à 6.
#5. Boucle de chat avec citations
Maintenant on assemble : on cherche les chunks pertinents, on construit un prompt avec le contexte, on envoie à Mistral via Ollama, et on demande au modèle de citer ses sources.
Trois détails qui comptent. Premièrement, temperature=0.2 : on veut une réponse factuelle, pas créative. Deuxièmement, num_ctx=8192 : la fenêtre par défaut d'Ollama (2048) est trop courte une fois qu'on injecte 4 chunks de 800 caractères. Troisièmement, le system prompt force le modèle à dire 'je ne sais pas' plutôt qu'hallucinier — c'est le principal garde-fou anti-hallucination du RAG.
#6. Exemple concret : chatbot juridique sur des contrats
Imaginons un cabinet qui veut interroger 200 contrats de prestation en PDF. Avec la stack ci-dessus, en moins d'une heure on a un assistant qui répond à des questions du type :
- Question typique
- « Quels contrats incluent une clause de non-concurrence post-rupture supérieure à 12 mois ? »
- Ce qui se passe
- L'embedding de la question retrouve les chunks qui contiennent les mots-clés sémantiquement proches (non-concurrence, post-rupture, durée). Mistral lit ces 4 passages et répond avec les noms de fichiers concernés.
- Garantie de confidentialité
- Aucune donnée ne sort du poste. Pas de clé d'API. Aucune télémétrie. C'est ce qui distingue un RAG local d'un wrapper OpenAI.
#Dépannage
- ChromaDB lent à l'ingestion
- Le goulot est presque toujours l'appel embeddings vers Ollama. Vérifiez que nomic-embed-text tourne sur GPU avec ollama ps. Sur CPU, comptez ~50 chunks/seconde ; sur GPU, ~500.
- « model not found »
- Ollama ne trouve pas nomic-embed-text. Relancez ollama pull nomic-embed-text et vérifiez avec ollama list.
- Réponses qui inventent des sources
- Mistral 7B hallucine encore parfois. Passez à qwen2.5:14b ou mistral-small:24b si vous avez la VRAM. Ou ajoutez un reranker (cross-encoder) après ChromaDB pour filtrer les faux positifs.
- Embeddings de mauvaise qualité en FR
- nomic-embed-text est multilingue mais pas optimal en français pur. Pour du contenu juridique ou médical, testez Solon-embeddings-large-0.1 ou bge-m3 (à charger via sentence-transformers, hors Ollama).
- ChromaDB grossit sans limite
- Chaque réindexation ajoute des doublons. Avant de réingérer un PDF, faites collection.delete(where={"source": name}) pour purger les anciens chunks.
#Pour aller plus loin
Vous avez un RAG fonctionnel. Voici les chantiers naturels pour le pousser plus loin :
- Comparer les modèles d'embeddings FR
- Notre guide « Les meilleurs modèles d'embeddings FR » compare BGE, E5, Solon et nomic sur du contenu francophone.
- Améliorer le chunking
- « Stratégies de chunking » détaille le chunking sémantique, par titres markdown, ou par paragraphes — souvent ce qui débloque le plus de précision.
- Ajouter un reranker
- « Ajouter un reranker à son pipeline » : +15% de pertinence en plaçant un cross-encoder après Chroma. Le pas suivant logique.
- Recherche hybride
- « Recherche hybride BM25 + vectoriel » combine lexical et sémantique, indispensable dès qu'il y a beaucoup de jargon ou de noms propres.
Un retour, une erreur, une précision ? Faites-nous signe, ça améliore le guide pour tout le monde.