RAG locale con ChromaDB e Ollama : tutorial Python
Realizzare un RAG in locale con ChromaDB, Ollama e Python significa combinare tre componenti: un vector store che conserva i dati su disco (ChromaDB), un modello di embedding che trasforma i tuoi chunk in vettori (nomic-embed-text tramite Ollama) e un LLM di chat che risponde basandosi sui passaggi recuperati. Nessuna chiave API, nessuna fuga di dati. Questa guida ti porta da un PDF grezzo a un chatbot che cita le sue fonti in 22 minuti.
#Perché questo stack per un RAG locale
Molti tutorial sul RAG iniziano con LangChain o LlamaIndex. Questi framework sono potenti ma nascondono ciò che avviene dietro le quinte. Qui scriviamo la pipeline a mano con solo tre dipendenze. Capirai ogni passaggio e saprai cosa ottimizzare in seguito.
- ChromaDB
- Vector store open source, interamente in Python, modalità persistente integrata (SQLite + indice HNSW). Nessun server da avviare.
- Ollama
- Serve sia il modello di embedding (nomic-embed-text) che il LLM di chat (Qwen 3.5, Granite 4.2, Gemma 4). Endpoint HTTP unico su localhost:11434.
- Python nativo
- Alcune funzioni, nessun framework. Potrai collegare LangChain in seguito se necessario, ma non è necessario per iniziare.
#Prerequisiti
Questa guida ti porta al modello. Il kit ti porta al copilota che scrive codice nel tuo editor.
- Spazio online a vita
- PDF + file
- Aggiornamenti a vita
- Python 3.10+
- ChromaDB richiede almeno 3.10. Verifica con python --version.
- Ollama installato e avviato
- Il daemon ascolta di default su http://localhost:11434. Se inizi da zero, segui prima la guida di installazione di Ollama.
- 8 GB di RAM
- 16 GB comodi. Il modello di chat 9B in Q4 occupa circa 6 GB, il modello di embedding circa 300 MB.
- Una GPU non è obbligatoria
- L'inferenza su CPU funziona, è solo più lenta. Per l'ingestione di un corpus di grandi dimensioni, una GPU da almeno 6 GB accelera molto il calcolo degli embedding.
#1. Installare ChromaDB e preparare Ollama
Si crea un ambiente virtuale pulito, si installano le tre librerie necessarie e si scaricano i modelli tramite Ollama.
Tre pacchetti: chromadb per lo store vettoriale, ollama per il client Python ufficiale, pypdf per leggere i PDF. È tutto.
nomic-embed-text è un modello di embedding da 137M parametri, multilingue, che genera vettori di dimensione 768. Leggero, veloce, buono in francese. Qwen 3.5 9B (6,6 GB, 256k di contesto, multilingue, Apache 2.0) serve per la chat finale: è la scelta predefinita per 8 GB nel 2026. Puoi sostituirlo con granite4.2:8b (più parsimonioso) o gemma4:12b senza cambiare nulla nel codice.
#2. Configurare il modello di embeddings
Un embedding è un vettore che rappresenta il senso di un testo. Due testi semanticamente vicini hanno vettori vicini. È il motore del RAG: si cercano i chunk il cui embedding si avvicina di più a quello della domanda.
Dovresti vedere Dimensione del vettore: 768. Se l'esecuzione fallisce con model not found, significa che ollama pull nomic-embed-text non è stato eseguito.
#3. Acquisizione di PDF in francese
L'ingestione fa tre cose: legge le pagine di un PDF, taglia il testo in pezzi di dimensione ragionevole e salva ogni pezzo insieme al suo embedding in ChromaDB in modalità persistente.
Il chunker suddivide il testo in blocchi di 800 caratteri con una sovrapposizione di 100 caratteri. È un punto di partenza: blocchi né troppo piccoli (contesto insufficiente) né troppo grandi (il segnale si diluisce). Per contenuti giuridici molto densi, scendi a 500. Per manuali tecnici con un'impaginazione più ariosa, sali a 1200.
Avvia l'ingestione su una cartella ./pdfs/ contenente i tuoi documenti:
#4. Ricerca top-k in ChromaDB
Una volta indicizzati i chunk, la ricerca consiste nel generare l'embedding della domanda e poi chiedere a Chroma i k vettori più vicini in base alla distanza coseno. È istantaneo, anche su 100.000 chunk.
k=4 è un buon valore predefinito. Se è troppo basso, perdi parte del contesto pertinente; se è troppo alto, sommergi il LLM di rumore e superi la capacità della finestra di contesto. Per domande molto precise, k=2 basta. Per domande trasversali, aumenta il valore a 6.
#5. Ciclo di chat con citazioni
Ora mettiamo insieme i vari elementi: cerchiamo i chunk pertinenti, costruiamo un prompt con il contesto, lo inviamo a Qwen 3.5 tramite Ollama e chiediamo al modello di citare le sue fonti.
Tre dettagli che contano. Primo, temperature=0.2: vogliamo una risposta fattuale, non creativa. Secondo, num_ctx=8192: la finestra predefinita di Ollama (2048) è troppo corta quando si inseriscono 4 chunk di 800 caratteri. Terzo, il system prompt costringe il modello a dire 'non so' piuttosto che a produrre allucinazioni: è la principale misura di protezione contro le allucinazioni nel RAG.
#6. Esempio concreto: chatbot giuridico su contratti
Immaginiamo uno studio che voglia interrogare 200 contratti di prestazione di servizi in PDF. Con lo stack descritto sopra, in meno di un'ora si ha un assistente che risponde a domande del tipo:
- Domanda tipica
- «Quali contratti prevedono una clausola di non concorrenza dopo la cessazione del rapporto, di durata superiore a 12 mesi?»
- Cosa succede
- L'embedding della domanda individua i chunk che contengono parole chiave semanticamente vicine (non concorrenza, dopo la cessazione del rapporto, durata). Qwen 3.5 legge questi 4 passaggi e risponde con i nomi dei file interessati.
- Garanzia di confidenzialità
- Nessun dato esce dal computer. Nessuna chiave API. Nessuna telemetria. Questo è ciò che distingue un RAG locale da un wrapper OpenAI.
#Risoluzione dei problemi
- ChromaDB lento nell'ingestione
- Il collo di bottiglia è quasi sempre la chiamata a Ollama per gli embedding. Verifica che nomic-embed-text sia in esecuzione su GPU con ollama ps. Su CPU, calcola circa 50 chunk al secondo; su GPU, circa 500.
- « model not found »
- Ollama non trova nomic-embed-text. Esegui di nuovo ollama pull nomic-embed-text e verifica con ollama list.
- Risposte che inventano fonti
- Un modello 9B produce ancora talvolta allucinazioni. Passa a mistral-small (24B, ~14 GB, molto buono in francese) o a qwen3.8:27b se hai abbastanza VRAM. Oppure aggiungi un reranker (cross-encoder) dopo ChromaDB per filtrare i falsi positivi.
- Embedding di scarsa qualità in francese
- nomic-embed-text è multilingue, ma non è ottimale per contenuti esclusivamente in francese. Per contenuti giuridici o medici, prova Solon-embeddings-large-0.1 o bge-m3 (da caricare tramite sentence-transformers, al di fuori di Ollama).
- ChromaDB cresce senza limiti
- Ogni reindicizzazione aggiunge duplicati. Prima di acquisire nuovamente un PDF, esegui collection.delete(where={"source": name}) per eliminare i vecchi chunk.
#Per approfondire
Hai un RAG funzionante. Ecco i prossimi interventi per svilupparlo ulteriormente:
- Confrontare i modelli di embedding FR
- La nostra guida «I migliori modelli di embedding FR» confronta BGE, E5, Solon e nomic su contenuti in lingua francese.
- Migliorare il chunking
- «Strategie di chunking» illustra in dettaglio il chunking semantico, per titoli markdown o per paragrafi: spesso è ciò che permette di ottenere il maggiore miglioramento della precisione.
- Aggiungere un reranker
- «Aggiungere un reranker alla propria pipeline»: +15% di pertinenza inserendo un cross-encoder dopo Chroma. Il passo successivo logico.
- Ricerca ibrida
- «Ricerca ibrida BM25 + vettoriale» combina ricerca lessicale e semantica ed è indispensabile quando ci sono molti termini specialistici o nomi propri.
Un feedback, un errore, una precisazione? Facci sapere, così la guida migliora per tutti.