Hai una cartella di PDF, contratti, manuali, dispense, appunti, e vorresti farci domande in linguaggio naturale ottenendo risposte basate solo su quei documenti, senza caricarli su un servizio esterno. Questa tecnica si chiama RAG (Retrieval-Augmented Generation) e in questa guida la costruiremo passo passo interamente in locale e gratis, con strumenti open source. Niente abbonamenti, niente dati che escono dal tuo computer.
Come funziona il RAG, in due minuti
Un modello linguistico, da solo, sa molte cose ma non conosce i tuoi documenti privati e tende a "inventare" quando non sa. Il RAG risolve il problema con un'idea semplice: invece di sperare che il modello sappia la risposta, gliela mettiamo davanti. Il processo si divide in due fasi. Nella prima, una tantum, prendiamo i PDF, li spezziamo in pezzi piccoli (chunk), trasformiamo ogni pezzo in un vettore di numeri (embedding) che ne cattura il significato, e archiviamo questi vettori in un database vettoriale. Nella seconda fase, a ogni domanda, trasformiamo anche la domanda in un vettore, cerchiamo nel database i pezzi piu' "vicini" per significato e li passiamo al modello insieme alla domanda, chiedendogli di rispondere usando solo quel materiale. Il risultato e' una risposta ancorata ai tuoi documenti, con la possibilita' di citare da dove arriva. Capire questo schema aiuta a intervenire quando qualcosa non funziona: quasi sempre il problema sta nel recupero (chunk sbagliati o mal tagliati) piu' che nel modello.
A chi serve e cosa ti serve prima di iniziare
Questa guida e' pensata per chi sa usare il terminale e ha qualche base di Python, ma e' scritta per essere seguibile anche da chi parte quasi da zero. Alla fine avrai uno script che indicizza i tuoi PDF e risponde alle tue domande citando i passaggi pertinenti. Prerequisiti reali:
- Un computer con Windows, macOS o Linux e almeno 8-16 GB di RAM (con 8 GB usa modelli piccoli).
- Python 3.10 o superiore installato.
- Ollama, per eseguire i modelli in locale (gratuito).
- Una manciata di PDF da interrogare.
Quali strumenti scegliere (e perche')
Per fare RAG servono tre ingredienti: un modello di embedding (che trasforma il testo in vettori numerici), un database vettoriale (che li archivia e cerca i piu' simili) e un modello linguistico (che scrive la risposta). Ecco le opzioni e la mia raccomandazione:
- Ollama (prima scelta per il motore locale): gratuito, semplice, esegue sia gli embedding sia il modello di chat. Pro: privacy totale, nessun costo. Contro: la qualita' dipende dal tuo hardware.
- nomic-embed-text come modello di embedding: leggero e di buona qualita', gira bene anche su CPU.
- llama3.2 (3B) o qwen2.5 come modello di risposta: piccoli e veloci. Se hai una buona GPU puoi salire a modelli piu' grandi per risposte migliori.
- ChromaDB come database vettoriale: si installa con pip, salva tutto in una cartella locale, zero configurazione. Alternative: FAISS (piu' veloce, meno comodo) o Qdrant (per progetti grandi).
- LangChain per incollare insieme i pezzi. Alternativa valida: LlamaIndex, piu' orientato proprio al RAG.
Se invece ti serve la massima qualita' e non hai vincoli di privacy, puoi sostituire Ollama con le API di OpenAI o Google: vedremo come nella sezione varianti.
Passo 1: installare Ollama e scaricare i modelli
Scarica Ollama dal sito ufficiale e installalo. Poi, da terminale, scarica i due modelli che useremo:
# modello di embedding (trasforma il testo in vettori)
ollama pull nomic-embed-text
# modello che genera le risposte (piccolo e veloce)
ollama pull llama3.2
# verifica che il server Ollama sia attivo
ollama list
Ollama avvia in automatico un server locale su http://localhost:11434: e' quello che useremo dal codice Python.
Passo 2: creare l'ambiente Python e installare le librerie
python -m venv .venv
# su macOS/Linux:
source .venv/bin/activate
# su Windows:
.venv\Scripts\activate
pip install -U langchain langchain-community langchain-ollama langchain-chroma chromadb pypdf
Passo 3: indicizzare i PDF
Metti i tuoi PDF in una cartella chiamata documenti. Questo script li legge, li spezza in pezzi (chunk), li trasforma in vettori e li salva in ChromaDB:
from langchain_community.document_loaders import PyPDFDirectoryLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_ollama import OllamaEmbeddings
from langchain_chroma import Chroma
# 1. carica tutti i PDF della cartella
loader = PyPDFDirectoryLoader("documenti")
docs = loader.load()
# 2. spezza il testo in pezzi gestibili, con un po di sovrapposizione
splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=150)
chunks = splitter.split_documents(docs)
print(f"PDF caricati: {len(docs)} pagine, {len(chunks)} chunk")
# 3. crea gli embedding con Ollama e salvali in Chroma
embeddings = OllamaEmbeddings(model="nomic-embed-text")
db = Chroma.from_documents(chunks, embeddings, persist_directory="./chroma_db")
print("Indice creato in ./chroma_db")
Lancialo con python indicizza.py. La prima volta puo' richiedere qualche minuto a seconda del numero di pagine.
Passo 4: fare domande ai documenti
Ora lo script che, data una domanda, recupera i pezzi piu' pertinenti e chiede al modello di rispondere usando solo quelli:
from langchain_ollama import OllamaEmbeddings, ChatOllama
from langchain_chroma import Chroma
from langchain_core.prompts import ChatPromptTemplate
embeddings = OllamaEmbeddings(model="nomic-embed-text")
db = Chroma(persist_directory="./chroma_db", embedding_function=embeddings)
llm = ChatOllama(model="llama3.2", temperature=0)
prompt = ChatPromptTemplate.from_template(
"Rispondi alla domanda usando SOLO il contesto qui sotto. "
"Se la risposta non e nel contesto, dillo chiaramente.\n\n"
"Contesto:\n{contesto}\n\nDomanda: {domanda}"
)
domanda = "Qual e la durata prevista del contratto?"
risultati = db.similarity_search(domanda, k=4)
contesto = "\n\n".join(d.page_content for d in risultati)
catena = prompt | llm
risposta = catena.invoke({"contesto": contesto, "domanda": domanda})
print(risposta.content)
print("\nFonti:")
for d in risultati:
print("-", d.metadata.get("source"), "pag.", d.metadata.get("page"))
Cambia la variabile domanda con quello che ti interessa. Il modello rispondera' basandosi sui passaggi recuperati e ti mostrera' da quali file e pagine provengono.
Prompt da provare
Riassumi in 5 punti gli obblighi a carico del fornitore descritti nel documento.
Elenca tutte le scadenze e le relative date citate nei testi, in forma di tabella.
Il risultato atteso e' una risposta sintetica e fedele al contenuto dei PDF, con l'indicazione delle fonti. Se chiedi qualcosa che non c'e', un buon prompt come quello sopra fa rispondere al modello "non presente nel contesto" invece di inventare.
Errori comuni e come risolverli
- "Connection refused" su localhost:11434: Ollama non e' in esecuzione. Avvialo (apri l'app o esegui
ollama serve). - Risposte lente o blocco del PC: stai usando un modello troppo grande per la tua RAM. Resta su
llama3.2o provaqwen2.5:1.5b. - Il modello "inventa" risposte: aumenta il numero di chunk recuperati (
k=6), riducichunk_sizee rafforza il prompt vincolandolo al contesto. - PDF scansionati senza testo: sono immagini, non testo. Serve un passaggio di OCR (per esempio con lo strumento
ocrmypdf) prima di indicizzarli.
Come migliorare la qualita' delle risposte
Una volta che il flusso base funziona, ci sono tre leve per migliorare i risultati. La prima e' il taglio dei chunk: pezzi troppo grandi confondono il recupero, pezzi troppo piccoli perdono il contesto. Per documenti discorsivi un chunk_size tra 800 e 1200 caratteri con una sovrapposizione di 100-200 funziona bene; per tabelle e testi molto strutturati conviene sperimentare. La seconda leva e' il numero di chunk recuperati (il parametro k): alzarlo da' al modello piu' contesto ma rischia di introdurre rumore; un valore tra 4 e 8 e' un buon punto di partenza. La terza e' il prompt: istruzioni chiare ("usa solo il contesto", "cita le fonti", "se non sai, dillo") riducono drasticamente le invenzioni.
Un consiglio spesso trascurato: conserva nei metadati di ogni chunk il nome del file e il numero di pagina, come fa lo script qui sopra. Poter risalire alla fonte non solo aumenta la fiducia nella risposta, ma ti permette di verificare velocemente quando hai un dubbio. Per casi d'uso seri, vale la pena costruire anche un piccolo set di domande di prova con le risposte attese, da rilanciare ogni volta che cambi un parametro: e' l'unico modo per capire se una modifica migliora davvero le cose o le peggiora.
Varianti e quando non usare questo approccio
Per risposte di qualita' superiore puoi sostituire gli embedding e il modello locali con le API di OpenAI (OpenAIEmbeddings e ChatOpenAI) o di Google: il codice resta quasi identico, cambia solo l'oggetto del modello, ma i tuoi dati usciranno dal computer e ci sara' un costo a consumo. Per archivi molto grandi, valuta Qdrant al posto di Chroma e l'aggiunta di un "reranker" che riordina i risultati prima di passarli al modello. Se invece hai pochissimi documenti brevi, il RAG potrebbe essere eccessivo: a volte basta incollare l'intero testo nel contesto di un modello con finestra ampia. Da qui puoi proseguire trasformando lo script in una piccola interfaccia web (con Streamlit) o in un'API, e aggiungendo la memoria della conversazione per fare domande di seguito. Hai costruito, di fatto, un assistente privato sui tuoi documenti.




