Cloudflare在AI代码审查单次成本翻倍后,追查了Token消耗。团队通过提示词缓存和智能模型路由,将推理费用降低68%,同时加快审查速度。
我们在 4 月的上一篇博客中介绍 AI 代码审查工具时,每次审查的平均成本为 1.19 美元,还算可以接受。到了 8 月底,这个数字已经飙升至约 2.46 美元。我们并没有对架构做出任何重大调整:协调器仍然将工作分配给同一组专项审查器,审查的深入程度也仍然由相同的风险等级决定。
9 月中旬左右,我们让智能体查清这些 token 究竟花在了哪里,并大幅削减用量。在代码质量审查保持高推理强度的情况下,现在每次审查的平均成本仅为 0.77 美元,相比优化开始前成本最高的两周里平均每次 2.43 美元,下降了 68%。我们的主要目标是降低成本,但在这项工作中,我们也成功优化了审查器,使审查耗时的中位数从之前的 4 分 09 秒缩短到了现在的 1 分 32 秒。
约 40% 的成本节省来自对审查器代码的修改和优化,其余则来自对模型选择及其推理强度的试验。
下图展示了自 3 月以来每个工作日的平均审查成本。图中固定了各审查等级的占比,因此某一天大型审查较多,也不会导致数据偏斜。

单次审查成本从 3 月的 1.22 美元,稳步上涨至 6 月的 1.44 美元、7 月的 1.74 美元,最终在 8 月达到 2.32 美元。其中一小部分原因是,落入最高成本等级的审查变多了:3 月占 59%,到 8 月则达到 65%。我们认为,原因很简单:模型正在变得更好,能力也更强。工程师自然会提交规模更大、复杂度更高的改动,因而更多改动被归入我们的“完整审查”等级。不过,我们发现,成本上涨的大部分实际上源于这几个月里的模型调整。
8 月初,我们将所有审查器都切换到了 Anthropic 模型,使每个任务的成本从 1.38 美元上升到了 2.03 美元。之所以做出这个选择,是因为评估结果表明,在这项特定工作负载上,Anthropic 模型的表现优于其他模型。
综合性能和可靠性方面的考量,我们的内部规范审查器 Codex 最终采用了 Claude Opus 4.7。这使它的单次审查成本从约 0.25 美元上升至 8 月的 0.88 美元,甚至比协调器智能体本身还贵。
缓存命中率也有所提高,从 4 月的 85.7% 上升到了 9 月的 97%;与此同时,每次审查的 token 数量却增加到了原来的三倍多,从 91 万增长到 299 万。即使缓存读取价格很低,当 token 数量变成三倍时,再高的命中率也依然意味着高得多的成本。
最大的变化之一是,审查器现在运行在 OpenCode v2 上,而 v2 是对 v1 的一次彻底重写。在将所有审查从 v1 切换到 v2 之前,我们以影子对比模式,让每个金丝雀仓库中的每次审查都同时在 v1 和 v2 上运行。在测试的合并请求中,两者对是否批准的判断有 98% 一致,因此我们在 10 月初一次性完成了全面切换,并删除了 v1 代码。对比使用相同模型的两天数据,v2 上完整等级审查的完成速度快了约 13%。我们目前也在试验运行内部 AI 审查器的其他方式,包括使用 pi-durable,让它完全运行在 Durable Objects 上。希望很快就能分享更多进展,敬请关注!
在我们开始专注于成本优化之前,AI 审查支出的构成是:输出 token 约占 23%,缓存写入占 43%,缓存读取占 34%。这意味着,大部分钱都花在了模型已经看过,或者即将再次看到的上下文上。
仔细查看单个会话,就不难理解这一点。代码质量审查器平均每次审查会调用大语言模型(LLM)16.6 次,在这些调用中,它向缓存写入了 7.2 万个 token,又读取了 108 万个 token,因为每次调用都会将完整对话作为上下文发送。会话成本会随着调用次数和上下文大小一起增长,而在过去几个月里,我们一直在逐步增加这两项。
值得花几分钟了解一下 Anthropic 的提示词缓存机制。它会缓存提示词中已经由大语言模型(LLM)计算过的部分。这些部分称为前缀,因为缓存只适用于对话开头的消息,而不适用于中间或末尾的消息。这意味着,后续请求可以复用先前的前缀,直到对话内容与缓存内容开始出现差异的位置。在我们的场景中,AI 审查器会话开头的运行框架工具、系统提示词和消息很少变化,因此非常适合使用前缀缓存。只有在缓存断点结束的位置,前缀才会被存储。缓存写入的价格是普通输入价格的 1.25 倍,读取价格则是 0.1 倍;如果没有被读取,缓存条目会在五分钟后过期。
这意味着我们需要解决两件事:让每个会话的首次请求读取缓存,而不是写入缓存;让每个会话使用更少的调用。
从 9 月 14 日开始,审查工具会记录每个审查器首次 LLM 调用的 token 用量,以及第一个系统消息块的哈希值;哈希依据的是实际发送给模型服务商的原始内容。
数据显示,会话的首次请求只有 14% 到 26% 的概率能找到已缓存的前缀。每个会话都以相同的工具定义和智能体提示词开头,这部分内容有 1.1 万到 2.1 万个 token,在我们审查的所有仓库中都完全一致,因此几乎都应该命中缓存。
问题最终出在 OpenCode v1 构造系统提示词的方式上:它先创建一个包含智能体提示词的消息块,再在后面附加包含工作目录和日期的环境信息,并将唯一的缓存断点放在整个消息块的末尾。共享部分从未被单独存储,因此,一个仓库中的审查永远无法复用另一个仓库中的审查器写入的缓存。
9 月 16 日,我们在每个智能体提示词的末尾添加了一个标记,并通过插件钩子在该标记处将消息块拆成两部分。现在,前半部分会在所有任务共有的字节内容处结束,并拥有自己的缓存断点。为了保持这种状态,我们必须移除它前面所有与仓库相关的内容,因此禁用了可用技能列表,并确保工具描述在不同任务之间保持不变。

