Cos'è il RAG e perché serve
I Large Language Model hanno una conoscenza impressionante, ma con limiti fondamentali: le informazioni sono "congelate" al momento del training, non citano le fonti, e possono inventare fatti (hallucination). Il Retrieval Augmented Generation (RAG) risolve questi problemi combinando due capacità:
- Retrieval — cercare informazioni rilevanti in una base di conoscenza
- Generation — usare quelle informazioni come contesto per generare una risposta
In pratica, invece di chiedere al modello "cosa sai su X?", gli dici "ecco i documenti rilevanti su X, rispondi basandoti su questi". Il risultato è una risposta ancorata a dati reali, aggiornati e verificabili.
Quando usare il RAG: Ogni volta che il modello deve rispondere con informazioni specifiche del tuo dominio — documentazione interna, FAQ, knowledge base, cataloghi prodotti, normative, manuali tecnici.
Architettura: il pipeline completo
Un sistema RAG ha due fasi: l'indicizzazione (offline, una tantum) e la query (online, ad ogni richiesta).
Ogni fase ha le sue scelte architetturali e trade-off. Vediamole nel dettaglio.
Strategie di chunking
Il chunking è il processo di dividere i documenti in frammenti gestibili. È forse la fase più sottovalutata del RAG, ma ha un impatto enorme sulla qualità del retrieval.
Fixed-size chunking
Il metodo più semplice: dividere il testo in blocchi di dimensione fissa (es. 512 token) con un overlap (es. 50 token) per non perdere informazioni ai confini.
# Chunking a dimensione fissa con overlap
def fixed_chunk(text, chunk_size=512, overlap=50):
words = text.split()
chunks = []
i = 0
while i < len(words):
chunk = " ".join(words[i:i + chunk_size])
chunks.append(chunk)
i += chunk_size - overlap
return chunkspython
Pro: semplice, prevedibile, veloce. Contro: ignora la struttura del testo — può tagliare a metà una frase o un concetto.
Semantic chunking
Divide il testo in base al significato, non alla dimensione. Usa gli embedding per identificare dove il tema cambia: quando la similarità tra frasi consecutive scende sotto una soglia, si crea un nuovo chunk.
Pro: ogni chunk è semanticamente coerente. Contro: più lento, dimensioni variabili (alcuni chunk possono essere molto grandi o molto piccoli).
Recursive chunking
L'approccio usato da LangChain: prova a dividere prima per paragrafi (\n\n), poi per frasi (.\n), poi per spazi, fino a ottenere chunk della dimensione target. Rispetta la struttura naturale del testo.
# RecursiveCharacterTextSplitter di LangChain
from langchain.text_splitter import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200,
separators=["\n\n", "\n", ". ", " ", ""]
)
chunks = splitter.split_text(document)python
Document-aware chunking
Per documenti strutturati (Markdown, HTML, codice), usare parser specifici che rispettano la struttura: dividere per sezioni, funzioni, classi. Un chunk che contiene una funzione intera è molto più utile di uno che ne contiene metà.
Embedding models
Gli embedding trasformano il testo in vettori numerici che catturano il significato semantico. Due testi con significato simile avranno vettori vicini nello spazio. Questo è il fondamento del retrieval semantico.
| Modello | Dimensioni | Max token | Note |
|---|---|---|---|
| text-embedding-3-large (OpenAI) | 3072 | 8191 | Ottima qualità, API semplice |
| embed-v4 (Cohere) | 1024 | 512 | Multilingue eccellente |
| jina-embeddings-v3 | 1024 | 8192 | Lungo contesto, multilingue |
| BGE-M3 (BAAI) | 1024 | 8192 | Open source, dense+sparse |
| nomic-embed-text | 768 | 8192 | Open source, efficiente |
| Voyage 3 | 1024 | 32000 | Ottimizzato per RAG, lungo contesto |
# Generare embedding con OpenAI
from openai import OpenAI
client = OpenAI()
response = client.embeddings.create(
model="text-embedding-3-large",
input=["Come funziona il retrieval augmented generation?"]
)
vector = response.data[0].embedding # lista di 3072 floatpython
Vector databases
I vector database sono ottimizzati per memorizzare e cercare vettori ad alta dimensionalità. Usano algoritmi di Approximate Nearest Neighbor (ANN) come HNSW o IVF per cercare i vettori più simili in millisecondi, anche su milioni di documenti.
Pinecone
Fully managed, serverless. Zero infrastruttura. Ottimo per iniziare. Pricing basato su storage + query.
Weaviate
Open source, hybrid search nativa (dense+BM25). GraphQL API. Moduli di vectorizzazione integrati.
Qdrant
Open source, scritto in Rust. Eccellente performance. Filtri avanzati, payload ricchi. API REST e gRPC.
Chroma
Open source, embedding-first. Semplicissimo per prototipi. Gira in-memory o persistente.
pgvector
Estensione PostgreSQL. Perfetto se hai già Postgres. Niente infrastruttura aggiuntiva. Buono fino a ~1M vettori.
Milvus
Open source, scalabile. Supporta miliardi di vettori. Architettura distribuita. Più complesso da gestire.
# Esempio: RAG con pgvector e psycopg2
import psycopg2
from pgvector.psycopg2 import register_vector
conn = psycopg2.connect("dbname=myapp")
register_vector(conn)
# Cercare i 5 chunk più simili alla query
cur = conn.cursor()
cur.execute("""
SELECT content, 1 - (embedding <=> %s) AS similarity
FROM documents
ORDER BY embedding <=> %s
LIMIT 5
""", (query_embedding, query_embedding))
chunks = cur.fetchall()python
Hybrid search: dense + sparse
La ricerca puramente semantica (dense) ha un punto debole: può mancare match esatti su termini tecnici, nomi propri, codici prodotto. La ricerca ibrida combina:
- Dense retrieval (embedding) — cattura il significato semantico
- Sparse retrieval (BM25/TF-IDF) — cattura le corrispondenze lessicali esatte
I risultati vengono combinati con Reciprocal Rank Fusion (RRF) o un semplice punteggio pesato:
Un valore di alpha = 0.7 (favorire il semantico) è un buon punto di partenza. Ma il valore ottimale dipende dal dominio: per documentazione tecnica con molti acronimi, alpha = 0.5 funziona meglio.
Reranking
Il retrieval iniziale è veloce ma approssimativo (usa embedding compressi). Il reranking prende i top-K risultati e li ri-ordina con un modello più sofisticato che esamina query e documento insieme (cross-encoder), non solo i loro vettori.
- Cohere Rerank — API managed, eccellente qualità, multilingue
- Cross-encoder (Hugging Face) — modelli come
ms-marco-MiniLM, self-hosted - ColBERT — approccio ibrido late-interaction, buon compromesso velocità/qualità
# Reranking con Cohere
import cohere
co = cohere.Client("YOUR_API_KEY")
results = co.rerank(
query="Come configurare il RAG con pgvector?",
documents=[chunk.text for chunk in retrieved_chunks],
model="rerank-v3.5",
top_n=5
)
for r in results.results:
print(f"Score: {r.relevance_score:.3f} | {r.document.text[:80]}")python
Evaluation: misurare la qualità
Un sistema RAG va valutato su più dimensioni:
| Metrica | Cosa misura | Come |
|---|---|---|
| Context Relevance | I chunk recuperati sono pertinenti alla domanda? | LLM-as-judge o annotazione umana |
| Faithfulness | La risposta è fedele ai chunk (non inventa)? | Verifica claim-by-claim |
| Answer Relevance | La risposta risponde effettivamente alla domanda? | LLM-as-judge |
| Recall@K | Il chunk giusto è nei top-K risultati? | Set di valutazione con ground truth |
Framework come RAGAS e DeepEval automatizzano queste metriche usando un LLM come giudice.
Software e framework
LangChain
Il framework più popolare per applicazioni LLM. Offre astrazioni per document loaders, text splitters, embedding, vector stores e chain di RAG. Enorme ecosystem con centinaia di integrazioni. Criticato per l'eccesso di astrazione, ma ottimo per prototipi rapidi.
LlamaIndex
Focalizzato specificamente sul RAG. Offre "data connectors" per decine di fonti dati, strategie di indicizzazione avanzate (tree index, keyword index, knowledge graph), e un query engine sofisticato con sub-question decomposition.
Haystack (deepset)
Framework enterprise per NLP/RAG. Pipeline component-based, ottimo per sistemi di produzione complessi. Supporto nativo per hybrid retrieval e evaluation.
Best practices e anti-pattern
- Includi metadati nei chunk (titolo del documento, sezione, data) — usali per filtrare il retrieval
- Testa con domande reali dei tuoi utenti, non domande inventate
- Usa il reranking sempre — il costo è minimo e la qualità migliora significativamente
- Monitora le query senza risultati — rivelano buchi nella knowledge base
- Considera query rewriting — riformulare la domanda dell'utente prima del retrieval
- Chunk troppo grandi — diluiscono il segnale e sprecano context window
- Nessun overlap — informazioni ai confini dei chunk vengono perse
- Ignorare il preprocessing — header, footer, boilerplate degradano la qualità degli embedding
- Un unico indice per tutto — separare per tipo di documento migliora il retrieval
- Fidarsi ciecamente del RAG — il modello può ancora allucinare anche con contesto; usa guardrails
Tecniche avanzate
Query Decomposition
Per domande complesse, scomporle in sotto-domande e fare retrieval separato per ognuna. Esempio: "Confronta i prezzi e le performance di Pinecone e Qdrant" diventa due query separate, una per i prezzi e una per le performance.
Parent Document Retrieval
Indicizzare chunk piccoli (per precisione nel retrieval) ma passare al modello il documento genitore più grande (per contesto completo). Il meglio di entrambi i mondi.
Self-RAG
Il modello decide autonomamente se ha bisogno di fare retrieval, valuta la rilevanza dei documenti recuperati, e giudica se la sua risposta è fedele al contesto. Un ciclo di auto-miglioramento.
Collegamento con altri temi
Il RAG non esiste in isolamento. Per costruire sistemi completi:
- Connetti il retrieval a tool esterni con MCP
- Integra il RAG in un sistema agentico che decide quando e cosa cercare
- Ottimizza i prompt per il RAG nella sezione Prompt Engineering
- Vedi come ChipsBot usa il RAG nel caso studio