Passa al contenuto principale
Annunci

Benchmarking degli agenti di coding sulla codebase da svariati milioni di righe di Databricks

di Vinay Gaba, Ankit Mathur, Rishabh Singh, Patrick Wendell e Matei Zaharia

In Databricks, il modo in cui sviluppiamo software sta cambiando rapidamente, poiché adottiamo l'AI in modo deciso per l'ingegneria del software. Il panorama dei modelli e degli harness per la scrittura di codice si è espanso rapidamente nell'ultimo anno, offrendo agli sviluppatori più scelta che mai. Con un numero maggiore di opzioni, è diventato sempre più importante capire quali coding agent offrano le migliori prestazioni su task di programmazione reali, oltre a comprendere come le prestazioni sui task varino in base al prezzo.

Questo articolo condivide i risultati e la metodologia del benchmark di coding interno che abbiamo creato in Databricks, che valuta gli strumenti su task di programmazione reali eseguiti dai nostri ingegneri sulla codebase di Databricks. I task includevano modifiche a una codebase di svariati milioni di righe che copre molti linguaggi popolari (Python, Go, Typescript, Scala, ecc.), e sia i task che le soluzioni sono stati attentamente esaminati per garantirne l'accuratezza. Questo non vuole essere un report esaustivo, ma l'esercizio ha fatto emergere insight che hanno già reso il nostro team di ingegneria significativamente più efficiente con i coding agent. Di seguito, puoi vedere i punteggi ottenuti da modelli e harness nel benchmark complessivo:

Costo vs. Prestazioni nel nostro benchmark

Figura 1: Costo vs. Prestazioni nel nostro benchmark

Le principali conclusioni della nostra analisi sono state:

  1. La frontiera di Pareto per i task di coding (ovvero la migliore qualità per un determinato costo) include modelli di OpenAI, Anthropic e open source. Ciò significa che oggi solo un mix di strumenti può fornire prestazioni all'avanguardia.
  2. I modelli aperti, e in particolare GLM 5.2, sono ora in grado di gestire anche il livello più elevato di difficoltà dei task.
  3. Il prezzo per token di un modello è un indicatore scarso dei costi effettivi sostenuti per i task end-to-end. I modelli più grandi possono essere molto più efficienti nell'uso dei token e presentare costi complessivi inferiori.
  4. L'harness da cui viene chiamato un modello influisce drasticamente sui costi e sulla qualità. In molti casi, harness semplici come Pi hanno ottenuto le prestazioni migliori sui nostri workload.

Esaminiamo ciascun punto più nel dettaglio.

I modelli si raggruppano in "livelli di capacità" approssimativi

Piccole differenze di pochi punti nei risultati specifici spesso si compensano nei task del mondo reale. Ci siamo concentrati maggiormente sui pattern tematici che ci aiutano a valutare quali modelli utilizzare per i vari task. Infatti, i risultati hanno mostrato un chiaro raggruppamento dei modelli e degli harness in 3 livelli di capacità.

Livelli di capacità per i modelli

Figura 2: Dai nostri risultati complessivi sono emersi tre distinti livelli di capacità, con sfumature su quali modelli si sono rivelati efficaci in ciascun gruppo

Nella fascia più alta di prestazioni, vediamo che i modelli più intelligenti sono molto efficaci nel risolvere ogni tipo di problema, ma sono molto costosi. I modelli con intelligenza media e inferiore sono comunque altamente efficaci nei task comuni e, in molti casi, sono anche significativamente più economici.

Giorno dopo giorno, gli ingegneri svolgono molte attività diverse che variano notevolmente in complessità: i task operativi comuni, come l'attivazione di un flag o l'aggiornamento delle configurazioni, non richiedono modelli estremamente intelligenti, a differenza di esplorazioni di progettazione più approfondite. Tuttavia, in passato, i nostri modelli predefiniti erano sempre quelli più costosi. Sulla base di questa analisi, abbiamo stabilito che dovremmo affidare più lavoro alla classe di modelli Haiku e GPT 5.4 Mini.

I modelli aperti sono pronti per il coding

C'è stato molto entusiasmo intorno a GLM 5.2, e i nostri risultati hanno dimostrato che GLM può essere un modello di riferimento quotidiano per molti dei nostri sviluppatori. Si è posizionato nel livello di capacità più alto, statisticamente alla pari con Opus 4.8 in termini di qualità, ma con un costo di 1,28 $ per task rispetto ai 1,94 $ di Opus.

I punteggi di qualità di GLM sono coerenti con i feedback qualitativi che abbiamo ricevuto dagli sviluppatori interni che hanno testato GLM per lo sviluppo quotidiano. Date le sue ottime prestazioni nei task di programmazione di tutti i giorni, ci siamo concentrati sul servire GLM con le migliori prestazioni, e l'evidenza dimostra che è giunto il momento di iniziare a distribuirli come strumenti di riferimento quotidiani per il coding.

Prezzo per task vs prezzo per token