同一天,我们还将内部规范审查器 Codex 的参考索引移入了它的提示词,其中包括一张内部规范表、一个技能文件和一份参考文件列表。在此之前,每个会话都会花费最初三个回合,通过工具调用来读取这些文件。调整后,它每次运行的 LLM 调用次数从 15.9 次减少到 9.6 次,单次运行成本也从 1.12 美元降到了 0.72 美元。
这些改动共同将每次审查的成本从 2.47 美元降到了 2.02 美元,而且发布一开始,成本下降就已经十分明显。
下一批改动于 9 月 21 日和 23 日上线。单独看,每项改动都不算特别显眼,但合起来让每次审查又节省了约 0.20 美元。
子审查器能够看到用于发布最终审查评论的工具。在 57% 到 76% 的会话中,它们会尝试调用这个工具,随后被服务端检查拒绝,再花费一次调用来处理这一失败。
这样就省掉了一次无效调用,并且每个子审查器请求中的工具定义还减少了约 2,900 个 token。
在代码质量审查器的所有回合中,有 64% 的回合里,模型只进行了一次工具调用,通常只是为了读取一个文件。
现在,审查器会在首次响应中读取共享上下文和分配给自己的全部补丁,总量最多为 150 KB。
约三分之二的任务是对同一个合并请求进行第二次或后续审查,而其中往往有三个审查器没有任何新内容需要检查。
在完整等级的再次审查中,AGENTS.md 审查有 56% 的情况会被跳过,性能审查有 23% 会被跳过,文档审查有 13% 会被跳过。
OpenCode 会将结果再传回模型,让它多执行一个回合,重新读取整段对话,只为写出一份没人会看到的总结。
每个任务因此节省了约 0.07 美元。
此前,审查器达到时间上限时,会被当作模型服务商故障处理,随后以相同的时间上限重新启动。
配合更长的时间上限和更低的推理强度,安全审查器的超时率从占运行次数的 1.8% 降到了几乎为零。
GitLab 的 Kubernetes executor 在任务被取消时,不会向任务脚本发送信号。在抽样的 446 个已取消任务中,有 358 个仍继续运行,直到审查完成。
任务每隔 15 秒检查一次自身状态,如果已被取消就停止运行。
改动最复杂的一项,是让三个审查器共享同一个前缀。现在,代码质量、安全性和性能审查器发出的首次请求完全相同,依次包含相同的工具、共享规则文件,以及包含全部补丁的合并请求。各审查器自己的指令放在这些内容之后,我们还确保由其中一个审查器写入缓存,另外两个读取缓存。

