Torna al blog
Valutare un RAG: Metriche, Dataset di Eval e LLM-as-a-Judge

Valutare un RAG: Metriche, Dataset di Eval e LLM-as-a-Judge

9 min di lettura2 visualizzazioni
di Marco Salmaso
Un sistema basato su LLM non si valuta con cinque domande scelte a mano. Serve un dataset di eval, metriche separate per retrieval e generazione, e un uso consapevole dell'LLM-as-a-Judge. Ecco un flusso di lavoro ripetibile, con esempi di codice e una lista di errori da evitare.

Ogni volta che si mette in piedi un sistema basato su LLM — un assistente interno, un chatbot sui documenti aziendali, un RAG sul catalogo prodotti — arriva prima o poi la domanda: «funziona?». La risposta tipica è una demo con cinque domande scelte a mano, a cui il sistema risponde bene. È una risposta rassicurante e quasi inutile.

Il problema non è la buona fede di chi la fa: è che una demo non è una misura. Se le domande le hai scelte tu, se le hai scelte perché sapevi che il sistema le gestiva, se le hai scelte dopo aver visto gli output, non stai valutando il sistema: stai raccontando una storia. Questo articolo prova a mettere ordine su come si valuta un RAG in modo ripetibile, con strumenti semplici e senza trasformare la valutazione in un progetto infinito.

Perché «a occhio sembra funzionare» non è una valutazione

Un sistema RAG ha due parti che possono fallire in modo indipendente: il retrieval (trova i documenti giusti?) e la generazione (scrive una risposta corretta e fedele a quei documenti?). Un output sbagliato può dipendere da entrambe, e senza separarle non sai cosa stai aggiustando.

C'è poi il problema della ripetibilità. Gli LLM non sono deterministici: la stessa domanda può dare risposte diverse. Se non fissi un insieme di casi e non salvi i risultati, non puoi dire se una modifica ha migliorato o peggiorato il sistema. Puoi solo dire che ti è sembrato meglio, che è esattamente il tipo di giudizio che vorresti evitare quando il sistema tocca dati veri.

Costruire un dataset di eval: piccolo, ma rappresentativo

Non servono migliaia di esempi. Un insieme di 50-100 casi ben scelti è già sufficiente per accorgersi dei problemi grossi — sempre che i casi siano rappresentativi e non tutti facili. L'idea è avere un golden set: una raccolta stabile di domande, con il comportamento atteso, che usi come riferimento nel tempo.

Da dove prendere i casi

  • Dai log reali, se il sistema è già in uso: le domande che le persone fanno davvero sono la fonte più onesta. Anonimizza e riscrivi dove serve.
  • Dai documenti: per ogni sezione importante della knowledge base, scrivi una domanda a cui quella sezione dovrebbe rispondere.
  • Dai casi limite: domande ambigue, domande con refusi, domande che mescolano due argomenti.
  • Dai casi fuori dominio: domande a cui il sistema non deve rispondere. Sono i più trascurati e i più utili: un RAG che risponde a tutto è peggio di uno che ammette di non sapere.

Cosa mettere in ogni riga

Una tabella semplice basta e avanza. Un esempio di struttura:

iddomandatipodocumento attesorisposta attesa / criteri
001«Come si richiede una sostituzione?»fattualeresi.md#sostituzionideve citare la procedura e i tempi
002«Quanto costa il modello X?»fuori dominionessunodeve dichiarare di non avere l'informazione
003«Problema con ordine e fattura insieme»multi-argomento2 documentideve coprire entrambi i punti

Due regole pratiche:

  1. Il golden set non si tocca per far salire il punteggio. Se lo modifichi ogni volta che una modifica peggiora i risultati, stai solo spostando il bersaglio.
  2. Versiona il dataset insieme al codice, così sai a quale versione corrisponde un certo punteggio.

Metriche per il retrieval

Prima di guardare le risposte, guarda se il sistema recupera i documenti giusti. Se il documento corretto non entra nel contesto, nessun prompt salverà la generazione.

Tre metriche semplici e sufficienti nella maggior parte dei casi:

  • Hit rate@k (o recall@k): la frazione di domande per cui almeno un documento corretto compare nei primi k risultati.
  • MRR (Mean Reciprocal Rank): la media di 1/rank del primo documento corretto. Premia i documenti giusti che arrivano in alto.
  • Precision@k: quanti dei k documenti recuperati sono effettivamente utili. Utile per accorgersi se stai riempiendo il contesto di rumore.

In formule, in forma testuale:

hit_rate@k = casi con almeno un documento corretto nei primi k / casi totali
MRR        = media di (1 / posizione del primo documento corretto)

Un frammento di codice per calcolarle, assumendo che ogni caso abbia un insieme di id attesi e una lista di id recuperati:

def hit_rate_at_k(cases, k):
    ok = 0
    for c in cases:
        retrieved = c["retrieved"][:k]
        if any(doc in c["expected"] for doc in retrieved):
            ok += 1
    return ok / len(cases)

def mrr(cases):
    total = 0.0
    for c in cases:
        rank = None
        for i, doc in enumerate(c["retrieved"], start=1):
            if doc in c["expected"]:
                rank = i
                break
        total += 1 / rank if rank else 0.0
    return total / len(cases)

Sui numeri: se su 60 casi il tuo hit rate@5 è, per esempio, 0,72, il primo dato interessante non è il valore in sé ma la lista dei 17 casi mancati. Guardarli uno per uno è spesso più utile di qualsiasi media.

Metriche per la generazione

Qui non esiste un numero unico. Le metriche classiche di confronto tra testi — exact match, similarità a livello di token — funzionano male su risposte discorsive in italiano: due frasi corrette possono non condividere quasi nessuna parola.

