RAG Basics
Come far rispondere l'AI usando i tuoi documenti aziendali. Pipeline completa dal chunking alla ricerca semantica, con l'esempio di 200 linee guida cliniche.
RAG Basics
La Dott.ssa Elena Bianchi ha un problema comune: 200 linee guida cliniche in PDF sparse in cartelle e sottocartelle. I collaboratori non le trovano, i nuovi specializzandi non sanno che esistono. RAG trasforma quel caos in un assistente che risponde citando le fonti esatte.
Il problema
L'AI generica non conosce i tuoi documenti. Se chiedi "Qual è il protocollo per l'ipertensione resistente?", l'AI risponde con conoscenza generica trovata nel training. Non sa che l'ambulatorio della Dott.ssa Bianchi ha un protocollo interno aggiornato a gennaio 2026 con dosaggi specifici e percorsi personalizzati.
RAG (Retrieval-Augmented Generation) risolve questo: prima cerca nei tuoi documenti, poi genera la risposta basandosi su quello che ha trovato. Non inventa — cita.
Come funziona RAG
Il processo si divide in tre fasi distinte:
FASE 1 — INDICIZZAZIONE (una tantum)
Documenti PDF → Divisione in chunk → Calcolo embeddings → Salvataggio nel database vettoriale
FASE 2 — RETRIEVAL (ad ogni domanda)
Domanda utente → Embedding della domanda → Ricerca semantica → Top-K chunk rilevanti
FASE 3 — GENERATION (ad ogni domanda)
Chunk trovati + Domanda originale → Prompt aumentato → Risposta con citazioniLa fase 1 si fa una volta. Le fasi 2 e 3 avvengono in tempo reale ad ogni domanda.
Chunking: come spezzare i documenti
Il chunking è la decisione più importante di tutta la pipeline. Chunk troppo piccoli (100 parole) perdono contesto — il modello non capisce di cosa si parla. Chunk troppo grandi (2000 parole) aggiungono rumore — il modello si distrae con informazioni irrilevanti.
def crea_chunk(testo, dimensione=500, sovrapposizione=50):
"""
Spezza un documento in chunk con overlap.
L'overlap garantisce che nessun concetto venga tagliato a metà.
"""
parole = testo.split()
chunk_lista = []
inizio = 0
while inizio < len(parole):
fine = inizio + dimensione
chunk = " ".join(parole[inizio:fine])
chunk_lista.append(chunk)
# Avanza meno della dimensione per creare sovrapposizione
inizio += dimensione - sovrapposizione
return chunk_lista
# Esempio: linea guida ipertensione (circa 3000 parole)
with open("linea_guida_ipertensione.txt", "r") as file:
testo_completo = file.read()
chunk = crea_chunk(testo_completo, dimensione=500, sovrapposizione=50)
print(f"Documento diviso in {len(chunk)} chunk")
# Risultato: circa 7 chunk con sovrapposizioneGenerare embeddings
Un embedding è una rappresentazione numerica del significato di un testo. Frasi con significato simile hanno embeddings vicini nello spazio vettoriale. Questo permette di trovare chunk rilevanti anche se non contengono le parole esatte della domanda.
from openai import OpenAI
client = OpenAI(api_key="sk-la-tua-chiave")
def calcola_embedding(testo):
"""Converte un testo in un vettore numerico di 1536 dimensioni."""
risposta = client.embeddings.create(
model="text-embedding-3-small",
input=testo
)
return risposta.data[0].embedding
# Indicizza tutti i chunk
database_vettoriale = []
for i, chunk in enumerate(chunk):
vettore = calcola_embedding(chunk)
database_vettoriale.append({
"id": i,
"testo": chunk,
"embedding": vettore,
"fonte": "linea_guida_ipertensione.pdf"
})
print(f"Indicizzati {len(database_vettoriale)} chunk")Ricerca e generazione
Ora possiamo mettere insieme tutto: la domanda dell'utente diventa un embedding, lo confrontiamo con i chunk indicizzati e usiamo i più rilevanti per generare una risposta precisa:
import numpy as np
def coseno_similarita(vec_a, vec_b):
"""Calcola la similarità tra due vettori (0 = diversi, 1 = identici)."""
return np.dot(vec_a, vec_b) / (np.linalg.norm(vec_a) * np.linalg.norm(vec_b))
def cerca_e_rispondi(domanda, database, top_k=3):
"""Pipeline completa: ricerca semantica + generazione con contesto."""
# Fase 2: Retrieval
embedding_domanda = calcola_embedding(domanda)
# Calcola similarità con ogni chunk
risultati = []
for record in database:
score = coseno_similarita(embedding_domanda, record["embedding"])
risultati.append((score, record))
# Prendi i top-K chunk più rilevanti
risultati.sort(key=lambda x: x[0], reverse=True)
chunk_rilevanti = risultati[:top_k]
# Costruisci il contesto
contesto = ""
for score, record in chunk_rilevanti:
contesto += f"[Fonte: {record['fonte']}]\n{record['testo']}\n\n"
# Fase 3: Generation con contesto
risposta = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": """Rispondi SOLO in base al contesto fornito.
Se il contesto non contiene la risposta, dì "Non ho trovato informazioni su questo argomento nelle linee guida."
Cita sempre la fonte tra parentesi quadre."""},
{"role": "user", "content": f"Contesto:\n{contesto}\nDomanda: {domanda}"}
]
)
return risposta.choices[0].message.content
# Uso reale nell'ambulatorio
risposta = cerca_e_rispondi(
"Qual è il dosaggio iniziale consigliato per l'ipertensione resistente?",
database_vettoriale
)
print(risposta)
# "Secondo le linee guida [linea_guida_ipertensione.pdf], il dosaggio
# iniziale consigliato è..."Qualità: cosa la determina
La qualità di un sistema RAG dipende più dalla preparazione dei dati che dal modello scelto. Quattro fattori chiave:
1. DIMENSIONE CHUNK: 300-500 parole è il range ottimale per la maggior parte dei documenti.
Troppo piccolo = contesto perso. Troppo grande = rumore.
2. OVERLAP: 10-15% della dimensione del chunk. Garantisce che concetti
a cavallo tra due chunk non vengano persi.
3. METADATA: Aggiungi fonte, data, categoria a ogni chunk. Permette di
filtrare i risultati (es. "solo linee guida aggiornate dopo il 2024").
4. RELEVANCE THRESHOLD: Non usare tutti i top-K. Scarta i chunk con
similarità sotto 0.75 — meglio dire "non so" che rispondere male.Quando usare RAG
RAG è la soluzione giusta quando hai documenti proprietari che l'AI non conosce:
- ●Studio legale: archivio contratti, giurisprudenza interna, pareri precedenti
- ●Ambulatorio medico: linee guida cliniche, protocolli interni, schede farmaci
- ●Bottega artigiana: catalogo prodotti, schede materiali, storico preventivi
- ●Studio fiscale: circolari AdE, risoluzioni, normativa commentata
- ●Qualsiasi azienda: FAQ interne, documentazione tecnica, manuali operativi
Da ricordare
- ●RAG permette all'AI di rispondere usando i tuoi documenti, non la conoscenza generica
- ●Il processo ha 3 fasi: indicizzazione (una tantum), retrieval e generation (in tempo reale)
- ●Il chunking è la decisione più critica: 300-500 parole con overlap del 10-15%
- ●Gli embeddings catturano il significato, non le parole esatte — la ricerca è semantica
- ●Aggiungi sempre metadata ai chunk per filtrare e citare le fonti
- ●Meglio rispondere "non so" che generare una risposta basata su chunk poco rilevanti