深度·Databricks·2026.08.06

在数百万行代码库上给编程智能体做基准测试

Databricks 用真实 PR 自建编程智能体基准:开放模型已入第一梯队,token 单价不代表任务成本,简单脚手架反而最强。

在 Databricks,随着我们在工程上大力采用 AI,软件的构建方式正在快速变化。过去一年里,用于代码生成的模型和脚手架(harness)生态迅速扩张,开发者的选择比以往任何时候都多。选择越多,就越需要弄清楚:哪些编程智能体(coding agent)在真实编码任务上表现最好,以及任务性能如何随价格变化。

本文分享我们在 Databricks 搭建的内部编码基准测试(benchmark)的结果与方法论。这套基准基于工程师在 Databricks 代码库上实际完成的编码任务来评估各种工具。任务涉及对一个数百万行代码库的修改,覆盖多种主流语言(Python、Go、TypeScript、Scala 等),任务与参考答案都经过仔细审校以确保准确。这套基准不求全面,但过程中得到的洞察已经切实提升了我们工程团队使用编程智能体的效率。下图展示了各模型与脚手架在整个基准上的得分:

基准测试上的成本与性能对比

Figure 1: 基准测试上的成本与性能对比

我们分析得出的主要结论有:

  1. 编码任务的帕累托前沿(Pareto frontier,即给定成本下的最优质量)同时包含来自 OpenAI、Anthropic 以及开源社区的模型。这意味着今天只有混合使用多种工具,才能获得前沿水平的性能。
  2. 开放模型——尤其是 GLM 5.2——现在已经能应对最高难度级别的任务。
  3. 模型的 token 单价并不能很好预示端到端任务的实际成本。更大的模型可能 token 效率高得多,整体成本反而更低。
  4. 调用模型所用的脚手架对成本和质量影响巨大。在我们的工作负载上,很多情况下像 Pi 这样的简单脚手架表现最好。

下面逐条展开。

模型大致聚成几个「能力层级」

具体结果差上一两个百分点,在真实任务中往往会被抹平。我们更关注的是那些能帮助我们判断「什么任务该用什么模型」的规律性模式。事实上,结果显示模型和脚手架清晰地聚成了 3 个能力层级(capability tier)。

模型的能力层级

Figure 2: 总体结果呈现出三个明显的能力层级,每个层级内哪些模型有效则各有差异

在性能顶端,最聪明的模型能高效解决各类问题,但成本非常高。中等和较低智能水平的模型在常见任务上依然很有效,而且在很多情况下要便宜得多。

工程师日常做的事情五花八门,复杂度差异很大:翻转一个开关、更新配置这类常见运维任务并不需要极其聪明的模型,但深层的设计探索需要。然而过去我们的默认模型一直是最贵的那几个。基于这次分析,我们决定把更多工作推给 Haiku 和 GPT 5.4 Mini 这个级别的模型。

开放模型已经能用于编码了

GLM 5.2 最近热度很高,我们的结果提供了证据:对很多开发者来说,GLM 可以作为日常主力模型。它落在最高能力层级,质量上与 Opus 4.8 在统计意义上打平,但每个任务成本为 1.28 美元,而 Opus 为 1.94 美元。

GLM 的质量得分与我们从内部开发者那里拿到的定性反馈一致——他们一直在试点用 GLM 做日常开发。鉴于它在日常编码任务上的出色表现,我们一直专注于以最佳性能来 serve GLM,而证据表明,是时候开始把这类模型部署为编码的日常主力了。

单任务成本 vs 单 token 成本

开发者常常凭 token 价格来估算一个模型完成编码任务会有多贵。但我们发现,由于各模型推理效率的差异,token 价格往往无法很好预示整体任务成本。这正说明了任务级基准测试的必要性,因为任务的形态和复杂度在不同场景下并不相同。

举个例子:Sonnet 5 的每 token 价格比 Opus 4.8 便宜约 1.7 倍,但在我们的任务上,Sonnet 的成本是每任务 2.09 美元,Opus 是 1.94 美元,而且 Sonnet 的任务完成率还低 6 个百分点(81% 对 87%)。主要原因是 Sonnet 5 工作的时间更长、读的内容更多,消耗的 token 是后者的 1.9 倍。

脚手架对效率影响巨大

当我们用同一个模型、同样的思考强度,分别在两种不同的脚手架(Claude Code/Codex 对比 Pi)下运行时,观察到单任务成本差异显著(某些情况下超过 2 倍),而质量保持不变。主要差异在于每个脚手架每一轮喂给模型的上下文量。

Pi 每轮发送的上下文大约少 3 倍。它的上下文管理更好,保持更紧凑的工作集,用更少的运行轮次完成任务。

脚手架对效率的影响

每个任务重新喂给模型的总上下文量

这里的启示并不是「某个脚手架总是更便宜」或者「官方脚手架更差」。而是说,模型选择只是拼图的一块。正是为了获得这种灵活性,我们才投入建设 Omnigent,让模型与脚手架的切换无缝进行。

为什么要自建基准测试?

SWE-Bench 和 TerminalBench 这类公开基准测试很有用,但它们回答不了我们的问题。原因有几个:

  • 任务是公开的,其答案会随时间泄漏进训练数据。
  • 我们发现这些结果对我们的代码库没有代表性——我们的代码库横跨 10 多种语言,包含大量用 Scala、Go、Rust、Java 和 Python 编写的服务,还有 Bazel、Protobuf 等等。

基于我们自己的 PR 构建基准测试,能让我们在推出各种优化措施时更有信心:不会因此拖累开发者。

