Puoi interrogare i tuoi PDF — dispense, contratti, manuali, articoli scientifici — facendo domande in italiano e ottenendo risposte basate solo su quei documenti, senza inviare nulla a OpenAI o Google e senza pagare un centesimo. Tutto gira sul tuo computer grazie a Ollama e a una pipeline RAG (Retrieval-Augmented Generation) di poche righe di Python. In questa guida la costruiamo passo passo: alla fine avrai uno script che indicizza una cartella di PDF e un piccolo assistente da terminale che risponde citando i documenti.

RAG significa, in pratica, questo: invece di chiedere a un modello linguistico di rispondere "a memoria" (con il rischio che inventi), gli diamo in pasto i passaggi pertinenti dei tuoi documenti recuperati al momento della domanda. Il modello legge quei passaggi e risponde su quella base. È la tecnica dietro la maggior parte degli assistenti aziendali che "conoscono" la documentazione interna.

A chi serve, cosa otterrai e i requisiti reali

Questa guida è pensata per chi ha un minimo di dimestichezza con il terminale e vuole un assistente documentale privato e gratuito: studenti che lavorano su decine di paper, professionisti che consultano normative e capitolati, chiunque non voglia caricare documenti riservati su servizi cloud.

Cosa otterrai: una pipeline locale che (1) legge i PDF da una cartella, (2) li spezza in "chunk", (3) li trasforma in vettori con un modello di embedding, (4) li salva in un database vettoriale su disco e (5) risponde alle tue domande recuperando i passaggi giusti.

Requisiti reali:

  • Sistema operativo: Windows, macOS o Linux.
  • RAM: almeno 8 GB per i modelli da 3-4 miliardi di parametri; 16 GB consigliati per un modello da 8B come Llama 3.1. Non serve una GPU dedicata: funziona anche su CPU, solo più lentamente.
  • Spazio disco: 5-10 GB per i modelli scaricati.
  • Software: Python 3.10 o successivo e Ollama. Nessun account, nessuna carta di credito.
Tutta la pipeline RAG gira da terminale, senza inviare i documenti a servizi esterni.

Quali modelli usare: Ollama, l'embedding e il generatore

Per un RAG ti servono due modelli diversi: uno per gli embedding (trasforma il testo in vettori numerici per la ricerca semantica) e uno generativo (scrive la risposta). Ecco le scelte sensate nel 2026, tutte gratuite e open.

Per gli embedding la prima scelta è nomic-embed-text: leggero, veloce, produce vettori a 768 dimensioni e in italiano se la cava bene. Alternativa più precisa ma più pesante: mxbai-embed-large (1024 dimensioni). Se lavori molto in italiano puoi provare anche bge-m3, multilingua e ottimo sul recupero.

Per la generazione, con 16 GB di RAM la prima scelta è llama3.1:8b (Meta), equilibrato e con buon italiano. Con 8 GB scegli un modello più piccolo come gemma2:2b (Google) o qwen2.5:3b (Alibaba). Se hai una buona GPU e vuoi qualità superiore, qwen2.5:14b o gemma2:9b alzano l'asticella.

Perché Ollama e non altro? Perché installa e serve i modelli con un comando solo, espone un'API locale compatibile e gestisce automaticamente la quantizzazione (i modelli "compressi" che girano su hardware normale). L'alternativa con interfaccia grafica è LM Studio, comoda per chi non ama il terminale; per il RAG da script, però, Ollama è più diretto.

Passo 1 — Installare Ollama e scaricare i modelli

Scarica Ollama dal sito ufficiale (ollama.com): su macOS e Windows è un installer classico, su Linux basta un comando. Dopo l'installazione, da terminale scarica i due modelli:

# Linux: installazione in un comando (macOS/Windows: usa l'installer dal sito)
curl -fsSL https://ollama.com/install.sh | sh

# Modello di embedding (circa 270 MB)
ollama pull nomic-embed-text

# Modello generativo (circa 4,7 GB per la versione 8B quantizzata)
ollama pull llama3.1:8b

