Perché “sembra giusto” non è un test per un prompt
Stringi il testo di un prompt che instrada i ticket, lo provi sui tre esempi che hai aperti nel tuo notebook e lo metti in produzione. Una settimana dopo, i reclami di fatturazione che prima finivano nella coda giusta cominciano a finire in “domanda”. Non i tre casi che hai controllato: gli altri quattrocento che non hai controllato. Niente va in crash. Non c’è nessuno stack trace. Il prompt, letto ad alta voce, suona ancora bene.
Il code review e qualche controllo manuale non colgono il problema perché lo stesso prompt sullo stesso input non restituisce lo stesso testo due volte. Non puoi fare un diff byte per byte tra due run e chiamare bug una discrepanza, come faresti con una funzione pura. Quello che puoi controllare è se ogni output ha le proprietà che ti interessano: la categoria giusta, un riassunto sotto il limite di lunghezza, nessun campo che la fonte non menzionava. Un eval esegue questi controlli su un insieme di input reali ogni volta che il prompt o il modello cambiano, così la regressione salta fuori nel tuo terminale invece che nel ticket di un cliente.
Quali sono le parti di un LLM eval
Ogni eval, qualunque sia il tool che lo esegue, ha tre parti: un dataset di input rappresentativi, un assert che verifica ogni output rispetto a cosa quell’input dovrebbe produrre, e un pass rate che tieni traccia da una run all’altra.
Pensalo come la suite di test per una funzione che restituisce ogni volta una frase leggermente diversa. L’assert non può verificare una corrispondenza esatta, quindi verifica una proprietà della frase: nomina la categoria giusta, il numero al suo interno è parsabile. Una volta accettato che il test chiede “questo vale” e non “questo è identico”, scriverne uno è ordinaria amministrazione.
Come costruire un dataset golden per un LLM eval
Prendi un prompt che trasforma una email di supporto grezza in un ticket strutturato:
| |
Il dataset golden è un insieme di email reali accoppiate alla risposta che già sai essere corretta. Reali, non inventate per far sembrare bello il prompt:
| |
Da cinque a dieci casi bastano per iniziare. La dimensione conta meno di dove arrivano i casi successivi: fallimenti reali in produzione.
Come scrivere assert deterministici per l’output di un LLM
Gran parte di quello che va storto in questo prompt non ha niente a che fare con il gusto. Il modello restituisce JSON malformato, oppure inventa una quarta categoria, oppure scrive quaranta parole quando ne avevi chieste dodici. Il codice normale intercetta tutto questo, senza bisogno di un secondo modello:
| |
Eseguilo su tutto il dataset e ottieni un pass rate, non un semplice sì o no:
| |
Questi controlli non costano niente e non danno mai falsi positivi casuali: lo stesso output cattivo fallisce nello stesso modo ogni volta. Scrivili per primi. Intercettano la maggior parte delle regressioni reali, come un cambio di schema o un riassunto che si è raddoppiato in silenzio.
Quando usare LLM-as-judge e come calibrarlo
Categoria e urgenza sono oggettive, e il dataset contiene già la risposta giusta. La qualità del riassunto no. “Rimborso necessario, doppio addebito” e “Il cliente ha una domanda di fatturazione” sono entrambi JSON validi, entrambi sotto le 12 parole, e solo uno dei due descrive davvero l’email. Nessuna regex lo intercetta, quindi un secondo modello lo valuta rispetto a una rubrica:
| |
Prima di fidarti di quel verdetto sull’intero dataset, esegui il judge su 15-20 casi che una persona ha già valutato a mano e conta quanto spesso sono d’accordo. Un judge che non è d’accordo con le persone una volta su tre è un assert debole travestito da autorità di un modello.
Ci sono due bias da tenere d’occhio. I judge tendono a valutare meglio le risposte più lunghe ed elaborate anche quando una più corta è ugualmente corretta. E un modello che valuta i propri output li giudica più favorevolmente di quanto farebbe un modello indipendente. Usa un modello diverso come judge quando puoi, e tieni il prompt stretto: una domanda sì/no per chiamata, non cinque punteggi in una sola richiesta.
| Assert deterministico | LLM-as-judge | |
|---|---|---|
| Costo per caso | Gratis | Una chiamata al modello in più |
| Cosa intercetta | Schema, formato, fatti già nel dataset | Qualità soggettiva, pertinenza, tono |
| Ripetibilità | Stesso output, stesso verdetto | Un po’ di deriva anche a temperatura 0 |
| Costo di setup | Scrivi il controllo e basta | Serve prima la calibrazione su esempi valutati da persone |
In termini di suite di test, i controlli deterministici sono i tuoi unit test: rapidi ed esatti, e li esegui a ogni modifica. Il judge è più vicino a un controllo di accettazione, quello che altrimenti farebbe una persona leggendo ogni output. I compromessi tra questi livelli sono gli stessi trattati in test unitari vs test di integrazione vs test end-to-end.
Come eseguire gli LLM eval in CI per intercettare le regressioni
Un eval che viene eseguito solo quando qualcuno se ne ricorda non intercetta niente. Metti lo script vicino al prompt che testa (evals/triage.py insieme a prompts/triage.py) ed eseguilo in CI a ogni modifica di entrambi i file, prima del merge. Tieni traccia del pass rate nel tempo e fai fallire la build quando scende sotto una baseline che hai fissato. Un calo dal 95% all'88% è una regressione da bloccare, anche se la maggior parte dei casi passa ancora.
Mantieni un set di regressione dentro il dataset: ogni caso che è stato un bug segnalato, fissato in modo permanente dopo la correzione. Per un prompt, questo ti dà il ciclo descritto in cos’è davvero il TDD. Cambia il prompt, rilancia la suite, vedi esattamente quali assert si sono ribaltati, correggi, ripeti. Alimenta il dataset con i nuovi fallimenti di produzione man mano che accadono, così non stai testando solo gli input su cui il prompt è stato progettato.
Come valutare pipeline di tool calling e RAG
Le stesse tre parti si applicano anche qui. Per un ciclo di tool calling, il dataset accoppia ogni input al nome del tool corretto e agli argomenti corretti, e l’assert verifica che il modello abbia scelto quel tool con i campi richiesti compilati. La meccanica del ciclo stesso è in function calling e tool use negli LLM.
Una pipeline RAG ha bisogno di due punteggi separati. Il retrieval ha recuperato il chunk che contiene la risposta? La generazione ha usato quel chunk correttamente? Se li valuti insieme, non puoi sapere quale delle due metà si è rotta. Guarda come funziona il RAG per capire quale pipeline stai dividendo in due.
Errori comuni con gli LLM eval
Costruire il dataset con casi che già passano. Cinque esempi scelti perché il prompt li gestisce bene passeranno per sempre e non ti diranno niente sull’input che romperà tutto il mese prossimo. Ogni fallimento reale che ti arriva va messo nel dataset lo stesso giorno.
Verificare una corrispondenza esatta su un output variabile. summary == "Rimborso necessario per doppio addebito" fallisce nel momento in cui il modello cambia una parola, anche quando la nuova formulazione è altrettanto corretta. Verifica la proprietà, non il testo: il conteggio delle parole, la categoria, se un fatto richiesto compare.
Saltare la calibrazione del judge. Un judge che non hai mai confrontato nemmeno con una dozzina di esempi valutati da persone ti consegna i suoi bias come verità di fatto, incluso quello che favorisce il proprio stesso output.
Rilanciare solo il caso che hai appena corretto. È così che un vecchio bug torna senza che nessuno se ne accorga. Il set di regressione esiste proprio per questo.
Quando non vale la pena costruire una suite di LLM eval
Un prompt che eseguirai una volta sola, o che una persona revisiona a ogni chiamata, non ha bisogno di un harness. Metti il controllo di validità JSON in linea se è poco costoso e vai avanti. La suite si ripaga quando un prompt va in produzione agli utenti in modo ripetuto o pensi di modificarlo più di un paio di volte. È il momento in cui “sembrava a posto sui miei tre esempi” smette di bastare.
Per iniziare: cinque input reali, un assert deterministico per ogni proprietà che ti interessa, e un judge LLM solo per la dimensione che il codice non può controllare. Eseguilo prima della prossima modifica al prompt, non dopo il reclamo.