Hai cento PDF - manuali, contratti, dispense, paper - e vorresti "chiederci dentro" invece di sfogliarli a mano? La tecnica si chiama RAG (Retrieval-Augmented Generation): un modello linguistico risponde alle tue domande basandosi solo sui tuoi documenti, citandoli. In questa guida lo costruiamo interamente in locale e gratis, senza inviare nulla nel cloud: ideale per documenti riservati.
A chi serve: a chi vuole un assistente sui propri file mantenendo la privacy. Cosa otterrai: uno script Python che indicizza una cartella di PDF e risponde a domande citando i passaggi rilevanti. Prerequisiti reali: un computer con almeno 8-16 GB di RAM (meglio se con una GPU, ma non obbligatoria), Python 3.10 o superiore, e circa 6-8 GB di spazio su disco per i modelli. Niente account, niente carta di credito.
Gli strumenti che useremo (e perche')
Per far girare il modello in locale la scelta consigliata e' Ollama: gratuito, semplicissimo da installare, gestisce il download e l'esecuzione dei modelli con un comando. In alternativa c'e' LM Studio (interfaccia grafica, ottimo per chi non ama il terminale) o llama.cpp (massimo controllo, piu' tecnico). Per questo compito Ollama vince perche' espone sia il modello di chat sia il modello di embedding con la stessa, comoda API.
Per la "memoria" dei documenti useremo ChromaDB, un database vettoriale leggero che si installa con pip e salva tutto in una cartella locale: zero server da configurare. Per leggere i PDF, la libreria pypdf. Costi e limiti: tutto open source e gratuito; l'unico "costo" e' la potenza del tuo PC, che determina la velocita' delle risposte.
Passo 1: installare Ollama e scaricare i modelli
Scarica Ollama da ollama.com (Windows, macOS, Linux). A installazione finita, apri il terminale e scarica due modelli: uno per gli embedding e uno per la chat.
# Modello di embedding: trasforma il testo in vettori
ollama pull nomic-embed-text
# Modello di chat leggero e multilingue (italiano incluso)
ollama pull llama3.2
# Verifica che Ollama risponda
ollama list
Se hai una macchina potente puoi usare un modello piu' grande e capace al posto di llama3.2, per esempio qwen2.5:7b. Piu' grande il modello, migliori le risposte ma maggiore la RAM richiesta.
Passo 2: preparare l'ambiente Python
Crea una cartella di progetto, mettici dentro una sottocartella documenti con i tuoi PDF, poi installa le librerie:
python -m venv venv
# Windows: venv\Scripts\activate | macOS/Linux: source venv/bin/activate
pip install ollama chromadb pypdf
Passo 3: indicizzare i PDF (lo script di ingestione)
Questo script legge i PDF, li spezza in "pezzi" (chunk) di testo gestibili, calcola un embedding per ciascuno con Ollama e li salva in ChromaDB. Salvalo come indicizza.py.
import os, glob
import chromadb
from pypdf import PdfReader
import ollama
client = chromadb.PersistentClient(path="./chroma_db")
collection = client.get_or_create_collection("documenti")
def chunk_text(text, size=900, overlap=150):
parole = text.split()
chunks, i = [], 0
while i < len(parole):
chunks.append(" ".join(parole[i:i+size]))
i += size - overlap
return chunks
doc_id = 0
for pdf in glob.glob("documenti/*.pdf"):
reader = PdfReader(pdf)
testo = " ".join((p.extract_text() or "") for p in reader.pages)
for c in chunk_text(testo):
emb = ollama.embeddings(model="nomic-embed-text", prompt=c)["embedding"]
collection.add(
ids=[f"doc-{doc_id}"],
embeddings=[emb],
documents=[c],
metadatas=[{"fonte": os.path.basename(pdf)}],
)
doc_id += 1
print(f"Indicizzato: {pdf}")
print(f"Totale chunk salvati: {doc_id}")
Lancialo con python indicizza.py. La prima volta puo' richiedere qualche minuto a seconda del numero di pagine. I dati restano nella cartella chroma_db: non dovrai rifare l'indicizzazione a ogni avvio.
Passo 4: interrogare i documenti
Ora lo script che risponde. Prende la domanda, recupera i chunk piu' simili da ChromaDB e li passa al modello di chat dentro un prompt che gli impone di usare solo quel contesto. Salvalo come chiedi.py.
import chromadb, ollama
client = chromadb.PersistentClient(path="./chroma_db")
collection = client.get_collection("documenti")
def rispondi(domanda, k=4):
q_emb = ollama.embeddings(model="nomic-embed-text", prompt=domanda)["embedding"]
res = collection.query(query_embeddings=[q_emb], n_results=k)
contesto = "\n\n".join(res["documents"][0])
fonti = {m["fonte"] for m in res["metadatas"][0]}
prompt = f"""Rispondi alla domanda usando SOLO il contesto qui sotto.
Se la risposta non e' nel contesto, scrivi: "Non e' presente nei documenti".
CONTESTO:
{contesto}
DOMANDA: {domanda}"""
out = ollama.chat(model="llama3.2", messages=[{"role":"user","content":prompt}])
print(out["message"]["content"])
print("\nFonti:", ", ".join(fonti))
if __name__ == "__main__":
while True:
d = input("\nLa tua domanda (invio vuoto per uscire): ").strip()
if not d: break
rispondi(d)
Esempio di sessione. Immaginiamo di aver indicizzato il manuale di una stampante:
La tua domanda: Come si sostituisce il toner?
Risposta: Per sostituire il toner, apri lo sportello frontale, rimuovi la cartuccia esaurita tirandola verso di te e inserisci quella nuova fino allo scatto...
Fonti: manuale-stampante.pdf
Il bello del RAG e' proprio questo: la risposta arriva dai tuoi documenti, con la fonte citata, e se l'informazione non c'e' il modello lo dichiara invece di inventare.
Errori comuni e come risolverli
- "Connection refused" o nessuna risposta da Ollama: Ollama non e' avviato. Su Windows/macOS l'app deve essere in esecuzione; da terminale verifica con
ollama listo avviaollama serve. - Il PDF restituisce testo vuoto: probabilmente e' un PDF scansionato (immagini, non testo). Serve un passaggio OCR, ad esempio con lo strumento
ocrmypdf, prima di indicizzarlo. - Risposte fuori tema: aumenta il numero di chunk recuperati (parametro
k) o riduci la dimensione dei chunk per renderli piu' mirati. - Tutto troppo lento: usa un modello piu' piccolo per la chat, oppure attiva la GPU. Su CPU pura le risposte arrivano, ma con qualche secondo di attesa.
Varianti, casi avanzati e quando NON usarlo
Per migliorare la qualita': aggiungi un passaggio di "re-ranking" dei risultati, conserva i numeri di pagina nei metadati per citazioni precise, oppure sostituisci ChromaDB con FAISS o Qdrant se i documenti diventano milioni. Per un'interfaccia grafica invece del terminale, puoi avvolgere lo script con Streamlit in poche righe. Se preferisci non scrivere codice, strumenti come AnythingLLM o Open WebUI offrono un RAG locale pronto, sempre basato su Ollama.
Quando non conviene questo approccio? Se i tuoi documenti sono pubblici e non riservati, e ti serve la massima qualita' di sintesi su molti file insieme, strumenti cloud come NotebookLM di Google sono piu' comodi e potenti. Il RAG locale brilla quando la privacy e' la priorita': i tuoi PDF non lasciano mai il tuo computer. Da qui puoi proseguire collegando il sistema a una cartella sincronizzata, aggiornando l'indice quando arrivano nuovi documenti, o sostituendo il modello di chat con uno piu' capace man mano che il tuo hardware cresce. I comandi sono stati verificati sulla documentazione ufficiale di Ollama e Chroma alla data di pubblicazione.




