Hai una cartella piena di PDF — manuali, contratti, dispense, verbali, una tesi — e vorresti poterli interrogare a voce come faresti con ChatGPT, ma senza caricarli su un server altrui? Questa guida ti mostra come costruire un sistema RAG (Retrieval-Augmented Generation) che gira interamente sul tuo computer, gratis e offline, usando Ollama e poche righe di Python. Alla fine avrai un piccolo programma che indicizza i tuoi documenti e risponde alle domande citando le fonti.

A chi serve e cosa otterrai

E' una guida di livello intermedio, pensata per chi sa muoversi un minimo con il terminale e non si spaventa davanti a uno script Python. E' utile a professionisti che trattano documenti riservati (avvocati, commercialisti, medici, consulenti), a studenti e ricercatori, a chiunque voglia un assistente sui propri testi senza rinunciare alla privacy. Otterrai due script: uno che indicizza i PDF di una cartella e uno che ti permette di fare domande ottenendo risposte basate solo su quei documenti.

Prerequisiti reali

  • Un computer con Windows, macOS o Linux e almeno 8 GB di RAM (16 GB consigliati per modelli piu' grandi).
  • Python 3.10 o superiore installato.
  • Circa 10 GB di spazio libero su disco per Ollama e i modelli.
  • Nessun account e nessuna carta di credito: e' tutto gratuito e open source.

Cos'e' il RAG, in due parole

Un modello linguistico non conosce i tuoi documenti privati. Il RAG risolve il problema cosi': quando fai una domanda, il sistema cerca nei tuoi testi i frammenti piu' pertinenti e li passa al modello come contesto, chiedendogli di rispondere basandosi su quelli. Per farlo servono tre ingredienti: un modello di embedding che trasforma il testo in vettori numerici, un database vettoriale che li conserva e li cerca per similarita', e un modello linguistico che genera la risposta finale.

Con un sistema RAG locale puoi interrogare in linguaggio naturale i tuoi PDF senza inviarli online.

Quali strumenti useremo (e le alternative)

Per ognuno dei tre ruoli ci sono piu' opzioni. Ecco le scelte di questa guida e il perche'.

  • Motore IA locale: Ollama (prima scelta). Gratuito, semplice, gestisce con un comando sia il modello linguistico sia quello di embedding. Alternative: LM Studio (ottimo con interfaccia grafica, comodo per chi non ama il terminale) e llama.cpp (massimo controllo, piu' tecnico). Per automatizzare con Python, Ollama e' il piu' lineare.
  • Modello linguistico: llama3.2 (3B, leggerissimo) o qwen2.5:7b se hai 16 GB di RAM e vuoi risposte migliori in italiano.
  • Modello di embedding: nomic-embed-text, piccolo, veloce e adatto a documenti multilingue.
  • Database vettoriale: ChromaDB (prima scelta). Si installa con pip, salva tutto in una cartella locale e non richiede server. Alternativa: FAISS (di Meta, velocissimo su grandi volumi ma piu' spartano).
  • Orchestrazione: qui usiamo codice Python puro invece di framework come LangChain o LlamaIndex. Sono ottimi, ma per imparare conviene vedere ogni passaggio in chiaro; potrai sempre passare a loro in seguito.

Passo 1 - Installa Ollama

Scarica Ollama dal sito ufficiale ollama.com. Su macOS e Windows e' un normale installer. Su Linux puoi usare lo script ufficiale:

curl -fsSL https://ollama.com/install.sh | sh

Verifica che funzioni con:

ollama --version

Passo 2 - Scarica i modelli

Servono due modelli: quello linguistico e quello per gli embedding. Scaricali cosi':

ollama pull llama3.2
ollama pull nomic-embed-text

Il primo occupa circa 2 GB, il secondo poche centinaia di MB. Se hai 16 GB di RAM e vuoi risposte piu' curate in italiano, scarica anche qwen2.5:7b (circa 4,7 GB) e usalo al posto di llama3.2 nel codice.

Passo 3 - Prepara l'ambiente Python

Crea una cartella per il progetto, al suo interno una sottocartella documenti dove metterai i PDF, poi crea un ambiente virtuale e installa le librerie:

python -m venv venv
# macOS / Linux:
source venv/bin/activate
# Windows:
venv\Scripts\activate

pip install chromadb pypdf ollama
Bastano poche decine di righe di Python per costruire un assistente che risponde citando i tuoi documenti.

Passo 4 - Lo script di indicizzazione

Crea un file indicizza.py. Questo script legge tutti i PDF nella cartella documenti, li spezza in frammenti, calcola gli embedding con Ollama e li salva in ChromaDB.

import os, glob, chromadb, ollama
from pypdf import PdfReader

CARTELLA = "documenti"
client = chromadb.PersistentClient(path="db")
coll = client.get_or_create_collection("pdf")

def spezza(testo, dim=800, sovrapp=100):
    parole = testo.split()
    pezzi, i = [], 0
    while i < len(parole):
        pezzi.append(" ".join(parole[i:i+dim]))
        i += dim - sovrapp
    return pezzi

idx = 0
for pdf in glob.glob(os.path.join(CARTELLA, "*.pdf")):
    lettore = PdfReader(pdf)
    testo = "\n".join((p.extract_text() or "") for p in lettore.pages)
    for pezzo in spezza(testo):
        if not pezzo.strip():
            continue
        emb = ollama.embeddings(model="nomic-embed-text", prompt=pezzo)["embedding"]
        coll.add(ids=[str(idx)], embeddings=[emb], documents=[pezzo],
                 metadatas=[{"fonte": os.path.basename(pdf)}])
        idx += 1
    print("Indicizzato:", os.path.basename(pdf))

print(f"Totale frammenti indicizzati: {idx}")

Lancialo con python indicizza.py. Spezziamo il testo in blocchi di circa 800 parole con 100 di sovrapposizione: la sovrapposizione evita di tagliare a meta' un concetto importante tra un frammento e l'altro.

Passo 5 - Lo script per fare domande

Crea ora chiedi.py. Trasforma la tua domanda in un embedding, recupera i 4 frammenti piu' simili e li passa al modello linguistico chiedendogli di rispondere solo in base a quelli.

import sys, chromadb, ollama

client = chromadb.PersistentClient(path="db")
coll = client.get_collection("pdf")

domanda = sys.argv[1] if len(sys.argv) > 1 else input("Domanda: ")
q_emb = ollama.embeddings(model="nomic-embed-text", prompt=domanda)["embedding"]
res = coll.query(query_embeddings=[q_emb], n_results=4)

contesto = "\n\n".join(res["documents"][0])
fonti = ", ".join(sorted({m["fonte"] for m in res["metadatas"][0]}))

prompt = f"""Sei un assistente che risponde SOLO usando il contesto qui sotto.
Se la risposta non e' presente nel contesto, scrivi: "Non e' nei documenti".
Rispondi in italiano, in modo chiaro.

CONTESTO:
{contesto}

DOMANDA: {domanda}
RISPOSTA:"""

out = ollama.generate(model="llama3.2", prompt=prompt)
print(out["response"])
print("\nFonti consultate:", fonti)

Passo 6 - Provalo

Metti qualche PDF nella cartella documenti, esegui python indicizza.py e poi fai una domanda:

python chiedi.py "Qual e' la durata del contratto e come si rinnova?"

Il risultato atteso e' una risposta in italiano che riassume cio' che dicono i tuoi documenti sul tema, seguita dall'elenco dei file da cui sono stati presi i frammenti. Se l'informazione non c'e', il modello risponde "Non e' nei documenti": e' il comportamento corretto di un buon sistema RAG, che non deve inventare.

Varianti e miglioramenti

  • Risposte migliori: sostituisci llama3.2 con qwen2.5:7b nello script delle domande.
  • Piu' contesto: alza n_results da 4 a 6-8 per domande complesse.
  • Interfaccia grafica: se preferisci non usare il terminale, strumenti come AnythingLLM o Open WebUI offrono un RAG locale con interfaccia, sempre basati su Ollama.
  • Altri formati: aggiungi il supporto a Word o testo semplice adattando la lettura dei file.

Errori comuni e come risolverli

  • "connection refused" o errori di connessione a Ollama: il servizio non e' attivo. Avvialo con ollama serve in un altro terminale (su macOS/Windows parte da solo con l'app).
  • Il PC si impalla o va in "out of memory": stai usando un modello troppo grande per la tua RAM. Torna a llama3.2 o prova varianti quantizzate piu' leggere.
  • Risposte vuote o senza senso da un PDF: probabilmente e' una scansione di immagini senza testo. Serve un passaggio di OCR (per esempio con Tesseract) prima di indicizzarlo.
  • Risposte troppo generiche: riduci la dimensione dei frammenti (per esempio 500 parole) per indicizzazioni piu' precise.

Quando NON usare questo approccio

Il RAG locale e' perfetto per qualche centinaio o migliaio di documenti su un PC personale. Se devi gestire milioni di file, ti servira' un'infrastruttura dedicata con database vettoriali professionali (Qdrant, Weaviate, pgvector) e piu' potenza di calcolo. E in contesti dove un errore ha conseguenze legali o sanitarie, ricorda che il modello e' un assistente, non una fonte di verita': la risposta va sempre verificata sul documento originale, che il nostro script per fortuna ti indica.

Come proseguire

Da qui puoi crescere in piu' direzioni: aggiungere una memoria della conversazione, mostrare l'estratto esatto da cui proviene la risposta, o impacchettare tutto in una piccola interfaccia web. Il bello di aver scritto il codice a mano e' che ora sai cosa succede in ogni passaggio: quando passerai a framework come LlamaIndex o a soluzioni cloud, non saranno piu' una scatola nera. E soprattutto, i tuoi documenti non avranno mai lasciato il tuo computer.