Ir al contenido principal
Anuncios

Evaluación comparativa de agentes de codificación en la base de código de varios millones de líneas de Databricks

por Vinay Gaba, Ankit Mathur, Rishabh Singh, Patrick Wendell y Matei Zaharia

En Databricks, la forma en que creamos software está cambiando rápidamente a medida que adoptamos de manera decidida la AI para la ingeniería. El panorama de modelos y harnesses para la creación de código se ha expandido rápidamente en el último año, ofreciendo a los desarrolladores más opciones que nunca. Con más opciones, se ha vuelto cada vez más importante entender qué agentes de codificación ofrecen el mejor rendimiento en tareas de codificación del mundo real, así como comprender cómo varía el rendimiento de las tareas según el precio.

Este artículo comparte los resultados y la metodología del benchmark de codificación interno que creamos en Databricks, el cual evalúa las herramientas en tareas de codificación reales que nuestros ingenieros realizaron en la base de código de Databricks. Las tareas incluyeron modificaciones en una base de código de millones de líneas que abarca muchos lenguajes populares (Python, Go, Typescript, Scala, etc.), y tanto las tareas como las soluciones se revisaron minuciosamente para garantizar su precisión. Esto no pretende ser exhaustivo, pero el ejercicio reveló información valiosa que ya ha hecho que nuestro equipo de ingeniería sea significativamente más eficiente al usar agentes de codificación. A continuación, puede ver cómo puntuaron los modelos y harnesses en el benchmark general:

Costo vs. rendimiento en nuestro benchmark

Figura 1: Costo vs. rendimiento en nuestro benchmark

Las principales conclusiones de nuestro análisis fueron:

  1. La frontera de Pareto para las tareas de codificación (es decir, la mejor calidad para un costo determinado) incluye modelos de OpenAI, Anthropic y de código abierto. Esto significa que hoy en día, solo una combinación de herramientas puede proporcionar un rendimiento de vanguardia.
  2. Los modelos abiertos, y GLM 5.2 en particular, ahora son capaces de manejar incluso el nivel más alto de dificultad de las tareas.
  3. El precio por token de un modelo no es un buen indicador de los costos reales incurridos en tareas de extremo a extremo. Los modelos más grandes pueden ser mucho más eficientes en el uso de tokens y tener costos generales más bajos.
  4. El harness desde el que se llama a un modelo afecta drásticamente el costo y la calidad. En muchos casos, los harnesses sencillos como Pi obtuvieron el mejor rendimiento en nuestras cargas de trabajo.

Analicemos cada uno de ellos con un poco más de detalle.

Los modelos se agrupan en "niveles de capacidad" aproximados

El hecho de que los resultados específicos varíen por un par de puntos a menudo se compensa en las tareas del mundo real. Nos enfocamos más en los patrones temáticos que nos ayudan a razonar sobre qué modelos usar para diversas tareas. De hecho, los resultados mostraron una clara agrupación de los modelos y harnesses en 3 niveles de capacidad.

Niveles de capacidad para los modelos

Figura 2: Surgieron tres niveles de capacidad distintos en nuestros resultados generales, con matices sobre qué modelos fueron efectivos en cada grupo

En el extremo superior del rendimiento, vemos que los modelos más inteligentes son muy eficaces para resolver todo tipo de problemas, pero son muy costosos. Los modelos de inteligencia media y baja siguen siendo muy eficaces en las tareas comunes y, en muchos casos, también son significativamente más baratos.

En el día a día, los ingenieros realizan muchas tareas diferentes que varían significativamente en complejidad: las tareas operativas comunes, como cambiar un flag o actualizar configuraciones, no requieren modelos extremadamente inteligentes, pero las exploraciones de diseño más profundas sí. Sin embargo, en el pasado, nuestros modelos predeterminados siempre eran los más costosos. Con base en este análisis, determinamos que deberíamos delegar más trabajo a la clase de modelos Haiku y GPT 5.4 Mini.

Los modelos abiertos ya están listos para la codificación

Ha habido mucho entusiasmo en torno a GLM 5.2, y nuestros resultados demostraron que GLM puede ser un modelo de uso diario para muchos de nuestros desarrolladores. Se posicionó en el nivel de capacidad superior, empatando estadísticamente en calidad con Opus 4.8, pero con un costo de $1.28 por tarea frente a los $1.94 de Opus.

Las puntuaciones de calidad de GLM son consistentes con los comentarios cualitativos que hemos recibido de los desarrolladores internos que han estado probando GLM para el desarrollo diario. Debido a su excelente rendimiento en las tareas de codificación cotidianas, nos hemos enfocado en ofrecer GLM con el mejor rendimiento, y la evidencia demuestra que es hora de comenzar a implementarlos como modelos de uso diario para la codificación.

Precio por tarea vs. precio por token

