AI & MCP

Valutare un agente con un benchmark: harness, pass^k e varianza

Un benchmark per agenti è task fissi, ambiente pulito, grader automatico e k ripetizioni. Senza il quarto numero il risultato è un aneddoto.

In sintesi

  • Un harness ha quattro parti: task, ambiente che si resetta, grader e ripetizioni.
  • pass@k misura la capacità, pass^k misura l'affidabilità: sono due domande diverse.
  • Con 20 task il margine d'errore è largo: riporta l'intervallo, non il punto.

Un benchmark per agenti è fatto di quattro parti: task fissi, un ambiente che si resetta, un grader automatico e k ripetizioni per task. Se manca il quarto numero, il risultato è un aneddoto.

Le quattro parti di un harness

  • Task: una richiesta con un risultato atteso e un contesto di partenza identico per ogni modello.
  • Ambiente: una copia pulita del repository, senza node_modules o dist avanzati da un giro precedente.
  • Grader: un processo che decide pass o fail senza chiedere al modello come è andata.
  • Ripetizioni: almeno tre giri per task, perché un agente non dà due volte la stessa risposta.

Il grader è la parte che si sbaglia più spesso. Un test che l'agente può modificare non è un grader, è un suggerimento: va eseguito da fuori, su file che l'agente non può toccare. Lo stesso vale per il tempo limite e per il numero massimo di file modificati, che vanno decisi prima e non dopo aver visto il risultato.

pass@k e pass^k

pass@k risponde a «almeno uno dei k tentativi riesce», ed è la metrica giusta per la capacità: un agente che non ci arriva mai è fuori. pass^k risponde a «tutti i k tentativi riescono», ed è la metrica giusta per l'affidabilità: un agente che riesce una volta su tre non è pronto per un flusso automatico.

node -e "const p=0.8;for(const k of [1,3,5])console.log('pass^'+k,(100*p**k).toFixed(0)+'%')"

Con una probabilità di successo dell'80%, il comando stampa pass^1 80%, pass^3 51% e pass^5 33%. Lo stesso agente sembra ottimo con un tentativo e mediocre con cinque: la differenza non è nel modello, è nella domanda che il benchmark pone.

La varianza con 20 task

node -e "const n=20,p=0.8,se=Math.sqrt(p*(1-p)/n);console.log((100*p).toFixed(0)+'% +/- '+(196*se).toFixed(0)+' pt')"

Il comando stampa 80% +/- 18 pt: con venti task un 80% misurato vale fra il 62% e il 98%. Due modelli che differiscono di dieci punti su venti task sono indistinguibili. Per scendere a un margine di cinque punti servono centinaia di task, oppure meno task ma con un grader più fine di pass o fail.

Tre grader, tre verità

GraderCosa misuraCostoCome fallisce
Exit codebuild, tipi, testbassoun test debole passa per motivi sbagliati
Diff attesola modifica richiestamedioun fix equivalente ma diverso viene bocciato
Rubrica o giudicequalità, tono, completezzaaltopremia le risposte lunghe e ignora i dettagli

Per un agente di codice il primo è obbligatorio, il secondo è utile sui task piccoli, il terzo serve solo dove non esiste un oracolo. Un giudice automatico va validato contro un campione giudicato a mano: se non concorda su venti casi, il numero che produce non è una misura.

Contaminazione e rumore

Un task pubblico è quasi sempre già visto in addestramento, quindi il punteggio misura la memoria del modello e non la capacità. Anche l'harness introduce rumore: prompt diversi da quelli reali, cache calda al primo giro, tool che rispondono con latenze diverse. La difesa è noiosa: task presi da bug chiusi del tuo repository, ambiente ricreato a ogni giro, stesso prompt per tutti, ripetizioni dichiarate.

Cosa riportare

  • Numero di task e ripetizioni, perché senza quei due numeri il resto non è interpretabile.
  • Successo con intervallo, non il punto: 80% su venti task è 80% ± 18 punti.
  • Costo per task riuscito, che unisce token e fallimenti in un numero solo.
  • Passi mediani e file toccati, che anticipano la manutenzione futura meglio del punteggio.

La limitazione

Il nostro harness esegue due task: serve a verificare che un modello produca JSON valido e JSX coerente, non a classificare modelli. Anche un harness grande resta cieco su ciò che non misura: un agente può passare tutti i task e restare inutilizzabile perché lento, caro o insicuro sul disco. Il punteggio è un filtro, la decisione resta umana.

Dove continuare

La costruzione della fixture e il costo per attività riuscita sono in valutare un modello per il codice, l'ordine dei passi in orchestrare agenti multi-step, i rischi di scrittura su disco in sicurezza di un agente di coding. Il catalogo che alimenta i task è in 111.985 componenti e 1.556 blocchi; un record citabile è agent dock di dqnamo.

Domande frequenti

Quanti task servono per un benchmark credibile?

Venti task distinguono solo differenze grandi. Con venti task e un successo dell'80% l'intervallo è di circa 18 punti, quindi due modelli a dieci punti di distanza non sono separabili. Per differenze fini servono centinaia di task o un grader più fine.

Che differenza c'è fra pass@k e pass^k?

pass@k chiede se almeno un tentativo su k riesce, e misura la capacità. pass^k chiede se tutti i k tentativi riescono, e misura l'affidabilità. Un agente con pass^1 all'80% scende al 51% con pass^3 e al 33% con pass^5.

Posso usare un LLM come giudice?

Solo dove non esiste un oracolo, e solo dopo averlo validato su un campione giudicato a mano. Un giudice senza accordo misurato premia le risposte lunghe, ignora i dettagli e produce un numero che sembra una misura.

Come evito la contaminazione?

Usando task che non esistono in pubblico: bug chiusi del tuo repository, con il diff atteso già disponibile. Ruota i task quando il repository cambia forma e non riusare lo stesso set per anni.

Mi fido dei punteggi dei benchmark pubblici?

Come primo filtro sì, per escludere chi non sa scrivere codice. Non per scegliere fra i primi cinque, perché i task sono noti e il formato del prompt è diverso dal tuo. La fixture decide, il benchmark orienta.