Vuoi «parlare» con i tuoi PDF — manuali, contratti, dispense, bilanci — ponendo domande in italiano e ottenendo risposte basate solo sul loro contenuto, senza mandare nulla in cloud? In questa guida costruiamo un sistema RAG (Retrieval-Augmented Generation) che gira interamente sul tuo computer, gratis e privato, usando Ollama, un modello linguistico locale e un database vettoriale. Codice incluso, passo per passo.

A chi serve e cosa otterrai

Questa guida e' adatta a chi vuole interrogare documenti riservati senza inviarli a servizi esterni: avvocati, commercialisti, ricercatori, aziende con dati sensibili, ma anche semplici curiosi. Otterrai uno script Python che indicizza i tuoi PDF e risponde alle domande citando le informazioni contenute nei file. Tutto resta sul tuo disco.

Prerequisiti reali: un computer con almeno 8 GB di RAM (16 GB consigliati), Windows, macOS o Linux, Python 3.9+ e qualche giga di spazio libero per i modelli. Una GPU accelera molto ma non e' obbligatoria: i modelli piccoli girano anche su CPU, solo piu' lentamente.

Come funziona il RAG, in due righe

Il problema: un modello linguistico non conosce i tuoi documenti e, se interrogato, rischia di inventare. La soluzione RAG: prima si spezzano i documenti in piccoli pezzi e si trasformano in vettori numerici (embedding) salvati in un database; quando fai una domanda, il sistema recupera i pezzi piu' pertinenti e li passa al modello come contesto, chiedendogli di rispondere solo in base a quelli. Cosi' le risposte sono ancorate ai tuoi dati.

Passo 1: installare Ollama e scaricare i modelli

Ollama e' lo strumento che fa girare i modelli in locale. Scaricalo dal sito ufficiale ollama.com e installalo. Poi, da terminale, scarica due modelli: uno per generare le risposte e uno specializzato negli embedding.

# modello linguistico (leggero, multilingua)
ollama pull llama3.2

# modello per gli embedding
ollama pull nomic-embed-text

Verifica che Ollama sia attivo: il comando ollama list deve mostrare i due modelli scaricati. Ollama espone un server locale su http://localhost:11434 a cui si collegheranno gli script.

Passo 2: installare le librerie Python

Useremo LangChain per orchestrare il flusso e Chroma come database vettoriale:

pip install langchain langchain-community langchain-ollama langchain-chroma chromadb pypdf

Passo 3: indicizzare i PDF

Crea un file indicizza.py. Carica i PDF da una cartella, li spezza in pezzi, calcola gli embedding con nomic-embed-text e li salva in Chroma su disco.

from langchain_community.document_loaders import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_ollama import OllamaEmbeddings
from langchain_chroma import Chroma
import glob

# 1. carica tutti i PDF della cartella ./documenti
documenti = []
for percorso in glob.glob("documenti/*.pdf"):
    documenti.extend(PyPDFLoader(percorso).load())

# 2. spezza in pezzi da ~1000 caratteri con sovrapposizione
splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000, chunk_overlap=150
)
pezzi = splitter.split_documents(documenti)
print("Pezzi creati:", len(pezzi))

# 3. crea gli embedding e salva il database su disco
embeddings = OllamaEmbeddings(model="nomic-embed-text")
db = Chroma.from_documents(
    pezzi, embeddings, persist_directory="./db_vettoriale"
)
print("Indicizzazione completata.")

Metti i tuoi file nella cartella documenti e lancia python indicizza.py. Alla fine avrai una cartella db_vettoriale con l'indice pronto: questo passaggio va rifatto solo quando aggiungi o modifichi i documenti.

Tutto il calcolo avviene sul tuo computer: i documenti non lasciano il disco. (Foto: Pexels)

Passo 4: fare domande ai documenti

Crea un file chiedi.py. Recupera i pezzi pertinenti dal database e li passa a llama3.2 con un prompt che lo obbliga a rispondere solo in base al contesto.

from langchain_ollama import OllamaEmbeddings, ChatOllama
from langchain_chroma import Chroma

embeddings = OllamaEmbeddings(model="nomic-embed-text")
db = Chroma(persist_directory="./db_vettoriale", embedding_function=embeddings)
retriever = db.as_retriever(search_kwargs={"k": 4})

modello = ChatOllama(model="llama3.2", temperature=0)

def rispondi(domanda):
    pezzi = retriever.invoke(domanda)
    contesto = "\n\n".join(p.page_content for p in pezzi)
    prompt = (
        "Rispondi alla domanda usando SOLO il contesto qui sotto. "
        "Se la risposta non e' nel contesto, scrivi: 'Non trovato nei documenti.'\n\n"
        "CONTESTO:\n" + contesto + "\n\nDOMANDA: " + domanda
    )
    return modello.invoke(prompt).content

while True:
    d = input("\nLa tua domanda (invio vuoto per uscire): ")
    if not d:
        break
    print(rispondi(d))

