AI 代码审查如何降本 68%:缓存复用与调用优化
AI 代码审查的成本上涨,不一定是架构发生了大变化,也可能是模型选择、上下文长度和调用次数共同累积的结果。
这个案例中,系统依旧采用“协调器分配任务、多个专项审查器执行”的架构,但单次审查成本从 4 月的约 1.19 美元,升至 8 月底的约 2.46 美元。
经过一轮优化,在代码质量审查保持高推理强度的配置下,单次平均成本降至 0.77 美元,相较优化前成本最高的两周平均值 2.43 美元,下降约 68%。审查耗时中位数也从 4 分 09 秒缩短到 1 分 32 秒。
一、缓存命中率很高,为什么账单仍在上涨?
系统的缓存命中率从 4 月的 85.7% 提升到了 9 月的 97%,但每次审查处理的 token 总量也从 91 万增加到 299 万。
命中率提高,并不能抵消上下文规模大幅增长带来的成本。 即使缓存读取便宜,反复读取巨量上下文仍会产生可观开销。
优化开始前,推理费用大致分为:
- 输出 token:23%;
- 缓存写入:43%;
- 缓存读取:34%。
以代码质量审查器为例,每次审查平均调用模型 16.6 次,累计写入缓存约 7.2 万 token、读取约 108 万 token。由于每轮调用都会携带对话上下文,调用次数与上下文长度会共同推高成本。
因此,团队把重点放在两件事上:让首次请求也能复用缓存,以及减少每次审查的调用轮数。
二、修正缓存边界,让不同仓库共享固定前缀
每个审查会话都有约 1.1 万至 2.1 万 token 的通用工具定义和代理提示词,按理说应当很容易复用。但实际测量发现,会话首次请求的缓存命中率只有 14%—26%。
问题出在系统提示词的拼接方式:
- 先放入通用代理提示词;
- 再追加工作目录、日期等环境信息;
- 仅在整个块的末尾设置缓存断点。
这样一来,通用部分没有被单独缓存。不同仓库的环境信息不同,也就无法共享这个前缀。
修复方法是把通用提示词与环境信息拆开,在通用部分末尾单独设置缓存断点,并确保其前面没有仓库特定内容,工具描述在不同任务间也保持一致。
这里的关键是:不仅要有相同内容,还要让相同内容处于可独立复用的前缀边界内。
与此同时,团队把内部规范审查器反复读取的参考索引直接放进提示词,省去了开头读取这些文件的工具调用。该审查器每次运行的模型调用次数从 15.9 次降至 9.6 次,成本从 1.12 美元降至 0.72 美元。
这两项调整上线后,整体单次审查成本从 2.47 美元降至 2.02 美元。
三、减少无效调用,比压缩提示词更直接
接下来的优化并不复杂,但覆盖了多个容易被忽视的浪费点:
| 优化措施 | 发现的问题 | 调整方式或效果 |
|---|---|---|
| 隐藏无权使用的工具 | 子审查器会尝试发布最终评论,被服务端拒绝后再花一轮调用恢复 | 移除工具,避免无效调用,并减少约 2900 token 的工具定义 |
| 并行读取文件 | 代码质量审查器 64% 的轮次只调用一个工具,通常只读取一个文件 | 在首次响应中批量读取共享上下文和补丁,读取量上限为 150 KB |
| 跳过无变化的专项复审 | 约三分之二的任务是同一合并请求的第二次或后续审查 | 对没有新增相关内容的专项审查直接跳过 |
| 发布评论后立即结束 | 评论发布成功后,模型还会读取完整对话,生成无人查看的总结 | 每个任务节省约 0.07 美元 |
| 区分超时与服务故障 | 审查超时被当成服务故障,以相同限制重新执行 | 配合延长时限、降低推理强度,安全审查超时率从 1.8% 降至接近零 |
| 真正停止已取消任务 | 抽查的 446 个已取消任务中,358 个仍运行到审查结束 | 每 15 秒检查任务状态,取消后停止运行 |
这些调整合计使每次审查成本进一步下降约 0.20 美元。
四、多个审查器也能共享同一份上下文
代码质量、安全和性能审查器需要查看相同的合并请求与补丁。团队因此把它们的首次请求整理成统一结构:
- 相同的工具定义;
- 共享规则文件;
- 包含全部补丁的合并请求内容;
- 最后再追加各自的专项指令。
这样,三个审查器便能共享前面的缓存内容。
但缓存写入存在时序约束:写入请求开始流式返回后,其他请求才能读取这份缓存。因此,第二、第三个审查器会等待第一个开始返回,最长等待 20 秒。
这会增加少量启动延迟,但另外两个审查器每个可节省约 0.09 美元。
五、工程优化之外,还要评估模型和推理强度
按团队的估算,约四成节省来自代码与流程优化,其余来自模型及推理强度调整。
例如,同一模型下,采用中等推理强度的完整审查任务完成时间缩短了约 20%。部分对比中,更高推理强度增加了费用,却没有带来有意义的发现数量提升。
不过,这些数据需要谨慎理解:团队明确指出,按每次发布和配置变化前后的工作时段进行比较,并不构成严格的受控实验。不同优化之间的收益也不宜直接视为可独立相加的因果效果。
对多代理系统的启示
这次实践更值得复用的是排查顺序,而非某个具体模型配置:
- 拆开看账单:区分输出、缓存写入和缓存读取费用。
- 单独检查首轮请求:总体缓存命中率可能掩盖首次请求的浪费。
- 检查缓存边界:通用内容与动态内容应分开组织。
- 追踪每轮调用的用途:优先清除权限错误、重复读取和无用收尾。
- 检查任务生命周期:已取消、已完成的任务,不应继续消耗推理资源。
- 用评估决定模型配置:更贵的模型或更高推理强度,并不自动意味着更有价值的审查结果。
