分享开发者如何通过选择高效模型(如 Claude Opus)来优化 API 调用成本的真实案例。
上周我们写了一篇文章,介绍如何把数 TB 的 CI 日志交给 LLM 处理。Hacker News 上的大多数问题关注的却不是日志,而是 Agent:用了哪些模型、它们如何协作,以及这一切要花多少钱。
如今,我们运行的是 Opus 4.6,成本反而低于过去全部使用 Sonnet 4.0 时的水平。
原因主要在于 Opus 不需要做什么:80% 的失败根本不会传到它那里;即使传到了,它也不需要亲自读取任何一行日志。
整个架构是这样的:
上周,我们分析了大约 4,000 次 CI 失败。其中 818 次是新问题,其余 3,187 次都是已知问题再次出现:测试偶发失败、基础设施短暂异常,或者我们已经检测过的网络抖动。
既然 80% 的情况下答案都是“这是重复问题”,那就完全没必要唤醒一个昂贵的模型。遗憾的是,我们无法通过确定性方法识别重复问题:同一个 job 可能因为完全不同的原因失败多次,因此必须真正查看日志,才能判断这个问题以前是否出现过。
最初,我们使用 Sonnet 来兼顾成本和性能。它确实能工作,但结果却是两头不讨好:成本依然很高,效果又不如 frontier model。
后来,我们改用了“triager”模式:让一个 Haiku Agent 承担非常明确且范围极窄的任务——这个问题是否已经被记录?如果已经记录,到此为止;如果没有,则升级给 Opus。
事实证明,使用 Haiku 识别重复问题并不容易。我们需要尽可能降低任务难度,因此为历史失败记录附上了错误信息,并给 Haiku 提供了两个搜索工具:一个用于精确匹配已知错误片段,另一个用于对相似但不完全相同的错误执行语义搜索(pgvector)。RAG 已死,但语义搜索还挺好用。operator does not exist bigint character varying 和 migration type mismatch on installation_id 是两个不同的字符串,背后却是同一个根因,而语义搜索可以把这种关联找出来。
Haiku Agent 会读取日志、搜索错误信息、尝试与已知失败进行匹配,然后作出判断。只要拿不准,它就会升级给 Opus。误报只会多花一点钱;漏报则意味着我们可能错过一个真实问题。
每 5 次失败中,有 4 次不会传到 Opus。通过 triager 完成一次匹配,其成本大约只有完整调查的二十五分之一。
不少人问,我们如何处理超过 20 万行的日志。答案是:我们不会把这些日志塞进 prompt,而是为 Agent 提供一个连接 ClickHouse 的 SQL 接口,让它自行查询需要的信息。
这样做不只是为了节省 token。如果你把一组特定的日志行交给 Agent,就意味着在尚未弄清问题本质之前,你已经替它判断了哪些内容与问题有关。Agent 会锚定在你提供的信息上。如果真正的原因藏在其他地方,你反而会让它更难找到根因。这就像你不应该用“我觉得问题出在这个文件里”来开启一次调试:调查尚未开始,你就已经为它引入了偏见。
上周我们详细介绍过这套 SQL 方案。简单来说,系统中有一张保存原始数据的表(github_logs,每行日志对应一条记录),以及一组保存预聚合数据的物化视图,其中包括各 workflow 的失败率、job 执行时间和结果数量。大多数调查会先从物化视图入手,缩小原因范围;确有需要时,再深入查询原始日志。
我们不会告诉 Agent 应该查询哪张表,而是利用查询响应本身逐步引导它。如果某个查询返回的行数过多,我们会截断结果,并建议它使用更具体的物化视图。如果日志尚未被采集,我们会引导它使用 GitHub CLI。这样一来,Agent 可以自行判断需要什么,而我们不必提前预判所有可能的调查路径。
Opus 会查看哪里发生了失败、形成假设,然后生成 Haiku sub-agent,让它们执行实际的调查工作。每个 sub-agent 都会收到一段由 Opus 编写的 prompt,其中会明确说明要搜索什么、如何搜索,以及应该返回什么。sub-agent 最多只能存在一层,不能继续生成自己的 sub-agent。无限制的任务扇出,正是成本失控的根源。
几周前,同一个 commit 上有三个 Storybook CI job 失败了,全部崩溃在 pnpm install 阶段。
Opus 首先让一个 sub-agent 从失败的 pnpm install 步骤中提取错误信息。当时 ClickHouse 中尚未写入这些日志,因此 sub-agent 转而使用 GitHub CLI。
获取这次 run 的 CI 日志。返回 pnpm install 步骤中的确切错误信息和完整错误输出,尤其是最后 50~100 行。
结果是:gyp ERR! not found: make。由于 runner 上没有安装 make,[email protected] 无法完成编译。
Opus 搜索了已有的 insights,但没有发现匹配项;随后,它查询 ClickHouse,获取了 14 天内的失败趋势:
Feb 23: 0.2% failure rate
Feb 24: 1.1%
Feb 25: 8.0% <- inflection point
显然,2 月 25 日发生了某种变化。Opus 随即生成了 sub-agent #2:
调查 2 月 24 日至 25 日前后发生了什么变化。失败率从 0.2% 上升到了 8%。错误是 gyp ERR! not found: make。针对这段时间,对 workflow 文件和 package.json 执行 git log。
原来,构建依赖在一次不相关的迁移中被移除了。对那次迁移而言,这个改动本身没有问题,但 re2 仍然需要 make 才能完成原生编译。Opus 又生成了 sub-agent #3,用于验证当前的 workflow 状态,随后创建了一条包含根因和修复方案的 insight。
整个过程中,orchestrator 自己没有读取任何一行日志、git 历史或代码。
这里有几点值得注意:
成本。 Haiku 处理了全部输入 token 的约 65%,却只占 LLM 总支出的约 36%。昂贵的模型负责思考,便宜的模型负责阅读。如果没有这样的模型分层,每日账单将增加一倍以上。
Opus 会边调查边规划。 它从一个假设出发,但每个 sub-agent 返回的结果都会影响下一步。在这次调查中,它先获取错误信息,再搜索历史记录,然后调查发生了哪些变化。每一轮结果都会为下一轮提供依据。超过三分之一的调查会经历多轮交互,而新问题所需的调查深度大约是已知问题的两倍。
上下文卫生。 orchestrator 的上下文会始终保持干净:它接收的是 sub-agent 提供的结构化摘要,而不是原始日志输出。每个 sub-agent 都从一份干净的上下文开始,完成任务后,其上下文便会被丢弃。tool call 的输出积累得非常快,而同一次会话中早期遗留的陈旧上下文,会降低后续决策的质量。
定向搜索。 “返回 pnpm install 步骤中的确切错误信息”和“分析这些日志”是两种截然不同的 prompt。Opus 决定要找什么,Haiku 负责把它找出来。Haiku 的输入/输出比为 86:1——阅读大量内容,只返回高度聚焦的摘录;orchestrator 的这一比例约为 50:1——负责综合信息并作出决策。Haiku 吞下海量数据,因此 Opus 不必亲自处理它们。
六个月前,我们使用的是 Sonnet 4.0。它很难编写出正确的 ClickHouse 查询:查错表、漏掉过滤条件、读取远超所需的数据。当时的 Haiku 4.0 除了进行“是/否”分类,几乎派不上其他用场。
如今,Opus 4.6 已经能够规划调查流程,并为 sub-agent 编写精确的 prompt。Haiku 4.5 也能够处理范围狭窄、目标明确的任务,因为任务边界被收得足够紧,一个速度快、成本低的模型已经可以胜任。
升级到 frontier model,反而降低了成本。
我们是为 CI 日志构建这套系统的,但这一模式适用于任何事件量巨大的场景:安全日志、IoT 遥测数据、金融数据。大多数事件都是噪声或重复事件,昂贵的模型只应该看到那些不属于这两类的内容。
还有第四层我们尚未介绍:重新评估。系统会定期检查先前得出的结论是否仍然成立,关闭已经失效的 insights、发现误报,并验证修复是否真正生效。这个话题值得单独写一篇文章。
我们仍在持续调整 sub-agent 的边界。有时,生成一个 sub-agent 的成本反而高于直接在当前流程中完成任务,因为初始化开销超过了节省下来的成本。
最难的部分并不是让 Agent 变得更聪明,而是构建一层又一层的机制,确保它不该运行时就不会运行。