只有写入缓存的那次响应开始流式输出后,其他请求才能读取该缓存条目,而这三个审查器会同时启动。因此,第二个和第三个审查器会等待第一个审查器的响应开始,最多等待 20 秒,然后再发送自己的首次请求。这会让它们稍微慢一点,但每个审查器能节省约 9 美分。
下一张图展示了从 9 月初开始的同一条曲线,并标出了每次改动。

当我们按照每次发布和配置变更划分统计时段后,发现代码改动在节省的成本中占比较小。

在节省的 1.67 美元中,代码改动贡献了 0.65 美元,模型和推理力度调整贡献了 1.06 美元。每一步都是将改动前后的工作日时段进行比较,因此这并不是一项统计上严谨的对照实验。
自 8 月以来,协调器一直在运行三种模型与推理力度的组合。在同一个模型上,采用中等推理力度的完整档任务完成时间缩短了 20%,因此我们停用了另外两种组合。在随后的一次随机分组对比中,采用中等推理力度的 Opus 5.5 与 Opus 5 相比,审查结果和发现的严重问题都没有可测量的变化,因此我们切换到了 Opus 5.5。高推理力度的 Opus 5.5 成本高于中等推理力度,但发现的问题数量并没有显著增加。
Opus 5.5 的每 token 价格比 Opus 5 便宜,但降价只贡献了约 0.20 美元的成本降幅。其余节省来自发送和生成的 token 数量减少。自 9 月初以来,每次审查的缓存读取量下降了 82%,缓存写入量下降了 54%,输出量下降了 67%。
最近一次改动是引入新模型。10 月 3 日,我们将每个 Sonnet 审查器的任务按 50/50 分配给 Sonnet 5 和 Sonnet 5.5。两天半后,Sonnet 5.5 的单次运行成本在代码质量审查中降低了 62%,在性能审查中降低了 58%,在安全审查中降低了 47%。这两个模型的每 token 价格相同,因此全部节省都来自 token 用量减少。在完整档审查中,5.5 审查器报告的严重和警告级问题数量与 Sonnet 5 基本相当,因此我们在 10 月 5 日将它们全部切换到了 5.5。
在 Sonnet 5.5 上采用中等推理力度时,代码质量审查器每次运行调用 LLM 的次数从 6.2 次降到了 1.7 次,而且超过一半的运行只需一次调用就结束了,这意味着它们没有打开仓库中的任何其他文件。采用高推理力度时,每次运行会调用 3.4 次。我们担心这会让审查变得不够深入,下面的质量部分会讨论这一点。
为了确认在较高档位的审查中,提高推理力度能否找回那些漏掉的问题,从 10 月 5 日起,我们将 5.5 上代码质量审查器的任务按 50/50 分配给中等和高推理力度。一天后,高推理力度的单次运行成本高出 61%,报告的严重和警告级问题多出 58%。这相当于每次审查多花约 2 美分,而下面的质量部分表明,这些额外发现的问题值得这笔开销,因此我们正在将代码质量审查切换到高推理力度。本文的成本数据均按这一配置计算。
我们的目标并不是加快审查,但调用次数减少也意味着等待时间缩短,进而让审查更快,工程师也更满意。

最慢的那些审查改善最明显。在 9 月 14 日之前的四周里,37.9% 的审查耗时超过五分钟,现在这个比例只有 0.7%。如今第 99 百分位的审查耗时,比 4 月时四分之三的审查都要短。在此过程中,每个档位的审查都变快了:完整档审查的中位耗时从 5 分 16 秒降至 1 分 54 秒,轻量档从 2 分 58 秒降至 1 分 14 秒,微小改动档从 77 秒降至 30 秒。
到 8 月时,审查耗时的中位数比我们 4 月报告的数据慢了 14%,第 99 百分位慢了 48%,所以这次工作的很大一部分,只是让我们恢复到了之前的水平。
如果成本更低的审查发现的问题也更少,那省钱就没有太大意义,而这里的第一张图看起来正是如此。

代码质量审查器在 Sonnet 5.5 上采用高推理力度后,报告的严重和警告级问题比 8 月下旬减少了 40%。仍采用中等推理力度的安全和性能审查器,报告的问题分别减少了 56% 和 71%。仅凭高严重程度问题的数量下降,无法判断究竟是漏掉了真实问题,还是减少了噪声,因此我们从两个角度进行了分析。
首先,我们检查作者是否会针对审查器标出的问题采取行动。我们抽取了一些严重和警告级问题,将接受审查的提交与作者下一次推送的内容进行比较;如果问题指向的代码行发生了变化,就将该问题计为已采取行动。在所有时段中,距离各问题位置 30 到 40 行的代码,大约都有 15% 发生了变化,这为碰巧发生的修改提供了一个基线。

