Introduzione al Retrieval‑Augmented Generation
Chi sviluppa un chatbot aziendale che deve conoscere manuali, documenti interni o ticket di supporto non può fare a meno di una pipeline RAG. Il termine “Retrieval‑Augmented Generation” registra circa 3.600 ricerche mensili in Germania, con una tendenza in crescita. Il concetto è semplice: invece di far indovinare a un modello di linguaggio, si forniscono i frammenti di testo più pertinenti alla domanda.
In pratica, però, il percorso è disseminato di ostacoli: scelte sbagliate nella dimensione dei chunk, database vettoriali vuoti al primo test e problemi di compatibilità tra librerie. Questo tutorial mostra, passo dopo passo, come superare tali difficoltà e costruire una pipeline RAG completa, senza ricorrere a infrastrutture cloud: un normale laptop da sviluppatore è sufficiente.
Perché scegliere RAG ora?
RAG combina due sistemi distinti: una componente di ricerca (retrieval) e un modello generativo (generation). Il modello non deve memorizzare tutto il know‑how, ma recupera al volo i passaggi più rilevanti da una fonte esterna e li usa come contesto.
I vantaggi rispetto al semplice prompting includono:
- Aggiornamento continuo: basta aggiornare l’indice quando un manuale cambia.
- Costi ridotti rispetto al fine‑tuning: non è necessario ri‑addestrare il modello.
- Tracciabilità: è possibile restituire le fonti dei frammenti usati nella risposta.
RAG è oggi lo standard per ricerca aziendale, supporto tecnico, ricerca legale e knowledge base interne. Modelli come GPT, Claude, Llama o DeepSeek non conoscono i vostri PDF o pagine Confluence finché non li mostrate loro tramite RAG.
RAG vs. contesto esteso
Modelli moderni gestiscono centinaia di migliaia di token, ma inserire interi manuali in ogni prompt è poco pratico per tre motivi:
- Costi: ogni richiesta con un contesto enorme è più costosa, indipendentemente dall’utilizzo effettivo.
- Precisione: l’attenzione si diluisce su sequenze lunghe, riducendo l’affidabilità.
- Tracciabilità: è impossibile indicare quali parti hanno influenzato la risposta.
Per documenti singoli e ben delimitati, l’approccio diretto può funzionare; ma per centinaia o migliaia di file in evoluzione, RAG è l’unica soluzione scalabile. Molti sistemi combinano entrambi: RAG filtra i documenti più pertinenti, poi il modello riceve un contesto più ampio ma ancora gestibile.
Prerequisiti: software, versioni e hardware
Prima di iniziare, assicuratevi di avere gli strumenti seguenti (versioni valide ad agosto 2026):
- Python 3.11 o superiore
- langchain ≥ 1.3.18 e langchain‑core ≥ 1.6.1
- sentence‑transformers (ultima versione su PyPI)
- Una vector database: chromadb per test locali o Qdrant per ambienti più robusti
- Ollama (ultima versione) per eseguire modelli LLM locali; in alternativa API OpenAI, Claude o Gemini
- Almeno 8 GB di RAM per piccoli corpora; 16 GB consigliati per dataset di qualche centinaio di MB
- GPU con minimo 8 GB VRAM per modelli più grandi (opzionale ma consigliata)
Chi ha già Ollama installato può partire direttamente dallo step 9. Per chi parte da zero, il tutorial “Ollama Setup: Lokale LLMs in 12 Schritten” fornisce una guida completa, e le istruzioni ufficiali sono disponibili sul sito di Ollama.
Pianificazione hardware
Le fasi di chunking ed embedding sono intensive su CPU (o GPU), ma avvengono solo quando i dati cambiano. La generazione, invece, è eseguita ad ogni richiesta utente e determina la quantità di RAM/VRAM da tenere disponibile in maniera costante. Un modello quantizzato da 7‑8 M di parametri funziona su laptop senza GPU; per modelli più grandi è consigliata una GPU con almeno 16 GB VRAM.
Architettura della pipeline RAG
La pipeline si suddivide in sei fasi:
- Dati di ingresso: caricamento di PDF, file di testo, pagine web, ecc.
- Chunking: suddivisione in blocchi di dimensione ottimale.
- Embedding: conversione dei chunk in vettori con sentence‑transformers.
- Indicizzazione: salvataggio dei vettori nella vector database.
- Retrieval: trasformazione della query in vettore e ricerca dei top‑k chunk.
- Generation: composizione del prompt (query + chunk) e invio al modello LLM.
Le prime quattro fasi sono offline (eseguite una tantum o al cambiamento dei dati). Le ultime due sono online, ovvero per ogni interazione utente.
Guida passo‑passo: i 14 step
Step 1‑2: Creare il progetto e installare le dipendenze
Aprite il terminale e create una cartella dedicata:
mkdir rag-pipeline && cd rag-pipeline
python3 -m venv venv
source venv/bin/activate
pip install "langchain>=1.3.18" "langchain-core>=1.6.1" sentence-transformers chromadb ollama
Con la virtual environment attiva, tutti i pacchetti saranno isolati.
Step 3: Caricare i documenti
Utilizzate i loader di LangChain per leggere PDF, file .txt o HTML. Esempio con PDF:
from langchain.document_loaders import PyPDFLoader
loader = PyPDFLoader("manuale_prodotto.pdf")
documents = loader.load()
Per più file, potete iterare su una lista di percorsi e concatenare i risultati.
Step 4: Chunking dei documenti
Il chunking ideale dipende dal modello LLM. Con modelli da 8 k token, consigliamo chunk di 500‑800 parole. LangChain offre RecursiveCharacterTextSplitter:
from langchain.text_splitter import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter(chunksize=800, chunkoverlap=100)
chunks = splitter.split_documents(documents)
L’overlap garantisce che le informazioni di confine non vadano perse.
Step 5‑6: Generare gli embedding e indicizzarli
Usiamo un modello sentence‑transformers locale, ad esempio all-MiniLM-L6-v2:
from sentence_transformers import SentenceTransformer
embedder = SentenceTransformer("all-MiniLM-L6-v2")
embeddings = [embedder.encode(chunk.page_content) for chunk in chunks]
Creiamo poi il client Chroma e inseriamo i vettori:
import chromadb
client = chromadb.Client()
collection = client.create_collection(name="rag-index")
for i, (chunk, vector) in enumerate(zip(chunks, embeddings)):
collection.add(
ids=[str(i)],
documents=[chunk.page_content],
metadatas=[{"source": chunk.metadata.get("source", "unknown")}],
embeddings=[vector.tolist()]
)
Se preferite Qdrant, la sintassi è analoga ma richiede un server in esecuzione.
Step 7: Configurare il modello LLM locale con Ollama
Assicuratevi che Ollama sia avviato e che il modello desiderato sia disponibile, ad esempio llama3:
ollama pull llama3
In Python, la connessione è così semplice:
from langchain.llms import Ollama
llm = Ollama(model="llama3")
Step 8: Definire la catena di retrieval‑generation
LangChain fornisce RetrievalQAChain. Configuriamola:
from langchain.chains import RetrievalQAChain
from langchain.vectorstores import Chroma
vectorstore = Chroma(collectionname="rag-index")
retriever = vectorstore.asretriever(search_kwargs={"k": 4})
qachain = RetrievalQAChain.fromchain_type(
llm=llm,
chain_type="stuff",
retriever=retriever,
returnsourcedocuments=True
)
Il parametro k definisce quanti chunk recuperare per ogni query.
Step 9‑10: Testare la pipeline con una domanda
Eseguiamo una prima prova:
query = "Come posso resettare la password del portale?"
result = qa_chain(query)
print("Risposta:", result["answer"])
print("Fonti:", [doc.metadata["source"] for doc in result["source_documents"]])
Se l’indice è vuoto o i chunk non sono stati inseriti correttamente, la risposta sarà generica o verrà mostrato un errore di ricerca.
Step 11: Gestire errori comuni
Tra i problemi più frequenti troviamo:
- Database vuoto: verificare che
collection.count()restituisca un valore > 0. - Dimensione del chunk errata: ridurre il
chunk_sizese il modello supera il limite di token. - Incompatibilità di versione: controllare le dipendenze di LangChain e aggiornare con
pip install --upgrade. - GPU non riconosciuta: assicurarsi che i driver CUDA siano installati e che
torch.cuda.is_available()ritorni True.
Step 12: Ottimizzare le performance
Per ridurre la latenza:
- Cacheare gli embedding dei documenti più usati.
- Usare un indice annidato (IVF‑PQ) in Qdrant per ricerche più rapide.
- Quantizzare il modello LLM con Ollama (es.
--quantize 4bit).
Step 13: Deploy su ambiente di test
Impostate un semplice server FastAPI per esporre la pipeline:
from fastapi import FastAPI, Request
app = FastAPI()
@app.post("/ask")
async def ask(request: Request):
data = await request.json()
query = data["question"]
result = qa_chain(query)
return {"answer": result["answer"], "sources": [doc.metadata["source"] for doc in result["source_documents"]]}
Eseguite con uvicorn main:app --reload. Ora potete interrogare la vostra RAG tramite HTTP.
Step 14: Manutenzione e aggiornamento dei dati
Quando un manuale viene modificato, basta ripetere gli step 3‑6 per i file interessati e sovrascrivere i chunk corrispondenti nell’indice. LangChain permette di aggiornare singoli documenti usando collection.update(), evitando di ricreare l’intero indice.
Conclusioni
Seguendo questi 14 passaggi avrete una pipeline RAG funzionante, pronta per essere adattata a qualsiasi corpus interno. La soluzione è completamente locale, non richiede costi di cloud e garantisce tracciabilità delle risposte. Con una configurazione hardware modesta è possibile completare l’intero setup in circa 90 minuti, includendo installazione, indicizzazione e primo test.
Ricordate di monitorare le metriche di latenza e di aggiornare periodicamente l’indice per mantenere le risposte accurate e pertinenti. Buon lavoro con la vostra nuova capacità di ricerca intelligente!