Gli sviluppatori spesso valutano a occhio i costi dei token per determinare quanto sarà costoso un modello nel completare i task di coding. Abbiamo scoperto, tuttavia, che i costi dei token sono spesso un indicatore scarso dei costi complessivi dei task, a causa della variazione nell'efficienza di ragionamento tra i modelli. Ciò sottolinea la necessità di un benchmarking a livello di task, poiché la forma e la complessità dei task possono variare in contesti diversi.

Ad esempio, Sonnet 5 è circa 1,7 volte più economico per token rispetto a Opus 4.8, ma, sui nostri task, abbiamo riscontrato che Sonnet è costato 2,09 $ per task contro gli 1,94 $ di Opus, ottenendo un punteggio inferiore di sei punti nel completamento dei task (81% contro 87%). Ciò è dovuto principalmente al fatto che Sonnet 5 ha lavorato più a lungo e ha letto di più per raggiungere l'obiettivo, consumando 1,9 volte più token.

Gli harness hanno un impatto significativo sull'efficienza

Quando abbiamo eseguito lo stesso modello con lo stesso sforzo di elaborazione attraverso due diversi harness (Claude Code/Codex vs Pi), abbiamo osservato che il costo per task differiva in modo significativo (più del doppio in alcuni casi), mentre la qualità rimaneva invariata. La differenza principale risiedeva nella quantità di contesto che ciascun harness forniva al modello a ogni turno.

Impatto dell'harness sull'efficienza

Pi ha inviato circa 3 volte meno contesto per turno. Ha gestito meglio il contesto, mantenendo un working set più ristretto e completando i task in meno passaggi.
Contesto totale reinserito nel modello per task

La lezione qui non è che un harness sia sempre più economico o che gli harness nativi siano peggiori. Al contrario, la scelta del modello è solo un pezzo del puzzle. Stabilire questa flessibilità è il motivo per cui abbiamo investito in Omnigent per rendere immediati gli scambi tra modalità e harness.

Perché creare un proprio benchmark?

I benchmark pubblici come SWE-Bench e TerminalBench sono utili, ma non possono rispondere alle domande che avevamo. Ci sono diversi motivi per questo:

  • I task sono pubblici, quindi con il tempo le soluzioni finiscono per confluire nei dati di addestramento.
  • Abbiamo riscontrato che i risultati non erano rappresentativi per la nostra codebase, che spazia su oltre 10 linguaggi e molti servizi scritti in Scala, Go, Rust, Java e Python, Bazel, Protobuf e altri ancora.

Creando un benchmark sulle nostre PR, possiamo prendere queste decisioni con maggiore sicurezza di non ostacolare i nostri sviluppatori introducendo ottimizzazioni.

Come abbiamo creato il benchmark

Abbiamo utilizzato Unity AI Gateway per acquisire i log di tutte le nostre interazioni di coding, il che ci ha permesso di analizzare la complessità dei task affrontati dagli ingegneri che utilizzano i coding agent. C'era una notevole diversità nella complessità dei task: circa un quarto è stato classificato come lavoro a bassa complessità e circa il 60% a media complessità.

Cosa chiedono effettivamente i nostri ingegneri ai coding agent

Tuttavia, i modelli costosi sono quelli predefiniti utilizzati dagli ingegneri, quindi c'era chiaramente un'enorme opportunità per migliorare l'efficienza.

Costruzione dei task

I nostri ingegneri integrano migliaia di modifiche al codice ogni giorno, quindi disponiamo già di un ottimo dataset di partenza. Una buona pull request è un elemento ricco di informazioni, con commit che mostrano l'iterazione dello sviluppatore, la revisione da parte di persone e test che aiutano a verificare che una modifica al codice sia fedele al suo intento. Tuttavia, abbiamo avuto bisogno di diversi controlli di qualità e filtri per ricavarne un benchmark di alta qualità:

  • Recency: Attingiamo dalla cronologia recente in modo che i task riflettano il nostro modo attuale di sviluppare, inclusi i framework, i pattern e le convenzioni attualmente in uso.
  • Scritto da umani: I commit dei bot, gli account di servizio, le modifiche interamente generate dall'AI e quelle generate automaticamente sono stati filtrati.
  • Suite di test di alta qualità associata: Abbiamo filtrato le PR che includevano test di alta qualità per convalidare le modifiche al codice.
  • Autocontenuto: Le modifiche erano limitate a pochi moduli.
  • Rappresentativo di attività tipiche: Abbiamo selezionato PR da una distribuzione di attività su tutto lo stack: servizi backend Scala, codice di sistema Rust, frontend React e TypeScript, contratti protobuf e gRPC e configurazioni Bazel.
Piano dettagliato per la creazione delle attività

