Immagina di avere una cartella piena di PDF – manuali, contratti, dispense, verbali – e di poter fare domande in linguaggio naturale ottenendo risposte precise, con la citazione del punto esatto da cui provengono. E' cio' che permette il RAG (Retrieval-Augmented Generation): invece di affidarsi solo alla memoria del modello, il sistema recupera i pezzi rilevanti dei tuoi documenti e li passa all'IA come contesto. In questa guida costruiremo un RAG che gira interamente sul tuo computer, senza inviare nulla nel cloud e a costo zero, usando Ollama, LangChain e ChromaDB.
A chi serve, cosa otterrai e i prerequisiti
Questa guida e' pensata per chi ha una minima dimestichezza con il terminale e con Python e vuole interrogare documenti riservati senza affidarli a servizi esterni: professionisti, studiosi, aziende che trattano dati sensibili. Al termine avrai uno script che indicizza i tuoi PDF e risponde alle domande su di essi in locale. Ti serve: un computer con Windows, macOS o Linux; Python 3.10 o superiore; almeno 8 GB di RAM (16 GB consigliati per risposte piu' fluide); circa 5-6 GB di spazio libero per i modelli. Non serve una GPU: tutto funziona anche solo con la CPU, seppur piu' lentamente.
Quali strumenti useremo e perche'
La pila che consiglio come prima scelta e' tutta gratuita e locale. Ollama e' il modo piu' semplice per scaricare ed eseguire modelli linguistici e di embedding sul proprio PC: un comando e il modello e' pronto. LangChain e' la libreria che orchestra i passaggi del RAG (lettura, suddivisione, ricerca, generazione). ChromaDB e' il database vettoriale che memorizza le rappresentazioni numeriche dei testi e permette la ricerca per similarita'; e' leggero e non richiede installazioni di server. Come modelli useremo nomic-embed-text per gli embedding e un modello compatto come llama3.2 (o qwen2.5) per generare le risposte. In alternativa si potrebbe usare LlamaIndex al posto di LangChain, o un database come FAISS al posto di Chroma: le logiche non cambiano. Il vantaggio rispetto ai servizi cloud e' duplice: privacy totale e nessun costo a consumo; lo svantaggio e' che i modelli locali piccoli sono meno brillanti di GPT-5.6 o Claude, e su documenti molto complessi la qualita' delle risposte puo' calare.
Passo 1: installare Ollama e scaricare i modelli
Scarica Ollama dalla pagina ufficiale e installalo. Poi, da terminale, scarica i due modelli che ci servono:
ollama pull nomic-embed-text
ollama pull llama3.2
Verifica che Ollama sia attivo: di norma espone un servizio locale all'indirizzo http://localhost:11434. Puoi provarlo con:
ollama run llama3.2 "Ciao, dimmi in una frase cosa sai fare."
Se ricevi una risposta, sei pronto. Nota: la prima esecuzione carica il modello in memoria e puo' richiedere qualche secondo.
Passo 2: installare le librerie Python
Crea una cartella per il progetto, apri un ambiente virtuale (consigliato) e installa le dipendenze:
python -m venv venv
source venv/bin/activate # su Windows: venv\Scripts\activate
pip install langchain langchain-community langchain-ollama chromadb pypdf
Passo 3: leggere e suddividere i PDF
Il primo blocco di codice carica i PDF da una cartella e li spezza in "chunk", brani di dimensione gestibile. La suddivisione e' cruciale: pezzi troppo grandi diluiscono l'informazione, pezzi troppo piccoli la frammentano. Un buon punto di partenza sono 1000 caratteri con 150 di sovrapposizione, per non tagliare le frasi a meta'.
from langchain_community.document_loaders import PyPDFDirectoryLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
loader = PyPDFDirectoryLoader("documenti/") # metti qui i tuoi PDF
docs = loader.load()
splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=150)
chunks = splitter.split_documents(docs)
print(f"Caricati {len(docs)} documenti, suddivisi in {len(chunks)} chunk.")
Passo 4: creare gli embedding e l'indice vettoriale
Ora trasformiamo ogni chunk in un vettore con il modello di embedding di Ollama e li salviamo in ChromaDB. L'indice viene scritto su disco, cosi' non dovrai ricalcolarlo a ogni avvio.
from langchain_ollama import OllamaEmbeddings
from langchain_community.vectorstores import Chroma
embeddings = OllamaEmbeddings(model="nomic-embed-text")
vectordb = Chroma.from_documents(
documents=chunks,
embedding=embeddings,
persist_directory="indice_chroma"
)
print("Indice creato e salvato in 'indice_chroma'.")
Passo 5: fare domande ai documenti
L'ultimo pezzo collega il recupero dei brani rilevanti al modello che genera la risposta. Definiamo un prompt che ordina all'IA di rispondere solo sulla base del contesto recuperato, per ridurre le allucinazioni.
from langchain_ollama import ChatOllama
from langchain.chains import RetrievalQA
from langchain.prompts import PromptTemplate
llm = ChatOllama(model="llama3.2", temperature=0)
retriever = vectordb.as_retriever(search_kwargs={"k": 4})
template = """Rispondi alla domanda usando SOLO il contesto qui sotto.
Se la risposta non e' nel contesto, di' "Non trovo questa informazione nei documenti".
Contesto:
{context}
Domanda: {question}
Risposta in italiano:"""
prompt = PromptTemplate(template=template, input_variables=["context", "question"])
qa = RetrievalQA.from_chain_type(
llm=llm, retriever=retriever,
chain_type_kwargs={"prompt": prompt},
return_source_documents=True
)
risposta = qa.invoke({"query": "Qual e' la durata prevista dal contratto?"})
print(risposta["result"])
for d in risposta["source_documents"]:
print("Fonte:", d.metadata.get("source"), "pag.", d.metadata.get("page"))
Il parametro k=4 indica quanti brani recuperare: aumentalo se le risposte risultano incomplete, riducilo se diventano confuse. Impostare temperature=0 rende le risposte piu' deterministiche e aderenti alle fonti.
Prompt da provare
Una volta funzionante, il sistema si presta a molti usi. Ecco alcune richieste tipiche:
Riassumi in cinque punti gli obblighi previsti dal documento per la parte venditrice.
Elenca tutte le scadenze e le date citate nei documenti, con la relativa pagina.
Ci sono clausole che parlano di penali? Riportane il testo esatto e dove si trova.
Il risultato atteso e' una risposta sintetica e pertinente, accompagnata dall'indicazione del file e della pagina di provenienza, cosi' da poter verificare sempre la fonte.
Errori comuni e come risolverli
- "Connection refused" verso localhost:11434: Ollama non e' in esecuzione. Avvialo (all'apertura dell'app o con
ollama serve) e riprova. - Il modello risponde "Non trovo questa informazione" anche quando c'e': probabilmente i chunk pertinenti non vengono recuperati. Aumenta
k, riduci la dimensione dei chunk, oppure verifica che il PDF non sia una semplice scansione (immagine) priva di testo estraibile: in quel caso serve un passaggio OCR. - Risposte lente: e' normale senza GPU. Usa un modello piu' piccolo, riduci
max_new_tokensok, e ricorda che il primo avvio e' sempre il piu' lento. - "ModuleNotFoundError": hai dimenticato di attivare l'ambiente virtuale o di installare un pacchetto. Ricontrolla il passo 2.
Varianti, casi avanzati e quando non usare questo approccio
Per archivi molto grandi conviene ricalcolare l'indice solo quando i documenti cambiano, e valutare database vettoriali piu' robusti come Qdrant o pgvector. Se i tuoi PDF sono scansioni, aggiungi un passaggio OCR (per esempio con Tesseract) prima dell'indicizzazione. Per migliorare la qualita' del recupero puoi introdurre tecniche di re-ranking o la ricerca ibrida (parole chiave piu' similarita' semantica). Se invece hai bisogno della massima qualita' di risposta e i documenti non sono riservati, puo' avere senso usare modelli cloud piu' potenti tramite API, accettando il costo e la trasmissione dei dati. Il RAG locale, insomma, brilla quando contano privacy, costi e controllo; cede il passo quando serve la punta massima di ragionamento su testi molto complessi. Per approfondire, la documentazione di LangChain e quella di Chroma offrono ricette pronte per ognuno di questi scenari.




