RAG sta per Retrieval-Augmented Generation: invece di sperare che un modello «sappia» la risposta, gli si forniscono i pezzi giusti dei tuoi documenti al momento della domanda. In questa guida costruiamo un sistema RAG che gira interamente sul tuo computer: nessun dato esce, nessun abbonamento, nessuna API a pagamento. Potrai interrogare contratti, manuali, dispense o report in PDF e ottenere risposte con tanto di citazione della fonte.
A chi serve e cosa otterrai
È pensata per chi ha una base di documenti riservati (avvocati, consulenti, studenti, PMI, PA) e vuole un assistente che risponda solo in base a quei testi, in locale. Al termine avrai uno script Python che: legge una cartella di PDF, li spezza in frammenti, li trasforma in vettori (embedding), li salva in un database vettoriale e risponde alle domande citando i passaggi usati.
Prerequisiti reali: un computer con almeno 8-16 GB di RAM (Windows, macOS o Linux), Python 3.10+ installato, e circa 5 GB liberi per i modelli. Non serve una GPU: funziona anche su CPU, solo più lentamente.
Quali strumenti usiamo e perché
- Ollama — per far girare in locale sia il modello di linguaggio sia quello di embedding. È gratuito, semplice e multipiattaforma. È la nostra prima scelta perché elimina ogni complessità di configurazione.
- nomic-embed-text — modello di embedding leggero ed efficace, scaricabile via Ollama.
- llama3.2 (o qwen2.5) — il modello che genera la risposta. Sceglieremo una taglia piccola per restare leggeri; chi ha più RAM può salire.
- ChromaDB — database vettoriale locale, persistente, zero configurazione. Alternative valide: FAISS (più veloce ma senza persistenza integrata) o LanceDB.
- pypdf — per estrarre il testo dai PDF.
Perché non LangChain? Si può usare, ma per capire davvero cosa succede preferiamo codice esplicito e poche dipendenze. Aggiungere LangChain o LlamaIndex è una variante che vedremo alla fine.
Passo 1 — Installare Ollama e i modelli
Scarica Ollama dal sito ufficiale (ollama.com) e installalo. Poi, da terminale, scarica i due modelli che ci servono:
ollama pull nomic-embed-text
ollama pull llama3.2
Verifica che Ollama risponda:
ollama run llama3.2 "Dimmi ciao in italiano"
Se ottieni un saluto, sei pronto. Ollama espone in automatico un server locale su http://localhost:11434.
Passo 2 — Creare l'ambiente Python
python -m venv .venv
# Windows: .venv\Scripts\activate
source .venv/bin/activate
pip install ollama chromadb pypdf
Crea una cartella documenti/ e mettici dentro i PDF da interrogare.
Passo 3 — Indicizzare i PDF
Questo script legge i PDF, li divide in frammenti con sovrapposizione (per non spezzare le frasi a metà), calcola gli embedding con Ollama e li salva in ChromaDB.
import os, glob
import chromadb
from pypdf import PdfReader
import ollama
client = chromadb.PersistentClient(path="./chroma_db")
coll = client.get_or_create_collection("documenti")
def chunk(text, size=900, overlap=150):
out, i = [], 0
while i < len(text):
out.append(text[i:i+size])
i += size - overlap
return out
doc_id = 0
for pdf in glob.glob("documenti/*.pdf"):
reader = PdfReader(pdf)
for page_n, page in enumerate(reader.pages):
testo = page.extract_text() or ""
for frammento in chunk(testo):
if not frammento.strip():
continue
emb = ollama.embeddings(model="nomic-embed-text", prompt=frammento)["embedding"]
coll.add(
ids=[f"d{doc_id}"],
embeddings=[emb],
documents=[frammento],
metadatas=[{"fonte": os.path.basename(pdf), "pagina": page_n + 1}],
)
doc_id += 1
print(f"Indicizzati {doc_id} frammenti.")
Eseguilo una sola volta (o quando aggiungi documenti). Alla fine avrai una cartella chroma_db/ con l'indice persistente.
Passo 4 — Fare domande con citazione della fonte
Ora la parte interessante: recuperiamo i frammenti più simili alla domanda e li passiamo al modello, chiedendogli di rispondere solo in base a quelli.
import chromadb, ollama
client = chromadb.PersistentClient(path="./chroma_db")
coll = client.get_collection("documenti")
def chiedi(domanda, k=4):
q_emb = ollama.embeddings(model="nomic-embed-text", prompt=domanda)["embedding"]
res = coll.query(query_embeddings=[q_emb], n_results=k)
contesto = ""
for doc, meta in zip(res["documents"][0], res["metadatas"][0]):
contesto += f"[{meta['fonte']} p.{meta['pagina']}] {doc}\n\n"
prompt = (
"Rispondi alla domanda usando SOLO il contesto qui sotto. "
"Se la risposta non c'è, dillo chiaramente. Cita la fonte tra parentesi.\n\n"
f"CONTESTO:\n{contesto}\n"
f"DOMANDA: {domanda}"
)
r = ollama.chat(model="llama3.2", messages=[{"role": "user", "content": prompt}])
return r["message"]["content"]
print(chiedi("Qual è la durata del contratto e come si rinnova?"))
Risultato atteso: una risposta in italiano che riassume la clausola richiesta e indica, tra parentesi, il file e la pagina da cui proviene (ad esempio «La durata è di 24 mesi con rinnovo tacito... (contratto.pdf p.3)»). Se l'informazione non è nei documenti, il modello deve dirlo invece di inventare.
Prompt utili da riusare
«Elenca in punti tutti gli obblighi a carico del fornitore citati nei documenti, con la fonte di ciascuno.»
«C'è una clausola di recesso? Riportala testualmente e indica pagina e file. Se non esiste, scrivi: non presente.»
Errori comuni e come risolverli
- «Connection refused» su localhost:11434 → Ollama non è in esecuzione. Avvialo (apri l'app o esegui
ollama serve). - Il PDF restituisce testo vuoto → è un PDF scansionato (immagini). Serve l'OCR: integra
pytesseract+pdf2imageper estrarre il testo prima di indicizzare. - Risposte vaghe o inventate → aumenta
k(più frammenti), riduci la dimensione dei chunk, o usa un modello più grande (ollama pull qwen2.5:7b). Rafforza l'istruzione «usa solo il contesto». - Lentezza → su CPU è normale; scegli modelli piccoli, oppure usa una GPU. L'indicizzazione si fa una volta sola.
Varianti e passi successivi
Per un'interfaccia grafica, avvolgi le funzioni in Streamlit (pip install streamlit) con una casella di testo e un pulsante. Per qualità superiore, sostituisci il recupero «per similarità» con un re-ranking (un secondo modello che riordina i frammenti) o aggiungi la ricerca per parole chiave (BM25) accanto a quella vettoriale: è il cosiddetto hybrid search. Se preferisci un framework, LlamaIndex e LangChain offrono gli stessi passaggi con poche righe, utili quando il progetto cresce. Infine, se i documenti sono enormi, valuta chunk più intelligenti (per paragrafo o per titolo) invece del taglio fisso a caratteri.
Quando NON usare il RAG locale: se ti serve la massima qualità di ragionamento o devi elaborare migliaia di richieste al secondo, conviene un'API cloud. Ma per privacy, costo zero e controllo totale sui propri documenti, il RAG in locale con Ollama è oggi la soluzione più sensata — e in mezz'ora è in piedi.