Quello che conta davvero si riduce a poche dimensioni:

  • Fedeltà (faithfulness): la risposta è supportata dai documenti recuperati, o inventa?
  • Pertinenza: risponde alla domanda posta, o va fuori tema?
  • Correttezza: rispetto a una risposta attesa o a criteri espliciti, il contenuto è giusto?
  • Completezza: manca qualche punto richiesto?
  • Forma: cita le fonti? usa il tono giusto? rispetta i limiti di lunghezza?

Per i casi fuori dominio la metrica è binaria: ha rifiutato o no. È il tipo di metrica più facile da calcolare e più facile da ignorare, ed è un errore.

LLM-as-a-Judge: come usarlo senza autoinganni

Valutare a mano 60 risposte è fattibile una volta. Farlo a ogni modifica del prompt, del chunking o del modello, no. Da qui l'idea di far valutare le risposte a un LLM più potente, con una rubrica esplicita.

Funziona, ma ha distorsioni note e va usato con attenzione:

  • Bias di posizione: in un confronto a coppie, tende a preferire la prima risposta. Randomizza l'ordine.
  • Bias di verbosità: tende a preferire risposte lunghe. Esplicitalo nella rubrica come penalità.
  • Auto-preferenza: un modello giudica meglio i testi prodotti da modelli simili a sé. Se puoi, usa un giudice diverso dal generatore.
  • Instabilità: due esecuzioni possono dare giudizi diversi. Ripeti e guarda la varianza, oppure usa una scala ridotta.

Una rubrica secca, con pochi livelli, è meglio di una scala 1-10 dove i gradi intermedi non significano nulla. Esempio di prompt di valutazione, da adattare:

JUDGE_PROMPT = """Sei un valutatore. Ricevi una DOMANDA, dei DOCUMENTI
recuperati e una RISPOSTA. Valuta la RISPOSTA su due assi, con un
intero da 1 a 3 (1 = insufficiente, 2 = parziale, 3 = buono):

- FEDELTA: ogni affermazione e' supportata dai DOCUMENTI?
- PERTINENZA: la RISPOSTA risponde alla DOMANDA?

Penalizza le risposte inutilmente lunghe. Se la domanda e' fuori
dominio, il punteggio massimo si ottiene solo se la RISPOSTA dichiara
esplicitamente di non avere l'informazione.

Rispondi solo in JSON: {{"fedelta": int, "pertinenza": int,
"motivo": "una frase"}}

DOMANDA: {question}
DOCUMENTI: {context}
RISPOSTA: {answer}
"""

Due contromisure che valgono più di qualsiasi prompt furbo: verifica un campione a mano (20 risposte sono sufficienti per capire se il giudice è allineato al tuo giudizio) e non usare lo stesso modello per generare e giudicare, quando puoi evitarlo.

Un flusso di lavoro ripetibile

La valutazione diventa utile quando è noiosa: stessa procedura, ogni volta. Una struttura minima potrebbe essere questa:

eval/
  dataset/golden_v1.jsonl
  runs/2025-06-01_baseline/
  runs/2025-06-03_chunk400_overlap80/
  judge.py
  run_eval.py

E il ciclo è sempre lo stesso:

  1. Fissa il golden set e il codice di valutazione.
  2. Esegui il sistema su tutti i casi e salva output, documenti recuperati e metriche.
  3. Guarda il dettaglio dei casi peggiori, non solo la media.
  4. Cambia una cosa alla volta (chunking, k, prompt, modello).
  5. Riesegui e confronta.

Il confronto tra due configurazioni è la parte che dà più informazioni. Una tabella di esempio, con numeri inventati a scopo illustrativo:

Configurazionehit@5fedeltà mediarifiuti correttilatenza p95
baseline, chunk 8000,722,49/151,1 s
chunk 400 + overlap 800,812,613/151,4 s

Nota una cosa: il secondo sistema è migliore su tutte le metriche di qualità ma più lento. La decisione non è tecnica, è di prodotto: dipende da quanto pesa quel mezzo secondo in più per chi lo usa.

Errori comuni

  • Valutare solo i casi facili. Se tutti i punteggi sono alti, il dataset è troppo semplice.
  • Cambiare il dataset per far salire il numero. È il modo più rapido per non imparare nulla.
  • Guardare solo la media. La distribuzione dei casi peggiori contiene quasi sempre tutte le informazioni utili.
  • Confondere retrieval e generazione. Un recall basso non si risolve con un prompt migliore.
  • Dimenticare costo e latenza. Un sistema più preciso ma tre volte più lento può essere un peggioramento.
  • Dimenticare i casi fuori dominio. Sono quelli che, in produzione, fanno più danni.

Conclusione

Valutare un RAG non richiede un framework complicato. Richiede tre cose: un golden set stabile e rappresentativo, metriche separate per retrieval e generazione, e un flusso ripetibile che salvi i risultati per confrontarli nel tempo.

I punti chiave:

  • Una demo con cinque domande non è una valutazione: serve un insieme di casi fissato prima, non scelto dopo.
  • Includi sempre i casi fuori dominio: un sistema che ammette di non sapere è più affidabile di uno che risponde a tutto.
  • Misura prima il retrieval: se i documenti giusti non entrano nel contesto, la generazione non può salvarli.
  • L'LLM-as-a-Judge è utile per scalare, ma va controllato con un campione valutato a mano e usato con una rubrica breve.
  • Guarda i casi peggiori, non solo la media: è lì che stanno le prossime modifiche da fare.

Foto di copertina di James Harrison su Unsplash.