RAG PDF entièrement localisé avec LangChain, Ollama et Chroma
Une application PDF RAG entièrement locale : LangChain ingère les fichiers, Ollama les intègre, Chroma stocke les vecteurs, et les réponses fonctionnent hors ligne sans aucune clé API cloud.
Sources: Implement RAG with Langchain and local Ollama models
Créez un pipeline RAG entièrement local pour les PDF à l’aide de LangChain, Ollama et ChromaDB — discutez avec des documents sans recourir à des API cloud ni laisser les données quitter l’appareil.
Pourquoi le local est important
Les LLM cloud sont pratiques, mais chaque requête fait transiter du texte par le réseau d’un tiers. Les contrats juridiques, les dossiers médicaux et les rapports internes ne peuvent souvent pas accepter ce compromis. Une stack locale élimine ces fuites tout en permettant des questions-réponses en langage naturel sur vos propres fichiers.
Ce que vous obtenez
- Intégrer des fichiers PDF dans un stockage vectoriel persistant
- Répondre aux questions en langage naturel basées sur ces documents
- Fonctionner hors ligne au moment de l’inférence — aucune clé API requise une fois les modèles téléchargés
Stack : LangChain pour l’orchestration, Ollama pour les conversations locales et les embeddings, ChromaDB en tant que stockage vectoriel.
Architecture
Voie d’ingestion : les octets PDF entrent dans un chargeur basé sur PyPDF, sont divisés en tronçons, puis Ollama insère chaque tronçon dans Chroma.
Voie de demande : une question est envoyée au système de récupération, les tronçons correspondants deviennent le contexte, et le modèle de conversation local Ollama génère la réponse.
Deux phases permettent de maintenir des coûts maîtrisés :
- Ingestion — une fois (ou lorsque les documents changent) : analyse, segmentation en tronçons, insertion d’embeddings, stockage
- Récupération — à chaque requête : les meilleurs tronçons deviennent le contexte pour le LLM
Vous payez les coûts d’insertion d’embeddings lorsque le contenu change ; les requêtes interactives restent rapides.
Prérequis
Installez Ollama et téléchargez les modèles utilisés ici :
ollama pull embeddinggemma:latest # embedding model
ollama pull gemma4:latest # chat model
gemma4 a été optimisé pour des instructions spécifiques. Pour une empreinte plus légère, essayez gemma2:2b ; pour un raisonnement plus puissant, utilisez gemma4:27b — mettez à jour les chaînes de configuration en conséquence.
Mise en place du projet
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
Packages clés issus des exigences :
langchain
langchain-chroma
langchain-community
langchain-ollama
langchain-text-splitters
pypdf
chromadb
python-dotenv
Surchargez les paramètres par défaut via l’environnement sans modifier le code :
OLLAMA_BASE_URL=http://localhost:11434
OLLAMA_MODEL=gemma4:latest
OLLAMA_EMBED_MODEL=embeddinggemma:latest
Tuyau d’ingestion (src/ingest.py)
Chargement des documents
def load_documents(pdf_path: str | Path) -> list:
path = Path(pdf_path)
loader = (
PyPDFLoader(str(path))
if path.is_file()
else PyPDFDirectoryLoader(str(path))
)
return loader.load()py
Un outil d’aide gère soit un fichier unique, soit un répertoire. Les chargeurs LangChain renvoient toujours une liste uniforme de Document, quel que soit le cas.
Départage en blocs
def split_documents(documents: list) -> list:
splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200,
)
return splitter.split_documents(documents)
RecursiveCharacterTextSplitter privilégie d’abord la segmentation par paragraphes, puis par phrases, et enfin par mots afin de préserver les limites sémantiques. Une superposition de 200 caractères permet à une phrase se situant à la frontière entre deux segments de rester présente dans les deux parties voisines, ce qui réduit les erreurs de récupération aux extrémités.
Intégration et persistance
def build_vector_store(chunks: list) -> Chroma:
embeddings = OllamaEmbeddings(model=EMBEDDING_MODEL, base_url=OLLAMA_BASE_URL)
VECTOR_STORE_DIR.mkdir(parents=True, exist_ok=True)
return Chroma.from_documents(
documents=chunks,
embedding=embeddings,
collection_name=COLLECTION_NAME,
persist_directory=str(VECTOR_STORE_DIR),
)
Ollama sert les embeddings localement sur le port 11434. Chroma conserve ces données sous vector_store/ (fichiers SQLite ainsi que fichiers d’index HNSW) afin que la redémarrage du processus ne force pas une nouvelle génération complète des embeddings.
Chaîne de récupération (src/retrieval.py)
LangChain LCEL assemble les étapes à l’aide de l’opérateur |.
Modèle de prompt
RAG_PROMPT = ChatPromptTemplate.from_messages([
(
"system",
"You are an assistant that answers questions based on the provided context "
"from PDF documents. Use only the information in the context to answer. "
"If the answer is not in the context, say so clearly.\n\nContext:\n{context}"
),
("human", "{question}"),
])
Le message système relie les réponses au contexte récupéré — c’est la principale mesure de prévention des hallucinations issues uniquement du préentraînement.
Construction de la chaîne
def build_rag_chain(k: int = 4):
store = load_vector_store()
retriever = store.as_retriever(search_kwargs={"k": k})
llm = ChatOllama(model=MODEL_NAME, base_url=OLLAMA_BASE_URL)
return (
{"context": retriever | _format_docs, "question": RunnablePassthrough()}
| RAG_PROMPT
| llm
| StrOutputParser()
)
De gauche à droite : le retriever sélectionne les k fragments les plus similaires en se basant sur la similarité des embeddings, les outils d’aide formatent ces fragments en une seule chaîne de contexte, le modèle de prompt fusionne ce contexte avec la question de l’utilisateur, et ChatOllama génère la réponse.
Boucle de demande interactive
# Index a directory of PDFs (default: data/pdfs/)
python main.py ingest
k réglable afin de pouvoir équilibrer la taille du contexte et le temps de réponse.
Exécution de bout en bout
# Index a specific file
python main.py ingest --path /path/to/contract.pdf
# Ask a one-off question
python main.py query "What are the payment terms?"
# Interactive session
python main.py chat
Pointez l’outil vers votre dossier PDF, vérifiez que le répertoire du stockage vectoriel est rempli, puis effectuez des requêtes. Tout — embeddings, vecteurs, génération — reste sur le localhost.
En résumé
Le RAG local n’est plus une nouveauté. Ollama fournit les modèles, Chroma gère les vecteurs, et LangChain connecte le graphe. Pour des corpus privés, ce trio suffit pour déployer un assistant documentaire utile sans frais de cloud ni flux de données vers l’extérieur.