Databricks 编程 Agent 基准测试:在数百万行代码库上的实战评测
在 Databricks,我们构建软件的方式正在快速变化,因为我们在大规模采用 AI 进行工程开发。过去一年,代码编写相关的模型和 Agent 框架迅速扩张,给了开发者比以往更多的选择。选择越多,就越有必要弄清楚:哪些编程 Agent 在真实编程任务上表现最好,以及任务性能如何随价格变化。
本文分享我们在 Databricks 内部构建的编程基准测试的方法和结果。该基准测试在我们自己的代码库上,评估各种工具完成工程师实际编程任务的能力。任务涵盖对数百万行代码库的修改,涉及多种主流语言(Python、Go、TypeScript、Scala 等),且所有任务和解题方案都经过仔细审核以确保准确性。这不是一份全面的评估,但这次实践产出的洞察已经显著提升了我们工程团队使用编程 Agent 的效率。下图展示了各模型和 Agent 框架在整体基准测试中的得分:
主要结论
- 编程任务的前沿性能需要多种工具的组合。帕累托前沿(即在给定成本下的最佳质量)包含来自 OpenAI、Anthropic 和开源阵营的模型。这意味着今天没有任何单一工具能全面称雄。
- 开源模型已经能打硬仗。开源模型——尤其是 GLM 5.2——已经能够应对最高难度的任务。
- Token 单价是实际成本的糟糕指标。大模型在 token 效率上可能远超小模型,端到端总成本反而更低。
- Agent 框架对成本和质量有巨大影响。在很多情况下,像 Pi 这样的简单框架在我们的工作负载上表现最好。
模型聚类形成"能力分层"
具体结果差几个点,在真实世界中往往能拉平。我们更关注那些帮助我们判断"什么任务该用什么模型"的规律性模式。事实上,结果显示模型和框架明显聚集成三个能力层级。
在性能最高端,最聪明的模型在解决各类问题上都非常有效,但它们非常昂贵。中低智能水平的模型在常见任务上仍然高效,而且在很多情况下便宜得多。
工程师日常做的事复杂度差异很大:像翻转配置开关或更新配置这样的常规运维任务不需要极度聪明的模型,但深度设计探索需要。然而过去,我们的默认模型总是最贵的那些。基于这项分析,我们决定将更多工作推向 Haiku 和 GPT 5.4 Mini 这个级别的模型。
开源模型已经在编程领域站稳脚跟
业界对 GLM 5.2 有很多期待,而我们的结果表明 GLM 可以成为很多开发者的日常主力模型。它进入了最高能力层级,在质量上与 Opus 4.8 统计上打平,但每任务成本仅 $1.28,而 Opus 是 $1.94。
GLM 的质量得分与我们从内部开发者那里获得的定性反馈一致——他们一直在试用 GLM 进行日常开发。因为其在日常编程任务上的出色表现,证据表明现在是时候将这些模型部署为编程主力了。
按任务计价 vs 按 Token 计价
开发者经常凭 token 价格来判断一个模型在编程任务中会有多贵。但我们发现,由于模型之间的推理效率差异,token 价格往往是整体任务成本的糟糕指标。这凸显了任务级基准测试的必要性,因为任务形状和复杂度在不同场景下可能不同。
举个例子:Sonnet 5 的每 token 价格比 Opus 4.8 便宜约 1.7 倍,但在我们的任务上,Sonnet 每任务花费 $2.09,而 Opus 只要 $1.94,同时 Sonnet 的完成率还低了 6 个百分点(81% vs 87%)。这主要是因为 Sonnet 5 工作时间更长,读取了更多内容,消耗了 1.9 倍的 token。
Agent 框架对效率影响巨大
当我们用相同的模型、相同的思考强度,通过两种不同的框架(Claude Code/Codex vs Pi)运行时,每任务成本差异显著(某些情况下超过 2 倍),而质量保持不变。主要差异在于每个框架每轮喂给模型的上下文量。
Pi 每轮发送的上下文大约少 3 倍。它更好地管理了上下文,保持了更紧凑的工作集,用更少的轮次完成任务。
这里的教训不是"某个框架总是更便宜"或"原生框架更差"。相反,模型选择只是拼图的一部分。正是为了建立这种灵活性,我们投资开发了 Omnigent,让模型和框架的切换变得无缝。
为什么要自建基准测试?
像 SWE-Bench 和 TerminalBench 这样的公开基准测试是有用的,但它们回答不了我们的问题。原因如下:
- 数据泄漏。任务是公开的,解题方案会随时间泄漏到训练数据中。
- 代表性不足。我们发现这些结果对我们的代码库不具代表性——我们的代码库跨越 10+ 种语言,包含大量用 Scala、Go、Rust、Java 和 Python 编写的服务,以及 Bazel、Protobuf 等。
通过在自己的 PR 上构建基准测试,我们可以更有信心地做出决策,确保推出的优化不会影响开发者的工作效率。
我们使用 Omnigent 捕获了所有编程交互的日志,这使我们能够分析工程师使用编程 Agent 处理的任务复杂度。任务复杂度的多样性很大,约四分之一被标记为低复杂度工作,约 60% 为中等复杂度。
然而,昂贵的模型是工程师使用的默认模型,因此在效率提升方面显然有巨大的空间。
我们如何构建基准测试
我们的工程师每天合并数千次代码变更,所以我们已经有了一个很好的数据集来构建。一个好的 Pull Request 是一个丰富的制品——有展示开发者迭代过程的提交,有人工审查,有验证代码变更是否忠实地实现了其意图的测试。但我们需要几个质量检查和过滤器,才能从中构建出高质量的基准测试:
任务构建
- 时效性。我们从最近的历史中提取,使任务反映我们今天的构建方式,包括当前使用的框架、模式和约定。
- 人工编写。过滤掉 Bot 提交、服务账号、完全 AI 生成的变更和自动生成的变更。
- 关联高质量测试套件。筛选出包含高质量测试的 PR,用于验证代码变更。
- 自包含。变更限制在少数几个模块内。
- 代表典型任务。从全栈任务分布中选择 PR:Scala 后端服务、Rust 系统代码、React 和 TypeScript 前端、protobuf 和 gRPC 合约,以及 Bazel 配置。
有了候选 PR 之后,我们专注于通过以下步骤构建规范明确的任务:
- 提炼意图并总结为 prompt。我们阅读 PR 以理解它的真正目的,然后描述我们想要的产出。通常这意味着重写 PR 描述:陈述问题或目标,列出任何约束条件,并去掉对解题方案的描述。例如,必须删掉"为什么某 bug 修复是正确的"这类解释,因为那会让任务太简单。
- 拆分相关测试。非测试文件就是模型需要自行复现的变更,所以我们把测试文件放在一边,并确保能够编译。我们的构建系统已经可以确定原始 PR 中涉及的文件依赖了哪些测试目标,因此我们完整运行了所有这些测试目标。
这个过程的产出就是基准测试中的一个任务。以下是一个简化示例:
在
feature_flags.py中添加一个新的 feature flagenable_parallel_ingestion,默认值为False。在ingestion_service.go中,当 flag 为True时,将数据摄入拆分为两个并行 goroutine,每个处理一半的分区。保持现有的错误处理和日志记录行为不变。
虽然我们使用了脚本和 AI 来生成候选任务,但所有任务都是人工评估的。在某些情况下,我们发现原始 PR 中的测试需要重写,以允许替代实现或使其更严格——我们手动完成了这些工作(不使用 AI)。同样,我们也发现有些任务的描述需要改进,使其更加规范明确。
额外的防作弊措施
在早期实验中,有几个模型的得分看起来好得不真实,所以我们手动检查了运行轨迹以理解发生了什么。我们发现,由于我们最初的设置,"正确"的实现仍然可以从工作目录的 Git 历史中恢复!每个任务都源自一个已合并的提交,所以没有任何东西阻止一个拥有 shell 的 Agent 向前翻阅 git 历史找到答案。为了解决这个问题,我们封锁了 git 历史:在每次运行期间,我们将工作副本与仓库完全隔离。
我们使用标准开箱即用的设置来实例化编程 Agent 框架和模型,配备 Databricks 工程师可用的所有常用工具。
当 Agent 明确表示已完成任务时,我们对代码进行 checkpoint,补上被拆分出去的测试,然后运行测试来判定该模型+框架组合是否"通过"。我们不使用 LLM 评审来评估正确性,因为我们发现这种方式会奖励"听起来对"而非"真正对"。
接下来做什么?
我们从一个简单的问题开始:能不能更高效地使用编程 Agent?答案是明确的"能",而且因为我们可以用数据驱动,我们可以开始构建自动选择正确模型和追踪效率的能力。
任何公司都可以做到同样的事。任何拥有已合并 PR 积压的团队,就已经坐拥一个没有模型训练过的基准测试——由你自己团队写的测试来评分。我们正在积极添加更多任务(尤其是更难的任务),并计划让每个新的 Agent/框架都跑一遍,从而对自己的选择更有信心。
在 Databricks,我们一直警惕锁定——不只是对供应商的锁定,还有那些让团队随时间推移变得不那么灵活的假设。同样的直觉塑造了我们早期对开放格式和标准的押注,也塑造了我们现在对待 AI 的方式:衡量什么真正在我们发布的代码上有效,给工程师空间在一致的护栏下跨模型和框架切换,并做优化以有效使用 AI。
在后续的博客中,我们将进一步讨论如何在 Unity AI Gateway 和 Omnigent 中使用智能路由功能,帮助开发者在使用最智能 Agent 的同时保持高效。
作者:Databricks Engineering Team