Los desarrolladores a menudo estiman a simple vista los costos de los tokens para determinar qué tan costoso será un modelo al completar tareas de codificación. Sin embargo, descubrimos que los costos de los tokens suelen ser un mal indicador de los costos generales de las tareas, debido a la variación en la eficiencia de razonamiento entre los modelos. Esto subraya la necesidad de realizar benchmarks a nivel de tarea, ya que la forma y la complejidad de la tarea pueden ser diferentes en distintos contextos.

Como ejemplo, Sonnet 5 es aproximadamente 1.7 veces más barato por token que Opus 4.8, pero, en nuestras tareas, descubrimos que Sonnet costó $2.09 por tarea frente a los $1.94 de Opus, mientras que obtuvo una puntuación seis puntos menor en la finalización de tareas (81% frente a 87%). Esto se debió principalmente a que Sonnet 5 trabajó durante más tiempo y leyó más para lograrlo, consumiendo 1.9 veces más tokens.

Los harnesses tienen un gran impacto en la eficiencia

Cuando ejecutamos el mismo modelo con el mismo esfuerzo de pensamiento a través de dos harnesses diferentes (Claude Code/Codex frente a Pi), observamos que el costo por tarea difería significativamente (más del doble en algunos casos), mientras que la calidad se mantenía igual. La diferencia principal radicó en la cantidad de contexto que cada harness proporcionaba al modelo en cada turno.

Impacto del harness en la eficiencia

Pi envió aproximadamente 3 veces menos contexto por turno. Administró mejor el contexto, manteniendo un conjunto de trabajo más acotado y finalizando las tareas en menos ejecuciones.

Contexto total reintroducido al modelo por tarea

La lección aquí no es que un harness sea siempre más barato o que los harnesses nativos sean peores. En cambio, la elección del modelo es solo una pieza del rompecabezas. Establecer esta flexibilidad es la razón por la que invertimos en Omnigent para que los intercambios de modelo y harness sean fluidos.

¿Por qué crear su propio benchmark?

Los benchmarks públicos como SWE-Bench y TerminalBench son útiles, pero no pueden responder a las preguntas que teníamos. Hay algunas razones para esto:

  • Las tareas son públicas, por lo que las soluciones se filtran en los datos de entrenamiento con el tiempo.
  • Descubrimos que los resultados no eran representativos para nuestra base de código, que abarca más de 10 lenguajes y muchos servicios escritos en Scala, Go, Rust, Java y Python, Bazel, Protobuf y más.

Al crear un benchmark en nuestras propias PR, podemos tomar estas decisiones con mayor confianza de que no obstaculizaremos a nuestros desarrolladores al implementar optimizaciones.

Cómo creamos el benchmark

Utilizamos Unity AI Gateway para capturar registros de todas nuestras interacciones de codificación, lo que nos permitió analizar la complejidad de las tareas que los ingenieros abordan utilizando agentes de codificación. Hubo una diversidad significativa en la complejidad de las tareas, y aproximadamente una cuarta parte se etiquetó como trabajo de baja complejidad y alrededor del 60% como de complejidad media.

Lo que nuestros ingenieros realmente piden a los agentes de codificación

Sin embargo, los modelos costosos son los modelos predeterminados que usan los ingenieros, por lo que claramente había una gran oportunidad para mejorar la eficiencia.

Construcción de tareas

Nuestros ingenieros fusionan miles de cambios de código al día, por lo que ya tenemos un excelente conjunto de datos sobre el cual construir. Un buen pull request es un artefacto valioso, con commits que muestran la iteración del desarrollador, la revisión por parte de humanos y pruebas que ayudan a verificar que un cambio de código sea fiel a su intención. Sin embargo, necesitamos varios controles de calidad y filtros para construir un benchmark de alta calidad a partir de ellos:

  • Recente: Extraemos del historial reciente para que las tareas reflejen cómo construimos hoy en día, incluidos los frameworks, patrones y convenciones que se utilizan actualmente.
  • Escrito por humanos: Se filtraron los commits de bots, las cuentas de servicio, los cambios generados completamente por AI y los cambios generados automáticamente.
  • Suite de pruebas de alta calidad asociada: Filtramos las PR que incluían pruebas de alta calidad para validar los cambios de código.
  • Autónomo: Los cambios se limitaron a unos pocos módulos.
  • Representativo de tareas típicas: Seleccionamos PR de una distribución de tareas en todo el stack: servicios backend de Scala, código de sistemas de Rust, el frontend de React y TypeScript, contratos de protobuf y gRPC, y configuraciones de Bazel.
Plan paso a paso para la construcción de tareas