我们是如何构建这套基准测试的

我们用 Unity AI Gateway 捕获了所有编码交互的日志,这使我们得以分析工程师借助编程智能体处理的任务复杂度。任务复杂度的分布相当多样:约四分之一被标记为低复杂度工作,约 60% 为中等复杂度。

我们的工程师实际让编程智能体做什么

然而,工程师默认使用的都是昂贵的模型,所以效率提升的空间显然巨大。

任务构建

我们的工程师每天合入数千个代码变更,所以现成有一个很好的数据集可用。一个优质的 PR 是信息丰富的产物:其中有展示开发者迭代过程的提交记录、人工评审,以及帮助验证代码变更符合意图的测试。但要从中构建出高质量的基准测试,还需要多道质量检查和过滤:

  • 时效性: 我们从近期历史中取样,让任务反映当下的构建方式,包括当前在用的框架、模式和约定。
  • 人类撰写: 过滤掉机器人提交、服务账号、完全由 AI 生成的变更以及自动生成的变更。
  • 附带高质量测试套件: 只保留包含高质量测试、能验证代码变更的 PR。
  • 自包含: 变更范围限于少数几个模块。
  • 代表典型任务: 我们选取的 PR 覆盖全栈的任务分布:Scala 后端服务、Rust 系统代码、React 和 TypeScript 前端、protobuf 与 gRPC 契约,以及 Bazel 配置。

任务构建的分步流程

有了候选 PR 之后,我们专注于通过以下方式构建定义良好的任务:

  1. 提炼意图并总结为提示词。 我们阅读 PR 以理解它的实际目的,然后描述想要的结果。通常这意味着重写 PR 描述:陈述问题或目标、点明各项约束,并删掉对解决方案的描述。删掉诸如某个 bug 修复为什么是正确的这类解释很重要,因为那会让任务变得过于简单。
  2. 拆分出相关测试。 非测试文件是模型必须自行复现的变更,因此我们把测试文件单独放在一边,并确保代码可以编译。我们的构建系统本来就能确定哪些测试依赖于原 PR 中被改动的文件,于是我们完整运行了所有这些测试目标。

这一流程产出的就是基准测试中的单个任务。下面是一个简化示例:

{
  "id": "go_bugfix_01",
  "category": "Go — bugfix",
  "base_commit": "fga6rfb27…",
  "prompt": "Workflow submissions in the deploy service are deduplicated with an
             in-memory cache. Two distinct templates submitting at the same time
             can collide on the same cache key, and one submission is silently
             dropped. Make the dedup key distinguish submissions that should be
             treated as different.",
  "source_files": ["services/deploy/cache/recent_cache.go", "..."],
  "test_targets": ["//services/deploy/integration/gateway:gateway_test"]
}

虽然我们用脚本和 AI 来生成候选任务,但每个样本都经过人工评估。在某些情况下,我们发现原 PR 中的测试需要重写,以允许不同的实现方式或让校验更严格——这些重写都是手工完成的(不借助 AI)。同样,我们也发现有些任务描述需要改进,才能让任务定义得足够明确。

测试套件改写前后对比

Figure 3: 我们测试套件的一处改写前后对比:原先的测试锚定在精确字符串匹配上,导致模型尝试解题时出现一些失败。这不是测试非确定性输出的好方式,因此被改写为按行为评分。

我们用各编程智能体脚手架和模型标准的开箱配置进行实例化,并配备 Databricks 工程师日常可用的全部常用工具。

搭建与评审流程

当智能体明确表示任务已完成时,我们就对该代码做检查点,把之前扣留的测试打补丁进去,然后运行测试,判定该「模型 + 脚手架」组合在此任务上是否「通过」。我们没有使用 LLM 评委(LLM judge)来评估正确性,因为我们发现这种方式奖励的是「听起来对」而不是「真的对」。

额外的防护栏

在早期的实验中,有几个模型的得分好得不像真的,于是我们人工检查了执行轨迹,想弄清这些智能体轨迹里发生了什么。我们看到的是:由于我们最初的设置,「正确」实现仍然可以从工作区的 Git 历史中恢复出来!每个任务都源自一个已合入的提交,所以没有任何东西能阻止一个拥有 shell 的智能体沿 Git 历史向前走并找到它。为解决这个问题,我们封存了 Git 历史:在每次运行期间,将工作副本与代码仓库完全隔离。

额外的防护栏

接下来呢?

我们从一个简单的问题出发:能不能更高效地使用编程智能体?答案是明确的「能」。而且因为我们能以数据驱动,所以可以开始构建自动选择合适模型并跟踪效率的能力。

任何公司都可以做同样的事。任何有一堆积压已合并 PR 的团队,其实都坐拥一个现成的基准测试——没有任何模型在其上训练过,并由你的团队自己写的测试来评分。我们正在持续添加更多任务(尤其是更难的),并计划让每一个新的智能体/脚手架都跑一遍,从而对自己的选择更有信心。

在 Databricks,我们一直警惕锁定——不只是对厂商的锁定,还有那些会让团队随时间变得僵化的思维定式。同样的直觉塑造了我们早期对开放格式与标准的押注,也塑造了我们今天对待 AI 的方式:在实际交付的代码上衡量真正有效的东西,让工程师能在一致的防护栏下跨模型、跨脚手架迁移,并不断优化以高效地使用 AI。

在后续的博客中,我们会进一步介绍如何利用 Unity AI Gateway 和 Omnigent 中的智能路由功能,帮助开发者在保持效率的同时用上最聪明的智能体。