Lancia python chiedi.py e prova con una domanda concreta sul contenuto dei tuoi file, ad esempio: «Qual e' la durata del preavviso prevista dal contratto?». Il parametro k=4 indica quanti pezzi recuperare: alzalo per domande che richiedono piu' contesto, abbassalo per risposte piu' mirate. La temperature=0 rende le risposte piu' deterministiche e meno «creative», l'ideale per documenti.

Errori comuni e soluzioni

  • «Connection refused» su localhost:11434: Ollama non e' in esecuzione. Avvialo (l'app o il comando ollama serve) e riprova.
  • «model not found»: non hai scaricato il modello. Esegui ollama pull llama3.2 e ollama pull nomic-embed-text.
  • Risposte lente o blocchi: stai usando la CPU con un modello troppo grande. Resta su llama3.2 (versione piccola) o prova qwen2.5:3b; chiudi le altre applicazioni pesanti.
  • Il PDF non viene letto: spesso e' un PDF scansionato (solo immagini). Serve prima un passaggio di OCR, ad esempio con lo strumento ocrmypdf, per estrarre il testo.
  • Risposte imprecise: prova a ridurre chunk_size o ad aumentare k; documenti molto lunghi traggono beneficio da pezzi piu' piccoli e mirati.

Varianti e casi avanzati

Per qualita' migliore puoi sostituire llama3.2 con un modello piu' capace come qwen2.5:7b o mistral, se l'hardware lo regge. Per indicizzare molti documenti, mantieni il database persistente e aggiungi solo i nuovi file invece di ricostruire tutto. Se vuoi le citazioni delle fonti, modifica la funzione per stampare anche p.metadata di ogni pezzo, che contiene nome del file e numero di pagina. Per servire piu' utenti, puoi esporre la funzione rispondi dietro una piccola API con FastAPI.

Alternative senza scrivere codice

Se programmare non fa per te, esistono applicazioni desktop che fanno RAG locale con interfaccia grafica: AnythingLLM e GPT4All permettono di trascinare i PDF e chattare, usando proprio Ollama come motore; LM Studio e' ottimo per gestire i modelli in locale con un'interfaccia curata. Sono perfetti per iniziare, ma offrono meno controllo del flusso rispetto allo script che hai appena costruito.

Privacy, velocita' e spazio su disco

Il vantaggio piu' grande di questo sistema e' la privacy: nessun documento, nessuna domanda e nessuna risposta lascia il tuo computer. Per chi tratta dati personali o riservati, e' una differenza sostanziale rispetto ai servizi cloud, e semplifica molto il rispetto del GDPR, perche' non c'e' trasferimento di dati verso terze parti. Resta comunque buona norma proteggere la cartella del database vettoriale come faresti con i documenti originali: i pezzi di testo indicizzati ne contengono il contenuto.

Sul fronte delle prestazioni, le aspettative vanno calibrate sull'hardware. Su un portatile recente senza GPU, un modello come llama3.2 risponde in genere in alcuni secondi per domanda; con una GPU dedicata i tempi si riducono drasticamente. La fase piu' lenta e' di solito l'indicizzazione iniziale, che pero' si fa una volta sola. Quanto allo spazio: i modelli occupano qualche giga ciascuno (Ollama li scarica una volta e li riusa), mentre il database vettoriale cresce in proporzione alla quantita' di testo, restando comunque contenuto — centinaia di pagine occupano in genere poche decine di megabyte.

Un'ultima ottimizzazione utile riguarda la qualita' del recupero. Se noti che il sistema fatica a trovare le informazioni giuste, sperimenta con i parametri di suddivisione: pezzi piu' piccoli (chunk_size 500-700) funzionano meglio con documenti densi e tecnici, mentre pezzi piu' grandi preservano meglio il contesto nei testi discorsivi. La sovrapposizione (chunk_overlap) evita di tagliare a meta' un concetto importante al confine tra due pezzi.

Quando NON usare questo approccio

Il RAG locale e' ideale per documenti riservati e per chi vuole costi a zero, ma ha dei limiti. Se i tuoi documenti non sono sensibili e ti serve la massima qualita' con il minimo sforzo, i servizi in cloud (con i modelli piu' grandi) restano piu' precisi e veloci. E se ti serve solo riassumere un singolo PDF corto, caricarlo direttamente in un assistente come Claude o ChatGPT e' molto piu' rapido che costruire un'intera pipeline. Il sistema che hai realizzato qui da' il meglio quando hai molti documenti, esigenze di privacy e la volonta' di non pagare per ogni domanda. Da questa base puoi far crescere un vero assistente documentale su misura.

Per andare oltre, due direzioni valgono lo sforzo. La prima e' aggiungere una semplice interfaccia: con poche righe di Streamlit o Gradio trasformi lo script da terminale in un'app con casella di testo e cronologia, molto piu' comoda da usare ogni giorno. La seconda e' migliorare la qualita' del recupero con tecniche come il re-ranking, in cui un secondo modello riordina i pezzi recuperati per pertinenza prima di passarli al generatore: e' il passo che separa un prototipo da un assistente davvero affidabile su archivi grandi. In entrambi i casi, il cuore del sistema — modelli locali, embedding e database vettoriale — resta quello che hai costruito qui, completamente sotto il tuo controllo e senza un euro di costi ricorrenti.