Una vez que tuvimos las PR candidatas, nos enfocamos en construir tareas bien especificadas mediante:

  1. Deducir la intención y resumirla como un prompt. Leímos la PR para entender para qué servía realmente y luego describir el resultado que queremos. Por lo general, eso significaba reescribir la descripción de la PR indicando el problema o el objetivo, nombrando las restricciones y eliminando la descripción de la solución. Es importante eliminar, por ejemplo, las explicaciones de por qué una corrección de errores es la correcta, ya que eso hace que la tarea sea demasiado fácil.
  2. Separar las pruebas relevantes. Los archivos que no eran de prueba representaban el cambio que el modelo debe reproducir por sí mismo, por lo que dejamos de lado los archivos de prueba y nos aseguramos de que podíamos compilar eso. Nuestro sistema de compilación ya puede determinar qué pruebas dependen de los archivos que se modificaron en la PR original, por lo que ejecutamos todos esos objetivos de prueba por completo.

El resultado de este ejercicio fue una sola tarea en el benchmark. Aquí hay un ejemplo simplificado:

Aunque utilizamos scripts e AI para generar tareas candidatas, evaluamos cada muestra a mano. En algunos casos, descubrimos que las pruebas de la PR original debían reescribirse para permitir una implementación alternativa o para ser más rigurosas, lo cual hicimos manualmente (sin AI). Del mismo modo, también encontramos casos que requerían mejorar la descripción de la tarea para que estuvieran bien especificadas.

Antes y después de la suite de pruebas

Figura 3: Un antes y un después de nuestra suite de pruebas: la prueba anterior se basaba en verificar la coincidencia exacta de cadenas, lo que provocó algunos fallos cuando el modelo intentó resolver la tarea. Esta no era una buena manera de probar resultados no deterministas, por lo que se reescribió para evaluar el comportamiento en su lugar.

Instanciamos los arneses (harnesses) y modelos de agentes de codificación utilizando sus configuraciones estándar listas para usar, con todas las herramientas comunes que los ingenieros de Databricks tendrían a su disposición.

Proceso de configuración y revisión

Cuando el agente decía explícitamente que había completado la tarea, guardábamos un punto de control (checkpoint) de ese código, aplicábamos los parches a las pruebas que se habían reservado y evaluábamos las pruebas para determinar si esa tarea era un "aprobado" para esa combinación de modelo + arnés. No utilizamos un juez LLM para evaluar la corrección, ya que hemos descubierto que esto premia el parecer correcto por encima de ser correcto.

Medidas de seguridad adicionales

Medidas de seguridad adicionales

En nuestros primeros experimentos, algunas puntuaciones de los modelos parecían demasiado buenas para ser verdad, por lo que inspeccionamos manualmente las trazas para entender qué había sucedido en estas trayectorias de los agentes. Lo que vimos fue que, debido a nuestra configuración original, la implementación "correcta" aún se podía recuperar en el historial de Git del árbol de trabajo (worktree). Cada tarea se había originado a partir de un commit fusionado, por lo que nada impedía que un agente con una terminal (shell) avanzara por el historial de Git para encontrarlo. Para solucionar este problema, sellamos el historial de Git: durante la duración de cada ejecución, desconectamos por completo la copia de trabajo del repositorio.

¿Qué sigue?

Comenzamos con una pregunta simple: ¿podemos usar agentes de codificación de manera más eficiente? La respuesta es un sí rotundo y, dado que podemos basarnos en datos, podemos comenzar a crear capacidades para seleccionar los modelos correctos automáticamente y realizar un seguimiento de la eficiencia.

Cualquier empresa puede hacer lo mismo. Cualquier equipo con un backlog de PR fusionadas ya cuenta con un benchmark en el que ningún modelo se ha entrenado, evaluado por las pruebas que escribió su equipo. Estamos agregando activamente más tareas (especialmente las más difíciles) y planeamos pasar cada nuevo agente/arnés por ellas para sentirnos más seguros de nuestras elecciones.

En Databricks, siempre hemos sido cautelosos con la dependencia de proveedores (lock-in), no solo de los proveedores en sí, sino de las suposiciones que hacen que los equipos sean menos flexibles con el tiempo. Ese mismo instinto dio forma a nuestras primeras apuestas por formatos y estándares abiertos, y define cómo estamos abordando la AI ahora: medir lo que realmente funciona en el código que enviamos, dar espacio a los ingenieros para moverse entre modelos y arneses (harnesses) con medidas de seguridad consistentes, y realizar optimizaciones para usar la AI de manera efectiva.

En un próximo blog, hablaremos más sobre cómo estamos utilizando las funciones de enrutamiento inteligente en Unity AI Gateway y en Omnigent para ayudar a nuestros desarrolladores a usar los agentes más inteligentes de manera eficiente.

(Esta entrada del blog ha sido traducida utilizando herramientas basadas en inteligencia artificial) Publicación original

Recibe las últimas publicaciones en tu bandeja de entrada

Suscríbete a nuestro blog y recibe las últimas publicaciones directamente en tu bandeja de entrada.