Revenir au contenu principal
Annonces

Benchmarking des agents de codage sur la base de code de plusieurs millions de lignes de Databricks

par Vinay Gaba, Ankit Mathur, Rishabh Singh, Patrick Wendell et Matei Zaharia

Chez Databricks, notre manière de concevoir des logiciels évolue rapidement à mesure que nous adoptons massivement l'AI pour l'ingénierie. Le paysage des modèles et des harnais pour la génération de code s'est rapidement développé l'année dernière, offrant aux développeurs plus de choix que jamais. Avec la multiplication des options, il est devenu crucial de comprendre quels agents de codage offrent les meilleures performances sur des tâches de codage réelles, et de saisir comment ces performances varient en fonction du prix.

Cet article présente les résultats et la méthodologie du benchmark de codage interne que nous avons développé chez Databricks, qui évalue les outils sur des tâches de codage réelles réalisées par nos ingénieurs sur la base de code de Databricks. Les tâches comprenaient des modifications sur une base de code de plusieurs millions de lignes couvrant de nombreux langages populaires (Python, Go, Typescript, Scala, etc.), et les tâches comme les solutions ont été minutieusement examinées pour garantir leur exactitude. Cette étude ne se veut pas exhaustive, mais l'exercice a mis en lumière des enseignements qui ont déjà permis à notre équipe d'ingénierie de gagner considérablement en efficacité grâce aux agents de codage. Ci-dessous, vous pouvez voir les scores obtenus par les modèles et les harnais sur l'ensemble du benchmark :

Coût vs performance sur notre benchmark

Figure 1 : Coût vs performance sur notre benchmark

