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_size se 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!