# Verifica che Ollama risponda
ollama list

Ollama lascia un servizio in ascolto su http://localhost:11434: è l'endpoint che useremo da Python. Puoi provarlo subito con ollama run llama3.1:8b e fare una domanda qualsiasi per controllare che tutto funzioni; poi esci con /bye.

Passo 2 — Preparare l'ambiente Python

Crea una cartella di progetto, un ambiente virtuale e installa le librerie. Useremo LangChain per orchestrare la pipeline e Chroma come database vettoriale (salva su disco, zero configurazione).

# Crea e attiva l'ambiente virtuale
python3 -m venv .venv
source .venv/bin/activate      # su Windows: .venv\Scripts\activate

# Installa le dipendenze
pip install langchain langchain-community langchain-ollama \
            langchain-chroma chromadb pypdf

Crea una sottocartella documenti/ e mettici dentro i PDF che vuoi interrogare. Per il primo test bastano due o tre file.

Passo 3 — Lo script che indicizza i PDF

Questo script legge tutti i PDF della cartella, li spezza in chunk da circa 1000 caratteri con 150 di sovrapposizione (così non si perdono le frasi a cavallo tra due pezzi), calcola gli embedding e li salva in Chroma. Salvalo come indicizza.py.

import os
from langchain_community.document_loaders import PyPDFDirectoryLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_ollama import OllamaEmbeddings
from langchain_chroma import Chroma

CARTELLA_PDF = "documenti"
CARTELLA_DB = "chroma_db"

# 1) Carica tutti i PDF della cartella
loader = PyPDFDirectoryLoader(CARTELLA_PDF)
documenti = loader.load()
print(f"Caricate {len(documenti)} pagine dai PDF")

# 2) Spezza il testo in chunk con sovrapposizione
splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000,
    chunk_overlap=150,
    separators=["\n\n", "\n", ". ", " ", ""],
)
chunks = splitter.split_documents(documenti)
print(f"Creati {len(chunks)} chunk")

# 3) Calcola gli embedding con il modello locale
embeddings = OllamaEmbeddings(model="nomic-embed-text")

# 4) Salva tutto in Chroma (persistente su disco)
db = Chroma.from_documents(
    documents=chunks,
    embedding=embeddings,
    persist_directory=CARTELLA_DB,
)
print(f"Indice creato in ./{CARTELLA_DB}")

Lancialo con python indicizza.py. La prima volta impiega da pochi secondi a qualche minuto a seconda di quante pagine hai. Quando finisce, troverai una cartella chroma_db/: è il tuo indice, riutilizzabile senza ricalcolare nulla.

Lo script di indicizzazione trasforma le pagine dei PDF in vettori salvati su disco con Chroma.

Passo 4 — Interrogare i PDF dal terminale

Ora lo script che fa le domande. Recupera i 4 chunk più pertinenti, li mette nel prompt insieme alla domanda e chiede a Llama 3.1 di rispondere solo in base a quel contesto, citando le fonti. Salvalo come chiedi.py.

from langchain_ollama import OllamaEmbeddings, ChatOllama
from langchain_chroma import Chroma
from langchain_core.prompts import ChatPromptTemplate

CARTELLA_DB = "chroma_db"

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

modello = ChatOllama(model="llama3.1:8b", temperature=0.1)

template = ChatPromptTemplate.from_template(
    """Sei un assistente che risponde SOLO in base al contesto fornito.
Se la risposta non e' nel contesto, scrivi: "Non e' presente nei documenti."
Rispondi in italiano, in modo chiaro e conciso.

Contesto:
{contesto}

Domanda: {domanda}
Risposta:"""
)

while True:
    domanda = input("\nDomanda (vuoto per uscire): ").strip()
    if not domanda:
        break
    docs = retriever.invoke(domanda)
    contesto = "\n\n---\n\n".join(d.page_content for d in docs)
    prompt = template.format(contesto=contesto, domanda=domanda)
    risposta = modello.invoke(prompt)
    print("\n" + risposta.content)
    fonti = {d.metadata.get("source", "?") for d in docs}
    print("\nFonti:", ", ".join(fonti))