Les principales conclusions de notre analyse sont les suivantes :

  1. La frontière de Pareto pour les tâches de codage (c'est-à-dire la meilleure qualité pour un coût donné) inclut des modèles d'OpenAI, d'Anthropic et open source. Cela signifie qu'aujourd'hui, seule une combinaison d'outils peut offrir des performances de pointe.
  2. Les modèles ouverts, et GLM 5.2 en particulier, sont désormais capables de gérer même les niveaux de difficulté les plus élevés.
  3. Le prix par token d'un modèle est un indicateur peu fiable des coûts réels générés par les tâches de bout en bout. Les modèles plus grands peuvent être beaucoup plus efficaces en termes de tokens et présenter des coûts globaux inférieurs.
  4. Le harnais depuis lequel un modèle est appelé a un impact considérable sur le coût et la qualité. Dans de nombreux cas, des harnais simples comme Pi ont obtenu les meilleurs résultats sur nos charges de travail.

Examinons chacun de ces points plus en détail.

Les modèles se regroupent en grands « niveaux de capacité »

Des écarts de quelques points dans les résultats spécifiques se lissent souvent dans les tâches réelles. Nous nous sommes plutôt concentrés sur les grandes tendances qui nous aident à déterminer quels modèles utiliser pour différentes tâches. En fait, les résultats ont montré un regroupement clair des modèles et des harnais en 3 niveaux de capacité.

Niveaux de capacités pour les modèles

Figure 2 : Trois niveaux de capacité distincts sont ressortis de nos résultats globaux, avec des nuances quant aux modèles efficaces dans chaque groupe

Au sommet des performances, nous constatons que les modèles les plus intelligents sont très efficaces pour résoudre toutes sortes de problèmes, mais ils sont très coûteux. Les modèles d'intelligence moyenne et inférieure restent très efficaces pour les tâches courantes et, dans de nombreux cas, ils sont également nettement moins chers.

Au quotidien, les ingénieurs effectuent de nombreuses tâches de complexité très variable : les tâches opérationnelles courantes, comme l'activation d'un flag ou la mise à jour de configurations, ne nécessitent pas de modèles extrêmement intelligents, contrairement aux explorations de conception plus approfondies. Pourtant, par le passé, nos modèles par défaut étaient toujours les plus coûteux . Sur la base de cette analyse, nous avons déterminé que nous devions confier davantage de travail aux modèles de la catégorie Haiku et GPT 5.4 Mini.

Les modèles ouverts s'imposent pour le codage

Il y a eu beaucoup d'enthousiasme autour de GLM 5.2, et nos résultats prouvent que GLM peut être un modèle de choix au quotidien pour beaucoup de nos développeurs. Il s'est classé dans le premier niveau de capacité, à égalité statistique avec Opus 4.8 en termes de qualité, mais pour un coût de 1,28 $ par tâche contre 1,94 $ pour Opus.

Les scores de qualité de GLM sont cohérents avec les retours qualitatifs de nos développeurs internes qui testent GLM pour leur développement quotidien. En raison de ses excellentes performances pour les tâches de codage de tous les jours, nous nous sommes concentrés sur servir GLM avec les meilleures performances, et tout indique qu'il est temps de commencer à les déployer comme outils de travail quotidiens pour le codage.

Prix par tâche vs prix par token

Les développeurs évaluent souvent à l'œil nu le coût des tokens pour estimer ce que coûtera un modèle lors de la réalisation de tâches de codage. Nous avons cependant constaté que le coût des tokens est souvent un mauvais indicateur du coût global de la tâche, en raison des variations d'efficacité de raisonnement entre les modèles. Cela souligne la nécessité d'un benchmark au niveau de la tâche, car la forme et la complexité de la tâche peuvent varier selon les contextes.

À titre d'exemple, Sonnet 5 est environ 1,7 fois moins cher par token qu'Opus 4.8, mais sur nos tâches, nous avons constaté que Sonnet coûtait 2,09 $ par tâche contre 1,94 $ pour Opus, tout en obtenant un score inférieur de six points sur la complétion des tâches (81 % contre 87 %). Cela s'explique principalement par le fait que Sonnet 5 a travaillé plus longtemps et a lu davantage pour y parvenir, consommant 1,9 fois plus de tokens.

Les harnais ont un impact majeur sur l'efficacité

Lorsque nous avons exécuté le même modèle avec le même effort de réflexion via deux harnais différents (Claude Code/Codex vs Pi), nous avons constaté que le coût par tâche différait considérablement (parfois de plus du double), tandis que la qualité restait identique. La différence principale résidait dans la quantité de contexte que chaque harnais transmettait au modèle à chaque étape.

Impact du harnais sur l'efficacité

Pi a envoyé environ 3 fois moins de contexte par étape. Il a mieux géré le contexte, en conservant un ensemble de travail plus restreint et en terminant les tâches en moins d'exécutions.
Contexte total réinjecté dans le modèle par tâche

La leçon à en tirer n'est pas qu'un harnais est toujours moins cher ou que les harnais natifs sont moins bons. Le choix du modèle n'est qu'une pièce du puzzle. C'est pour instaurer cette flexibilité que nous avons investi dans Omnigent afin de rendre les changements de mode et de harnais totalement fluides.

Pourquoi créer votre propre benchmark ?

Les benchmarks publics comme SWE-Bench et TerminalBench sont utiles, mais ils ne pouvaient pas répondre aux questions que nous nous posions. Cela s'explique par plusieurs raisons :

  • Les tâches étant publiques, les solutions finissent par s'infiltrer dans les données d'entraînement au fil du temps.
  • Nous avons constaté que les résultats n'étaient pas représentatifs de notre base de code, qui s'étend sur plus de 10 langages et de nombreux services écrits en Scala, Go, Rust, Java et Python, Bazel, Protobuf, etc.

En construisant un benchmark sur nos propres PR, nous pouvons prendre ces décisions avec une plus grande certitude que nous ne gênerons pas nos développeurs en déployant des optimisations.

Comment nous avons construit le benchmark

Nous avons utilisé Unity AI Gateway pour capturer les logs de toutes nos interactions de codage, ce qui nous a permis d'analyser la complexité des tâches que les ingénieurs abordent à l'aide d'agents de codage. La complexité des tâches était très diversifiée : environ un quart d'entre elles ont été qualifiées de tâches à faible complexité, et environ 60 % de complexité moyenne.

Ce que nos ingénieurs demandent réellement aux agents de codage

Cependant, les modèles coûteux étant ceux utilisés par défaut par les ingénieurs, il y avait clairement une formidable opportunité d'améliorer l'efficacité.

Construction des tâches

Nos ingénieurs fusionnent des milliers de modifications de code par jour, nous disposons donc déjà d'un excellent jeu de données sur lequel nous appuyer. Une bonne pull request est un artefact riche, avec des commits qui montrent l'itération du développeur, la revue par des humains et des tests qui permettent de vérifier qu'une modification de code est fidèle à son intention. Cependant, nous avons eu besoin de plusieurs contrôles de qualité et filtres pour en tirer un benchmark de haute qualité :

  • Récence : Nous puisons dans l'historique récent afin que les tâches reflètent notre manière de concevoir aujourd'hui, y compris les frameworks, les patterns et les conventions actuellement en usage.
  • Écrit par l'humain : Les commits de bots, les comptes de service, les modifications entièrement générées par AI et les modifications générées automatiquement ont été filtrés.
  • Suite de tests de haute qualité associée : Nous avons filtré les PR qui incluaient des tests de haute qualité pour valider les modifications de code.
  • Autonome : Les modifications étaient limitées à quelques modules.
  • Représentatif des tâches typiques : Nous avons sélectionné des PR à partir d'une répartition de tâches sur l'ensemble de la pile : services backend Scala, code système Rust, frontend React et TypeScript, contrats protobuf et gRPC, et configurations Bazel.
Plan étape par étape pour la construction des tâches

Une fois les PR candidates obtenues, nous nous sommes concentrés sur la construction de tâches bien définies en :

  1. Comprendre l'intention et la résumer sous forme de prompt. Nous avons lu la PR pour comprendre son but réel, puis nous avons décrit le résultat souhaité. En général, cela consistait à réécrire la description de la PR en énonçant le problème ou l'objectif, en nommant les contraintes et en supprimant la description de la solution. Il est important de supprimer, par exemple, les explications sur le pourquoi d'une correction de bug, car cela rend la tâche trop facile.
  2. Isoler les tests pertinents. Les fichiers autres que les tests représentaient les modifications que le modèle doit reproduire lui-même. Nous avons donc mis de côté les fichiers de test et nous nous sommes assurés de pouvoir compiler le reste. Notre système de build peut déjà déterminer quels tests dépendent des fichiers modifiés dans la PR d'origine, nous avons donc exécuté l'ensemble de ces cibles de test.

Le résultat de cet exercice a été une tâche unique dans le benchmark. Voici un exemple simplifié :

Bien que nous ayons utilisé des scripts et l'IA pour générer des tâches candidates, nous avons évalué chaque échantillon à la main. Dans certains cas, nous avons constaté que les tests de la PR d'origine devaient être réécrits pour permettre une implémentation alternative ou pour être plus rigoureux, ce que nous avons fait manuellement (sans IA). De même, nous avons également trouvé des cas qui nécessitaient d'améliorer la description de la tâche pour la rendre bien définie.

Avant et après de la suite de tests

Figure 3 : Un avant-après de notre suite de tests : le test précédent reposait sur la vérification d'une correspondance exacte de chaîne, ce qui entraînait des échecs lorsque le modèle tentait de résoudre la tâche. Ce n'était pas une bonne méthode pour tester des résultats non déterministes, il a donc été réécrit pour évaluer le comportement à la place.

Nous avons instancié les harnesses d'agents de codage et les modèles en utilisant leurs configurations standard prêtes à l'emploi, avec tous les outils courants auxquels les ingénieurs de Databricks auraient accès.

Processus de configuration et d'examen

Lorsque l'agent indiquait explicitement qu'il avait terminé la tâche, nous enregistrions un point de contrôle (checkpoint) de ce code, appliquions les tests mis de côté et évaluions les tests pour déterminer si cette tâche était réussie (« pass ») pour cette combinaison modèle + harness. Nous n'avons pas utilisé de juge LLM pour évaluer l'exactitude, car nous avons constaté que cela valorise le fait d'avoir l'air correct plutôt que de l'être réellement.

Garde-fous supplémentaires

Garde-fous supplémentaires

Lors de nos premières expériences, certains scores de modèles semblaient trop beaux pour être vrais, nous avons donc inspecté manuellement les traces pour comprendre ce qui s'était passé dans ces trajectoires d'agents. Ce que nous avons constaté, c'est qu'en raison de notre configuration initiale, l'implémentation « correcte » pouvait encore être récupérée dans l'historique Git de l'arbre de travail (worktree) ! Chaque tâche provenant d'un commit fusionné, rien n'empêchait un agent disposant d'un shell de parcourir l'historique Git pour la trouver. Pour résoudre ce problème, nous avons verrouillé l'historique Git : pendant toute la durée de chaque exécution, nous avons complètement coupé la copie de travail du dépôt.

Et ensuite ?

Nous sommes partis d'une question simple : pouvons-nous utiliser les agents de codage plus efficacement ? La réponse est un oui catégorique, et parce que nous pouvons nous appuyer sur les données, nous pouvons commencer à développer des fonctionnalités pour sélectionner automatiquement les bons modèles et suivre l'efficacité.

Toute entreprise peut faire de même. Toute équipe disposant d'un backlog de PR fusionnées dispose déjà d'un benchmark sur lequel aucun modèle ne s'est entraîné, évalué par les tests que votre équipe a écrits. Nous ajoutons activement de nouvelles tâches (en particulier les plus difficiles), et nous prévoyons d'y soumettre chaque nouvel agent/harness pour valider nos choix avec plus d'assurance.

Chez Databricks, nous avons toujours redouté le verrouillage technologique (lock-in), non seulement vis-à-vis des fournisseurs, mais aussi vis-à-vis des certitudes qui rendent les équipes moins flexibles au fil du temps. Ce même instinct a guidé nos premiers choix en faveur des formats et standards ouverts, et il façonne aujourd'hui notre approche de l'IA : mesurer ce qui fonctionne réellement sur le code que nous déployons, donner aux ingénieurs la liberté de passer d'un modèle à l'autre et d'utiliser différents harnesses avec des garde-fous cohérents, et optimiser pour utiliser l'IA efficacement.

Dans un prochain article de blog, nous détaillerons comment nous utilisons les fonctionnalités de routage intelligent dans Unity AI Gateway et dans Omnigent pour aider nos développeurs à utiliser les agents les plus intelligents tout en restant efficaces.

(Cet article de blog a été traduit à l'aide d'outils basés sur l'intelligence artificielle) Article original

Recevez les derniers articles dans votre boîte mail

Abonnez-vous à notre blog et recevez les derniers articles directement dans votre boîte mail.