Hai una cartella piena di PDF — manuali, contratti, dispense, ricerche — e vorresti poterli "interrogare" come fai con ChatGPT, ma senza caricare nulla in cloud e senza pagare un abbonamento? Questa guida ti spiega come costruire un sistema RAG (Retrieval-Augmented Generation) che gira interamente sul tuo computer: l'intelligenza artificiale risponde alle tue domande citando i tuoi documenti, e i dati non escono mai dalla tua macchina. Useremo Ollama per i modelli, Python per la logica e ChromaDB come database vettoriale. Tutto gratis e open source.
A chi serve e cosa otterrai
Questo tutorial è pensato per chi ha un minimo di dimestichezza con il terminale e con Python di base, ma non serve essere esperti di machine learning. È perfetto per professionisti che gestiscono documentazione riservata (avvocati, commercialisti, studi tecnici, sanità), studenti e ricercatori che lavorano su molti paper, e sviluppatori che vogliono un punto di partenza per un assistente documentale aziendale. Alla fine avrai uno script funzionante a cui chiedere, in italiano, cose come "Qual è la durata del preavviso nel contratto?" e ottenere una risposta basata sul testo reale dei tuoi file, con l'indicazione della fonte.
Prerequisiti reali: un computer con Windows, macOS o Linux; almeno 8 GB di RAM (16 GB consigliati per modelli più capaci); circa 5-10 GB di spazio libero per i modelli; Python 3.10 o successivo installato. Una GPU non è obbligatoria, ma se ce l'hai velocizza molto le risposte.
Cos'è un RAG, spiegato semplice
Un modello linguistico da solo non conosce i tuoi documenti e, se interrogato, tende a "inventare". Il RAG risolve il problema in tre mosse: (1) spezza i tuoi PDF in piccoli pezzi di testo; (2) trasforma ogni pezzo in numeri (i cosiddetti embedding) e li archivia in un database vettoriale; (3) quando fai una domanda, cerca i pezzi più simili alla domanda e li passa al modello come "contesto", chiedendogli di rispondere usando solo quelle informazioni. Il risultato è una risposta ancorata ai tuoi dati, molto meno soggetta a errori inventati.
Quali strumenti usare e perché Ollama è la prima scelta
Per far girare un modello in locale ci sono diverse opzioni. Ecco le principali, con pro e contro per questo compito:
- Ollama (consigliato): gratis, leggerissimo da installare, espone un'API locale e gestisce sia i modelli di chat sia quelli di embedding. È la scelta più semplice per uno script Python. Contro: meno "visuale" di altri.
- LM Studio: interfaccia grafica comoda, ottimo per provare i modelli, ma per uno script da terminale è meno immediato di Ollama.
- AnythingLLM: applicazione no-code che fa già RAG con interfaccia pronta; perfetta se non vuoi scrivere codice, ma meno personalizzabile.
- API cloud (OpenAI, Google, Mistral): qualità di embedding superiore, ma i dati escono dalla tua macchina e c'è un costo. Da evitare se la privacy è il motivo per cui sei qui.
Useremo Ollama con due modelli: llama3.1:8b (o un equivalente) per generare le risposte e nomic-embed-text per gli embedding. Sono entrambi gratuiti e girano bene anche senza GPU di fascia alta.
Preparare l'ambiente: Ollama, modelli e librerie Python
Per prima cosa installa Ollama dal sito ufficiale (ollama.com); su macOS e Windows è un normale installer, su Linux basta un comando. Poi scarica i due modelli dal terminale:
ollama pull llama3.1:8b
ollama pull nomic-embed-text
Verifica che Ollama sia attivo (di solito parte da solo e resta in ascolto su http://localhost:11434). Crea ora una cartella per il progetto e installa le librerie Python necessarie, preferibilmente in un ambiente virtuale:
python3 -m venv venv
source venv/bin/activate # su Windows: venv\Scripts\activate
pip install ollama chromadb pypdf
Infine crea una sottocartella documenti/ e mettici dentro i PDF che vuoi interrogare.
Lo script passo passo: dal PDF alle risposte
Lo script fa due cose: prima indicizza i PDF (li legge, li spezza, li trasforma in embedding e li salva in ChromaDB), poi entra in modalità domanda-risposta. Ecco il codice completo, commentato; salvalo come rag.py.
import os, sys, glob
import chromadb
import ollama
from pypdf import PdfReader
CARTELLA = "documenti"
MODELLO_CHAT = "llama3.1:8b"
MODELLO_EMBED = "nomic-embed-text"
client = chromadb.PersistentClient(path="./db")
collection = client.get_or_create_collection("pdf")
def spezza(testo, dim=800, overlap=150):
# divide il testo in pezzi con un po' di sovrapposizione
pezzi, i = [], 0
while i < len(testo):
pezzi.append(testo[i:i + dim])
i += dim - overlap
return pezzi
def indicizza():
n = 0
for pdf in glob.glob(os.path.join(CARTELLA, "*.pdf")):
reader = PdfReader(pdf)
for npag, pagina in enumerate(reader.pages):
testo = (pagina.extract_text() or "").strip()
if not testo:
continue
for j, pezzo in enumerate(spezza(testo)):
emb = ollama.embeddings(model=MODELLO_EMBED, prompt=pezzo)["embedding"]
collection.add(
ids=[f"{os.path.basename(pdf)}-{npag}-{j}"],
embeddings=[emb],
documents=[pezzo],
metadatas=[{"fonte": os.path.basename(pdf), "pagina": npag + 1}],
)
n += 1
print(f"Indicizzati {n} frammenti.")
def chiedi(domanda, k=4):
emb = ollama.embeddings(model=MODELLO_EMBED, prompt=domanda)["embedding"]
res = collection.query(query_embeddings=[emb], n_results=k)
contesto = "\n\n".join(res["documents"][0])
fonti = ", ".join(f'{m["fonte"]} p.{m["pagina"]}' for m in res["metadatas"][0])
prompt = (
"Rispondi alla domanda usando SOLO il contesto qui sotto. "
"Se la risposta non c'e', scrivi che non e' presente nei documenti.\n\n"
f"CONTESTO:\n{contesto}\n\nDOMANDA: {domanda}"
)
out = ollama.chat(model=MODELLO_CHAT, messages=[{"role": "user", "content": prompt}])
print("\nRISPOSTA:", out["message"]["content"])
print("FONTI:", fonti)
if __name__ == "__main__":
if len(sys.argv) > 1 and sys.argv[1] == "index":
indicizza()
else:
while True:
d = input("\nDomanda (invio vuoto per uscire): ").strip()
if not d:
break
chiedi(d)
La prima volta lancia l'indicizzazione (da ripetere solo quando aggiungi nuovi PDF):
python3 rag.py index
Poi avvia la modalità interattiva e inizia a fare domande:
python3 rag.py
Provarlo: tre domande di esempio e cosa restituisce
Supponiamo di aver indicizzato un contratto di lavoro e un manuale tecnico. Ecco tre prompt utili e il tipo di risposta atteso:
Qual è il periodo di preavviso per le dimissioni indicato nel contratto?
Risposta attesa: la durata esatta riportata nel testo (es. "30 giorni"), con l'indicazione del file e della pagina da cui è tratta.
Riassumi in cinque punti la procedura di manutenzione descritta nel manuale.
Risposta attesa: un elenco sintetico costruito sui frammenti recuperati, senza aggiungere passaggi non presenti.
Nei documenti si parla di garanzia sui ricambi? Se sì, per quanto tempo?
Risposta attesa: se l'informazione c'è, viene riportata; se non c'è, il modello dichiara che non è presente nei documenti — esattamente il comportamento che vogliamo.
Migliorare i risultati: chunk, modelli, prompt
Se le risposte non ti soddisfano, agisci su tre leve. Dimensione dei frammenti: pezzi troppo piccoli perdono il contesto, troppo grandi confondono il modello; 600-1000 caratteri con un po' di sovrapposizione è un buon punto di partenza. Numero di frammenti recuperati (il parametro k): aumentalo a 6-8 per domande complesse, ma attenzione a non saturare il contesto. Modello di chat: se hai abbastanza RAM, prova un modello più grande o più recente per risposte migliori; se la macchina è lenta, scegline uno più piccolo. Infine, cura il prompt: l'istruzione di usare "solo il contesto" e di ammettere quando non sa è ciò che riduce di più gli errori inventati.
Errori comuni e come risolverli
- "Connection refused" su localhost:11434 → Ollama non è in esecuzione. Avvialo (apri l'app o lancia
ollama serve). - Il PDF non produce testo → probabilmente è una scansione (immagine). Serve un passaggio OCR, ad esempio con
ocrmypdf, prima di indicizzarlo. - Risposte lente → normale senza GPU; usa un modello più piccolo o riduci
k. La fase di indicizzazione è la più pesante, ma si fa una volta sola. - "model not found" → hai dimenticato di scaricare il modello con
ollama pull, oppure hai scritto male il nome. - Risposte generiche o fuori tema → controlla che l'indicizzazione sia andata a buon fine (il numero di frammenti dev'essere > 0) e aumenta la sovrapposizione tra i chunk.
Alternative e quando non usare un RAG locale
Se non vuoi scrivere codice, AnythingLLM o GPT4All offrono lo stesso risultato con un'interfaccia grafica e si appoggiano comunque a modelli locali. Se invece i tuoi documenti sono pochi e non riservati, caricarli direttamente in NotebookLM di Google o in ChatGPT è più rapido e dà ottimi risultati, al prezzo di mandare i dati in cloud. Il RAG locale conviene quando contano privacy, costo zero e controllo. Non è invece la scelta migliore per archivi enormi (centinaia di migliaia di documenti), dove servono database vettoriali gestiti e infrastruttura dedicata, né quando ti basta una risposta occasionale su un singolo file. Da qui puoi crescere: aggiungere un'interfaccia web con Streamlit, gestire più collezioni tematiche o sostituire il modello con uno aperto più recente come quelli della famiglia Qwen o Mistral. La base, però, è questa — ed è già pienamente tua.