改动之前,作者会针对代码质量审查器报告的 34% 的严重及警告级问题采取行动。现在,这个比例是 59%。审查器标出的问题更少了,但被标出的问题更有可能促成修改,因此每 1,000 次审查中促成修改的问题数量与之前基本相同。现在观察到的是 132 个,之前是 145 个,两者差异处于误差范围内。综合全部四个审查器,每 1,000 次审查中促成修改的严重及警告级问题,从 229 个增加到了 321 个。
其次,我们从三个 Sonnet 审查器发现的问题中抽取了 657 个样本,让来自不同提供商的两个模型进行盲评。评判模型可以看到每个问题对应的 diff,但不知道问题来自哪个时段。意见不一致的样本交给第三个模型裁决。每个问题都被标记为重要、次要、重复、错误或无法验证,其严重程度则被标记为合理、过高或过低。

减少的大部分是次要问题,此外还有一些重复问题,以及评判模型无法通过 diff 验证的问题。每次审查发现的重要问题从之前的 0.28 个变为现在的 0.30 个,两者的区间存在重叠。更大的变化体现在严重程度上。之前,评判模型认为,每次审查中有 0.46 个被标为严重或警告级的问题,其严重程度被夸大了。现在这个数字是 0.14,下降了 70%,这是这次研究中最明确的结果。严重问题数量下降,很大程度上是因为审查器不再把次要问题标为严重问题。
因此,答案取决于具体的审查器。对于采用高推理力度的代码质量审查器,减少的主要是噪声和被夸大的严重程度,它发现的真实问题仍然与之前基本一样多。中等推理力度确实会漏掉真实问题。在随机分组对比中,采用中等推理力度的代码质量审查器促成作者采取行动的问题数量,大约只有高推理力度的四分之一,这也是我们将它切换到高推理力度的原因。性能审查也漏掉了真实问题:评判模型发现,每次审查中的重要性能问题减少了约 64%,在实际使用中,相当于每 24 次完整档审查少发现一个重要问题。安全问题减少了约四分之一,不过这一差异在统计上并不显著。这两个审查器仍采用中等推理力度,下一步是测试它们在高推理力度下的表现。
这两种方法都不完美。作者修改了被标出的代码行,只能说明他们采取了行动,并不能证明问题判断正确。评判者本身也是模型,而且每组只有 35 到 66 个问题。“现在”这个观察窗口也只有一天。即便如此,两种方法得出的结果相互吻合,而且都没有表明代码质量审查器发现的真实问题变少了。
可靠性也保持良好。未给出结论就结束的审查比例从 0.79% 降至 0.27%,安全审查器的重试率从 3.5% 降至 0.04%。
现在,每次审查的平均成本从 2.43 美元降至 0.77 美元,耗时中位数从 4 分 9 秒缩短至 1 分 32 秒。最大的一项节省,来自检查每个会话的首次请求中究竟有哪些内容实际发送到了提供商,并找到一种消息排序方式,让消息顺序在不同提交、合并请求和代码仓库之间尽可能保持一致。其余成本节省则来自持续试验不同的模型和推理强度组合,同时确保我们放下对特定模型的先入之见。
审查成本降低,并不必然意味着质量变差,但也不意味着质量就一定没问题。代码质量审查在高推理强度下发现的真实问题数量与之前相当,同时噪声大幅减少,严重程度被夸大的情况也少得多。性能审查在中等推理强度下漏掉了一些真实问题,安全审查可能也有同样的情况,因此下一步是测试这两类审查在高推理强度下的表现。如果你在规模化运行智能体,我们建议首先检查两件事:每个会话的首次请求是否确实命中了缓存,以及在你付费采用的推理强度下,审查器是否仍能发现真实问题。
如果你将参加旧金山的 Cloudflare Connect,我会在 10 月 20 日星期二太平洋夏令时间中午 12:30 的演讲《是时候自己构建,而不是购买了》中,分享我们如何自行构建这套审查器,而非购买现成产品。如果你想就本文中的任何数据提问,我也会从下午 1:10 起在演讲嘉宾交流区(Speaker Connection Lounge)等你。