Una volta ottenute le PR candidate, ci siamo concentrati sulla creazione di attività ben specificate:

  1. Comprendere l'intento e riassumerlo sotto forma di prompt. Abbiamo letto la PR per capire a cosa servisse effettivamente e poi descritto il risultato desiderato. Di solito, questo significava riscrivere la descrizione della PR indicando il problema o l'obiettivo, nominando eventuali vincoli e rimuovendo la descrizione della soluzione. È importante rimuovere, ad esempio, le spiegazioni del perché una correzione di un bug sia quella corretta, poiché ciò renderebbe l'attività troppo semplice.
  2. Separare i test pertinenti. I file non di test rappresentavano la modifica che il modello deve riprodurre autonomamente, quindi abbiamo messo da parte i file di test e ci siamo assicurati di poter compilare il codice. Il nostro sistema di build è già in grado di determinare quali test dipendono dai file modificati nella PR originale, quindi abbiamo eseguito tutti i target di test per intero.

Il risultato di questo esercizio è stato una singola attività nel benchmark. Ecco un esempio semplificato:

Anche se abbiamo utilizzato script e AI per generare le attività candidate, abbiamo valutato ogni campione manualmente. In alcuni casi, abbiamo riscontrato che i test nella PR originale dovevano essere riscritti per consentire un'implementazione alternativa o per essere più rigorosi, cosa che abbiamo fatto manualmente (senza AI). Allo stesso modo, abbiamo riscontrato casi che richiedevano il miglioramento della descrizione dell'attività per renderla ben specificata.

Prima e dopo della suite di test

Figura 3: Un prima e dopo della nostra suite di test: il test precedente si basava sulla verifica della corrispondenza esatta della stringa, il che ha causato alcuni fallimenti quando il modello ha cercato di risolvere l'attività. Questo non era un buon modo per testare l'output non deterministico, quindi è stato riscritto per valutare invece il comportamento.

Abbiamo istanziato gli harness e i modelli degli agenti di codifica utilizzando le loro configurazioni standard pronte all'uso, con tutti gli strumenti comuni che gli ingegneri di Databricks avrebbero a disposizione.

Processo di configurazione e revisione

Quando l'agente indicava esplicitamente di aver completato l'attività, creavamo un checkpoint di quel codice, applicavamo le patch ai test che erano stati esclusi e valutavamo i test per determinare se l'attività fosse superata ("pass") per quella combinazione di modello + harness. Non abbiamo affatto utilizzato un giudice LLM per valutare la correttezza, poiché abbiamo riscontrato che questo premia il sembrare corretti rispetto all'esserlo davvero.

Ulteriori misure di sicurezza

Ulteriori misure di sicurezza

Nei nostri primi esperimenti, i punteggi di alcuni modelli sembravano troppo belli per essere veri, quindi abbiamo ispezionato manualmente le tracce per capire cosa fosse successo in queste traiettorie degli agenti. Abbiamo scoperto che, a causa della nostra configurazione originale, l'implementazione "corretta" era ancora recuperabile nella cronologia Git del worktree! Ogni attività aveva origine da un commit unito (merged), quindi nulla impediva a un agente dotato di shell di scorrere in avanti la cronologia di Git per trovarla. Per risolvere questo problema, abbiamo sigillato la cronologia di Git: per la durata di ogni esecuzione, abbiamo isolato completamente la copia di lavoro dal repository.

Quali sono i prossimi passi?

Siamo partiti da una domanda semplice: possiamo utilizzare gli agenti di codifica in modo più efficiente? La risposta è un sì definitivo e, poiché possiamo basarci sui dati, possiamo iniziare a sviluppare funzionalità per selezionare automaticamente i modelli giusti e monitorare l'efficienza.

Qualsiasi azienda può fare lo stesso. Qualsiasi team con un backlog di PR unite ha già a disposizione un benchmark su cui nessun modello è stato addestrato, valutato in base ai test scritti dal proprio team. Stiamo aggiungendo attivamente altre attività (soprattutto quelle più difficili) e prevediamo di testarvi ogni nuovo agente/harness per essere più sicuri delle nostre scelte.

In Databricks siamo sempre stati diffidenti nei confronti del lock-in, non solo verso i fornitori, ma anche verso i presupposti che rendono i team meno flessibili nel tempo. Questo stesso istinto ha guidato le nostre prime scommesse su formati e standard aperti, e influenza il modo in cui stiamo approcciando l'AI ora: misurare ciò che funziona effettivamente sul codice che rilasciamo, dare agli ingegneri lo spazio per passare da un modello all'altro e harness con misure di sicurezza coerenti, ed effettuare ottimizzazioni per utilizzare l'AI in modo efficace.

In un prossimo post del blog, parleremo più approfonditamente di come stiamo utilizzando le funzionalità di routing intelligente in Unity AI Gateway e in Omnigent per aiutare i nostri sviluppatori a utilizzare gli agenti più intelligenti mantenendo l'efficienza.

(Questo post sul blog è stato tradotto utilizzando strumenti basati sull'intelligenza artificiale) Post originale

Ricevi gli ultimi articoli nella tua casella di posta

Iscriviti al nostro blog e ricevi gli ultimi articoli direttamente nella tua casella di posta.