Beaucoup de développeurs se lancent dans la construction d’un chatbot basé sur la récupération augmentée de génération — le fameux RAG (Retrieval-Augmented Generation) — en empilant des bibliothèques sans vraiment comprendre ce qui se passe sous le capot. Résultat : des réponses hors contexte, des latences inexplicables, et une base de connaissances qui ne scale pas. Construire un chatbot RAG solide en Python avec LangChain et Chroma, c’est avant tout une question d’architecture réfléchie avant d’être une question de code.
Qu’est-ce qu’un chatbot RAG et pourquoi LangChain + Chroma ?
Un chatbot RAG ne se contente pas de s’appuyer sur les connaissances figées d’un modèle de langage. Il interroge dynamiquement une base de documents pour injecter du contexte pertinent dans le prompt envoyé au LLM. C’est la différence entre un assistant qui hallucine une réponse inventée et un assistant qui vous cite un extrait de votre documentation interne.
LangChain s’est imposé comme le framework d’orchestration de référence dans l’écosystème Python pour connecter LLMs, retrievers, mémoire conversationnelle et chaînes de traitement. Chroma, de son côté, est une base de données vectorielle open source, légère, qui tourne en local ou en mode serveur, particulièrement adaptée aux projets qui démarrent ou aux prototypes à déployer rapidement. Pour une PME française souhaitant indexer ses 500 pages de documentation technique sans passer par un fournisseur cloud, c’est une combinaison imbattable en termes de rapport coût/efficacité.
À noter que LangChain a connu une refonte majeure de son API avec l’introduction de langchain-core et des interfaces LCEL (LangChain Expression Language). Oubliez les anciens tutoriels basés sur ConversationalRetrievalChain : la bonne pratique aujourd’hui passe par les runnables et la composition explicite des chaînes.
Mise en place de l’environnement et ingestion des documents
Installation et dépendances essentielles
Commencez par créer un environnement virtuel propre. Les dépendances minimales pour un chatbot RAG opérationnel sont les suivantes :
pip install langchain langchain-community langchain-openai chromadb tiktoken pypdf
Si vous utilisez un modèle local via Ollama (ce que je recommande pour les environnements sans accès à l’API OpenAI ou pour des raisons de confidentialité des données), remplacez langchain-openai par langchain-ollama. La CNIL française est formelle sur la localisation des données traitées par des IA dans certains secteurs : cette alternative locale n’est pas qu’un choix technique, c’est parfois une obligation réglementaire.
Chargement et découpage des documents (chunking)
L’étape de chunking est sous-estimée et pourtant déterminante. Un mauvais découpage détruira la qualité de la récupération, peu importe la qualité de votre LLM. La règle empirique issue des benchmarks de la communauté LangChain : des chunks de 512 tokens avec un chevauchement (overlap) de 50 tokens offrent un bon équilibre pour la majorité des documents techniques en prose.
from langchain_community.document_loaders import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
loader = PyPDFLoader("documentation.pdf")
documents = loader.load()
splitter = RecursiveCharacterTextSplitter(
chunk_size=512,
chunk_overlap=50,
separators=["\n\n", "\n", " ", ""]
)
chunks = splitter.split_documents(documents)
Privilégiez le RecursiveCharacterTextSplitter au découpage par caractère fixe : il respecte la hiérarchie naturelle du texte (paragraphes, puis lignes, puis mots). Pour des documents structurés comme du code ou du Markdown, orientez-vous vers le MarkdownHeaderTextSplitter ou le Language splitter dédié.
Indexation vectorielle avec Chroma et construction du retriever
Une fois vos documents découpés, il faut les transformer en vecteurs (embeddings) et les stocker dans Chroma. Le choix du modèle d’embedding est critique : text-embedding-3-small d’OpenAI est un excellent compromis qualité/coût pour la langue française. Si vous voulez rester 100% local, le modèle nomic-embed-text via Ollama donne des résultats très corrects sur des corpus francophones.
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
vectorstore = Chroma.from_documents(
documents=chunks,
embedding=embeddings,
persist_directory="./chroma_db"
)
retriever = vectorstore.as_retriever(
search_type="mmr",
search_kwargs={"k": 5, "fetch_k": 20}
)
Un conseil d’expérience : utilisez le search_type="mmr" (Maximal Marginal Relevance) plutôt que la similarité cosinus classique. L’MMR pénalise les documents trop redondants entre eux dans les résultats retournés, ce qui améliore sensiblement la diversité et la pertinence du contexte injecté dans le prompt. Sur des corpus internes avec beaucoup de répétitions (FAQs, documentations versionnées), la différence de qualité des réponses finales est visible à l’œil nu.
Pour approfondir les considérations de sécurité autour de l’exposition d’une API RAG en production, consultez notre article sur les erreurs courantes en sécurité des API et comment les éviter — les endpoints d’inférence sont une surface d’attaque souvent négligée.
Construction de la chaîne RAG conversationnelle avec LCEL
C’est ici que la plupart des tutoriels échouent : ils construisent un retriever mais oublient la mémoire conversationnelle. Un chatbot sans historique, c’est un moteur de recherche glorifié. Avec LCEL, la composition est explicite et lisible :
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
prompt = ChatPromptTemplate.from_messages([
("system", """Tu es un assistant expert. Réponds uniquement à partir du contexte fourni.
Contexte : {context}"""),
MessagesPlaceholder(variable_name="history"),
("human", "{question}")
])
def format_docs(docs):
return "\n\n".join(doc.page_content for doc in docs)
chain = (
{"context": retriever | format_docs, "question": RunnablePassthrough(), "history": RunnablePassthrough()}
| prompt
| llm
| StrOutputParser()
)
store = {}
def get_session_history(session_id: str):
if session_id not in store:
store[session_id] = ChatMessageHistory()
return store[session_id]
chain_with_history = RunnableWithMessageHistory(
chain,
get_session_history,
input_messages_key="question",
history_messages_key="history"
)
Pour invoquer le chatbot et maintenir la continuité de session :
response = chain_with_history.invoke(
{"question": "Quelles sont les conditions de garantie ?"},
config={"configurable": {"session_id": "user_123"}}
)
print(response)
Ce pattern de gestion de session par session_id est directement intégrable dans une API FastAPI. Un exemple concret : une fintech parisienne avec laquelle j’ai travaillé a déployé exactement cette architecture pour un assistant interne indexant ses 2 000 pages de documentation réglementaire ACPR. Le temps de réponse moyen tourne autour de 1,8 secondes avec GPT-4o-mini, ce qui est acceptable pour un usage interne.
Si vous souhaitez comprendre comment les modèles génèrent du contenu à partir de représentations vectorielles, notre guide sur le fonctionnement de la génération par diffusion offre un éclairage technique complémentaire sur les mécanismes sous-jacents aux modèles génératifs.
Optimisation, évaluation et mise en production
Un chatbot RAG ne s’arrête pas au prototype. Trois points sont non-négociables avant toute mise en production :
- Évaluation du retriever : mesurez le recall@k de votre retriever sur un jeu de questions-réponses annotées manuellement. Des outils comme RAGAS (framework d’évaluation RAG open source) permettent de mesurer automatiquement la fidélité des réponses et la pertinence du contexte récupéré.
- Gestion de la fenêtre de contexte : au-delà de 5 à 7 exchanges, l’historique conversationnel peut dépasser la fenêtre de contexte du LLM. Implémentez une stratégie de résumé automatique (
ConversationSummaryBufferMemory) ou limitez la fenêtre glissante. - Re-ranking : pour les corpus volumineux (+10 000 chunks), ajoutez un cross-encoder de re-ranking après la récupération initiale.
FlashrankRerankest une solution légère et efficace intégrable directement dans LangChain.
Sur la question du déploiement, la conteneurisation de votre stack RAG (app Python + Chroma en mode serveur) est la voie à privilégier. Notre analyse sur la conteneurisation avec Docker et les nouvelles pratiques couvre les patterns d’isolation et de gestion des volumes persistants directement applicables à ce cas d’usage.
Mon verdict : le RAG n’est pas une solution universelle
Construire un chatbot RAG avec LangChain et Chroma est aujourd’hui à la portée d’un développeur Python intermédiaire en moins d’une journée. Mais attention à l’illusion du prototype réussi : la qualité d’un système RAG en production se mesure à la rigueur de l’ingestion documentaire, à la pertinence du chunking et à l’existence d’une boucle d’évaluation continue. Sans ces fondations, vous obtiendrez un chatbot qui impressionne en démo et déçoit en usage réel.
Ma recommandation ferme : avant de brancher un LLM, passez 80% de votre temps sur la qualité de votre pipeline d’ingestion et la pertinence de votre retriever. Le LLM, lui, fera son travail — à condition qu’on lui donne le bon contexte.
Quelle est la différence entre un chatbot RAG et un fine-tuning de LLM ?
Le fine-tuning modifie les poids du modèle pour lui inculquer des connaissances durables, ce qui est coûteux en données annotées et en calcul. Le RAG, lui, injecte dynamiquement du contexte au moment de l’inférence sans modifier le modèle. Pour la majorité des cas d’usage en entreprise — documentation interne, support client, base de connaissances évolutive — le RAG est à privilégier : il est plus économique, plus facile à mettre à jour et offre une meilleure traçabilité des sources.
Peut-on utiliser un LLM entièrement local avec cette architecture RAG ?
Oui, et c’est même fortement conseillé pour les données sensibles. La combinaison Ollama (pour les modèles comme Mistral, LLaMA 3 ou Gemma) + Chroma en local + LangChain permet de construire un chatbot RAG 100% hors cloud. Les performances dépendent du matériel disponible, mais sur une machine équipée d’un GPU NVIDIA de 8 Go de VRAM, des modèles à 7 milliards de paramètres offrent des résultats exploitables avec une latence de 2 à 4 secondes par requête.
Chroma est-il adapté à un usage en production à grande échelle ?
Chroma convient très bien pour des corpus allant jusqu’à quelques centaines de milliers de documents. Au-delà, ou pour des besoins de haute disponibilité et de scalabilité horizontale, des solutions comme Qdrant, Weaviate ou Pinecone seront plus adaptées. L’avantage de LangChain est que le passage d’un vectorstore à un autre ne nécessite souvent que de changer quelques lignes de code, grâce à l’interface unifiée des retrievers.