Lancialo con python chiedi.py e fai le tue domande. Esempio di interazione attesa:

Domanda: Quali sono i termini di recesso previsti dal contratto?

Risposta: Il contratto prevede un preavviso di 30 giorni da comunicare via PEC (art. 7). In caso di recesso anticipato è dovuta una penale pari a una mensilità...

Fonti: documenti/contratto-2026.pdf

Nota la riga temperature=0.1: tiene il modello "aderente" ai documenti e riduce le invenzioni. E nota l'istruzione esplicita a dire "Non è presente nei documenti" quando la risposta manca: è il modo più semplice per evitare che l'IA riempia i vuoti a fantasia.

Migliorare le risposte: chunk, numero di passaggi e prompt

Se le risposte sono imprecise, agisci su tre leve, una alla volta:

  • Dimensione dei chunk. Documenti molto strutturati (norme, manuali) rendono meglio con chunk più piccoli (600-800 caratteri); testi discorsivi con chunk più grandi (1200-1500). Cambia chunk_size e rilancia l'indicizzazione.
  • Numero di passaggi recuperati (k). Alzalo a 6-8 se le risposte sono incomplete, ma occhio: più contesto significa prompt più lunghi e risposte più lente.
  • Il prompt. Aggiungi istruzioni specifiche al tuo caso, ad esempio "cita sempre il numero dell'articolo" o "rispondi in massimo 5 righe".

Un upgrade utile è il re-ranking: recuperi 20 chunk e poi un secondo modello li riordina tenendo i 4 migliori. Migliora la precisione su archivi grandi, al costo di un po' di latenza. Per cominciare, però, la configurazione base basta e avanza.

Errori comuni e come risolverli

  • ConnectionError / connessione rifiutata su localhost:11434: Ollama non è in esecuzione. Avvialo (su Linux ollama serve, su macOS/Windows apri l'app) e riprova.
  • model 'nomic-embed-text' not found: hai saltato il ollama pull. Scarica il modello e rilancia.
  • Risposte lente o PC che "arranca": il modello generativo è troppo grande per la tua RAM. Passa a gemma2:2b o qwen2.5:3b e abbassa k a 3.
  • Il modello cita pagine sbagliate o "mescola" documenti: chunk troppo grandi o troppo pochi passaggi. Riduci chunk_size, rilancia l'indicizzazione e alza leggermente k.
  • PDF scansionati che non restituiscono testo: sono immagini, non testo. Ti serve un passaggio di OCR (per esempio con ocrmypdf) prima di indicizzarli.

Varianti, alternative e quando non conviene

Vuoi un'interfaccia grafica invece del terminale? Open WebUI (open source) si collega a Ollama e include una funzione "documenti" con RAG già pronta: ottimo per chi non vuole scrivere codice. In alternativa puoi avvolgere lo script in una piccola app con Streamlit in una ventina di righe.

Quando non usare il RAG locale? Se ti serve la massima qualità di ragionamento su documenti complessi e la riservatezza non è un problema, i modelli cloud di frontiera (Claude, GPT-5, Gemini) con contesto lungo restano superiori: puoi anche incollare direttamente un PDF nelle loro interfacce. Il RAG locale brilla invece quando i documenti sono tanti, riservati, o quando vuoi un sistema gratuito e sempre disponibile offline.

Da qui puoi proseguire in tante direzioni: aggiungere una memoria conversazionale per le domande di follow-up, indicizzare anche file Word e pagine web, o esporre la pipeline come API per integrarla in altri strumenti. La base che hai costruito — carica, spezza, vettorizza, recupera, genera — è la stessa di qualunque assistente documentale professionale.

Le indicazioni su comandi, nomi dei modelli e librerie sono state verificate sulla documentazione ufficiale di Ollama, LangChain e Chroma; i tempi e i requisiti hardware sono indicativi e dipendono dalla tua macchina.