ENZH
和 AI 讨论这篇文章
ChatGPTClaude

GPT-6 出了 Sol 和 Luna,我测了三轮才把 codex 的活分下去

起因

我现在用 codex 的方式大概是这样的:一个主 agent 当 coordinator,负责理解我要什么、拆任务、最后验收;它下面会派出一堆 subagent 去干具体的活,比如翻代码找东西、按定好的方案改代码、盯一个具体风险做 review。每个 subagent 都可以单独指定模型和 reasoning effort,effort 就是让模型每一步想多深,档位从 low、medium、high 一直到 xhigh 和 max。

之前我图省心,所有 role 全部用 GPT-6 Astra 配 high,coordinator 是它,翻代码的是它,改一行配置的也是它。这样确实省心,但用量烧得非常快。很多活其实用不着 Astra,比如列出某个函数的所有调用点、照着现成的写法改几个文件,让最贵的模型去干这种活,有点浪费。

GPT-6 这次在 codex 里多了 Sol 和 Luna 两个比 Astra 更轻、更便宜的模型。官方文档给 subagent 推荐的起点是 Sol/medium 和 Luna/high。我想要的其实就三件事:活干得快一点,不必要的 token 少花一点,完成质量不能掉。把一部分活路由到更小的模型上,是减少不必要的昂贵推理的一个办法,但单价便宜不等于 token 一定少、整体一定快,最后省不省还要看输出量、重试次数和 coordinator 那边多出来的活。第三件更没人能替我保证,所以还是得自己测。

最开始先拿一个很小的活试了一下,让 Luna/medium 和 Sol/high 各自去改同一个脚本,两边都过了事先写好的 20 个检查。但这种活太小了,只能算冒烟测试,说明不了它们能不能接更复杂的活,所以才有了后面三轮。

先把一件事说在前面。这篇文章里没有耗时、token、成本的数字。codex 的 subagent 工具会接受你指定的模型和 effort,但它不会给你一个独立的凭证证明后端真的跑的是这个模型,也没有靠得住的单次计费数据。所以我能测的只有一件事:同一个任务交给不同的模型和 effort,最后交付的东西对不对。省了多少钱、快了多少,要实际用一段时间才知道,而且还得把 coordinator 返工、review 的成本一起算进去。

怎么测的

一共七条路线:Astra/high 当对照,Sol 和 Luna 各跑 medium、high、xhigh 三档。每一轮的做法都差不多:

  1. 每条路线开一个全新的 agent,不带任何历史对话,显式指定模型和 effort,在一个独立的 git worktree 里干活。worktree 可以理解成同一个 repo 的另一份工作目录,改动互不干扰,跑完直接删掉。
  2. 七个 agent 拿到的任务描述一模一样,写清楚要做什么、能改哪些文件、不能碰哪些文件、要交什么报告。
  3. 评分用的测试在派活之前就写好冻结了,agent 看不到,这个一般叫 hidden test。交之前 agent 可以自己写测试、自己 debug、自己反复改;但交上来以后,不会把 hidden test 的结果反馈给它,也没有再修一轮的机会,交上来是什么就评什么。
  4. agent 自己报告的「测试全过」不算数。评分只看冻结的测试、原有的回归测试,还有评测代码实际跑出来的值。

具体跑评测、读 diff、判分的是 codex 的 coordinator,我没有逐行去看代码。我这边做的是定方向、追问:嫌测试不够硬,就让它加强,再拿真实的 PR 测;也专门问过它,有没有去看实际跑出来的值。

每条路线每轮只跑一次。所以这不是一个统计意义上的可靠性 benchmark,每个格子里就是一个样本,它能告诉我某种活在某个配置下出过什么问题,没法告诉我出问题的概率是多少。

三轮分别回答三个不同的问题,下面一轮一轮说。

第一轮:一个大 monorepo 里的三道题

第一轮用的是我自己一个产品的 monorepo,大概七千个 tracked 文件。三道题是连在一起的,都围着一条流式对话的接口:

  1. 追代码:把一次流式回复断线以后怎么恢复讲清楚,从服务端写入、鉴权、解析、重放到结束通知。评分是事先定好的 12 个事实点。
  2. 写实现:给一个解析 SSE 流的函数加上取消支持。SSE 就是服务端一条一条往下推事件的那种流,要求很细,各种换行符、半截的行、中文和 emoji 被切在两个包之间都要处理对,用户在任何时刻取消都要干净地停下来,锁和监听器要释放。冻结的测试有 29 个,另外还要过 42 个原有的回归测试和类型检查。
  3. 做 review:给一个还没合进去的 patch 做 review,里面事先埋了 3 个 bug,要求每个都给出具体的触发顺序和后果,不能只是「这里可能有问题」。
路线追代码实现补充的 EOF 取消用例review 抓到的埋点改动范围
Astra/high12/1229/293/3守住
Sol/medium12/1229/293/3守住
Sol/high12/1229/293/3守住
Sol/xhigh12/1229/293/3守住
Luna/medium12/1229/293/3守住
Luna/high11/1229/293/3越界
Luna/xhigh12/1229/293/3守住

光看冻结的测试,七条路线全过,42 个回归测试和类型检查也都过了,review 也没人报假 bug。结果这么整齐,看起来题出得有点松。

后来 coordinator 一份一份去读交上来的源码,发现一个边界大家处理得不一样。流结束的时候,缓冲区里可能还剩最后一条没换行的事件,解析器会把它 flush 出来交给调用方。调用方拿到这条以后如果取消了,下一次再去要数据,应该收到一个 AbortError。这个要求任务描述里是写了的,连「最后一条没换行的事件」这个路径都点名了,但冻结的 29 个测试刚好没覆盖到。

所以 coordinator 看完代码以后补了一个用例,七条路线都跑了一遍,也没给任何人修的机会。结果只有 Astra/high 和 Sol/high 过了,另外五条都在同一个地方漏了:交出最后一条事件之前检查了取消,交出去以后回来就没再查,下一次调用直接正常结束了。

这里要说清楚两点。第一,这个用例是看完代码之后补的,不是事先冻结的,所以单独列一栏,没混进 29 个里。第二,Sol/xhigh 挂了而 Sol/high 过了,这不能说明 high 比 xhigh 强,一个样本分不出是 effort 的影响还是运气。它能说明的是,effort 往上调并没有自动把这个漏洞补上。

Luna/high 还有两个问题。追代码那题它说 repo 里没有专门测这个流式消费逻辑的测试,其实有一个文件,里面 26 个测试。另外它把新测试加进了一个原有的测试文件,任务里写明了只能新建测试文件。加进去的测试没有削弱任何断言,但这还是越界了。「搜不到」和「不存在」是两回事,这个后面会变成一条派活规则。

第二轮:三个 repo 的跨项目题

第一轮的三道题都在同一个子系统里,覆盖面太窄。第二轮换到工作上的三个 repo:一个前端平台、一个 backend、一个数据库 schema 的 repo,出了四道题,有几道要跨 repo 改:

  1. 前端图表的数值裁剪,39 个检查,包括真实值和画出来的值要分开。
  2. backend 一个异步任务的取消原因要原样传下去,27 个检查。
  3. 前端 TS 和 backend Python 之间一个按日期筛选的接口契约,新老版本各种组合都要兼容,64 个检查。
  4. backend 加一个新的类型化字段,同时要在数据库 schema 里加迁移,39 个检查,其中包括「不相关的源码不能动」这种范围检查。

这一轮的结果很整齐。七条路线的功能全部通过,原有的 204 个回归测试每条都过,限定范围内的类型检查也都过。唯一的扣分是 Luna/high 和 Luna/xhigh 在第四题拿了 38/39,扣的那一分是范围检查,它们俩都改了原有的测试文件,SQL 本身没有问题。

这个范围问题得老实交代一个干扰因素。四道题各自的描述里提到了原有的测试文件,但总的任务说明里明确写了只能新建测试文件,两处有冲突,总说明优先。所以这两个结果能说明的是:这两次运行里,它们没处理好指令之间的冲突。它不能说明 Luna 一般都不守范围,也不能说明 medium 比 high 好。第三轮就把测试归属的说法统一成一种了。

我专门问过一句,有没有看实际跑出来的值,不能只看测试过没过。coordinator 把每条路线的实际输出都记了下来,也都看了。比如图表里真实值 -90 被画在 -10,真实值 45 画在 30,差值 35 保留着;一个 123456789.123456789 的小数从 TS 走到 Python 再回来,一位都没丢;数据库读回来的时候,老数据的 null 和新数据的 0 分得开。这些值都是评测代码自己执行出来的,不是 agent 在报告里写的。

中间出了两个跟模型无关的小事故。一次是机器负载太高,某个测试撞了 5 秒的默认超时,源码和断言不变,统一把超时调到 30 秒重跑就过了。另一次是评测这边统计输出的脚本漏数了一行被异步诊断信息切开的 ok,实际测试框架的汇总本来就是全过,修的是统计脚本。这两次都不算模型的问题。

第二轮下来,我对 Sol 写实现的信心多了不少。需求写清楚、边界划好的活,七条路线全都能做对。但它也有一个明显的局限,这些题是专门出的,需求非常明确。

第三轮:盲测复现历史 PR

第三轮是我最在意的一轮。第二轮过完我还是觉得测试不够硬,专门出的题需求太干净了,所以要求拿真实的历史 PR 再测一次。最后从工作上挑了三个 PR,合成两道题,把 repo 停在修复之前的那个提交上,只给 agent 当时的问题描述。修复之后加的那些测试放在 agent 看不到的地方,等它交完才拿进来跑。agent 也不准翻历史提交、不准看原来的 PR。每条路线有 32 分钟的有效工作时间。

第一题在前端。页面跳转的时候要保留状态,但换了一个账号登录以后,上一个账号的资料和问卷不能漏给下一个账号,同时匿名用户第一次登录前填的草稿、同一个账号刷新页面,这些都要保住。原来的修复带了 9 个测试。评测先校准了一下这 9 个测试有没有区分度:修复前的代码过 5 个;只打原修复的一半,反而只过 4 个,因为状态持久化以后账号串号的问题暴露得更明显了;完整的原修复 9 个全过。

第二题跨数据库和 backend。受限权限的数据库账号可以读 session,但一刷新最近活跃时间就报权限不足,原因出在一个审计触发器上。要修的是:给触发器一个有边界的权限,审计记录本身还是不能被普通账号读写。修完以后还要把 backend repo 里的迁移快照、快照的校验值、跑集成测试的 runner 一起同步好,runner 要真的建库、跑到新迁移、再跑 backend 原来的测试。所以这题要改的东西横跨两个 repo,改完还得真的跑通一遍。

路线前端 9 个原测试数据库迁移、行为、权限runner 中的真实调用路径
Astra/high9/9
Sol/medium9/9挂,backend 查询报权限不足
Sol/high9/9
Sol/xhigh9/9
Luna/medium9/9迁移自检没过,后面没跑到迁移没过
Luna/high9/9挂,兼容视图更新返回空
Luna/xhigh9/9挂,兼容视图更新返回空

前端那题七条路线全过。数据库那题,SQL 本身修对的有六条,但在 agent 自己的集成 runner 里把 backend 真实调用路径也跑通的只有三条:Astra/high、Sol/high、Sol/xhigh。

最值得说的是没做完的那几份是怎么交上来的。

Sol/medium 自己写了一个新迁移的测试,测试里直接调了一个 SQL 函数,跑下来是绿的。但 backend 原本的代码不是这么调的,它走 repository 那一层去查 session。用原来的 backend 测试在它自己建的库里一跑,直接报权限不足,它漏了那条路径需要的几个授权。

Luna/xhigh 的测试也是绿的,但它是直接去更新底表,还专门加了一条只对某一个写死的测试钱包生效的策略。backend 真实的代码走的是一个兼容视图,根本不直接碰底表。所以它的绿灯证明的是它自己搭的那条路是通的,证明不了问题修好了。拿 backend 原来的测试一跑,兼容视图的更新直接返回空。

自测走捷径全绿,真实调用方走兼容视图被权限拦住自测走捷径全绿,真实调用方走兼容视图被权限拦住

另外两份 Luna 反而挺老实的。Luna/high 自己在报告里说交的是部分完成,它的 runner 搭出来的兼容视图不完整,更新返回空。Luna/medium 的迁移在自检那一步把 PostgreSQL 自己保留的一条成员关系也当成违规拒掉了,整个迁移中止,它在报告里也写了没做完,只是对剩下那条关系的描述有点偏差。说实话这种直接告诉我没做完的,比一份看着全绿但其实没修好的,处理起来省心太多了。

这一轮评分这边也出了问题,要交代一下。评分用的 runner 适配层是按原来那个 PR 的写法写的,假设了一个固定的环境变量名,也假设 backend 测试在一个阶段里一起跑完。Astra/high 和 Sol/xhigh 很合理地给受限账号单独开了一个连接,结果被适配层当成了 owner 连接,判成了失败。Luna/high 把 backend 测试拆成两个阶段跑,这个写法本身也没问题,适配层同样不认。

这几处的锅在评分器,不该算到实现的写法上。所以后面补了一个兼容的适配层:先确认 agent 提供的那个连接真的是一个没有特权的受限账号,再把它接进原来的测试夹具里;阶段拆开了就把几个阶段的结果累加起来,三个原测试都要过才算过。agent 的 SQL、源码、原测试的断言一个字都没改,七条路线统一用这个适配层,原修复照样通过;另一个对照组快照已经修好,但用的还是老版本、没有新版本测试阶段的 runner,它虽然退出码是 0,照样判失败。原来那份误判的结果也留着。这是交完之后补的兼容处理,不是事先冻结的,所以这里单独说一下。

还有几处跟代码对错无关的小问题。Sol/medium 和 Luna/medium 把报告写在了 repo 根目录,没写到指定的位置;Luna/xhigh 更离谱一点,把报告写到了自己 worktree 外面的主 checkout 里。这些是放文件的位置不对,没有破坏源码,单独记了一笔,没算进上面的表。

两份 xhigh 的提交在前端那题都多做了一件事:把用户资料的本地存储改成了按账号隔离的格式,老数据在下次登录时丢掉。Sol/xhigh 在报告里说明了这一点。9 个原测试照样全过,所以它不算错,但这是一个原修复里没有的产品决定,需要有人去 review 它该不该这么做。这次观察到的两份 xhigh 提交都多做了这一步,样本就两个,不能说 effort 开高就一定会这样,但多做的那部分还是得有人去看。

最后说一下实际看了什么,这部分也是 coordinator 做的。每份报告、前端完整的 diff、数据库的迁移、backend runner 相关的 diff、所有失败的输出和实际记录下来的值,它都读了;runner 没跑通的那几份,它还去看了自测的源码,确认到底走偏在哪条路径上。但它没有逐行审每一份自测代码和每个文件。

最后怎么定的

三轮下来,我把 codex 的分工改成下面这样:

模型 / effort
coordinator,理解需求、拆任务、最后验收Astra/high
权限、鉴权、架构、原因不明的 debug 这类后果重的判断Astra/high
边界清楚的实现、跨文件集成、盯一个具体风险的 review、有边界的调研Sol/high
有边界的代码探索Sol/medium
源码清点,比如列出所有调用点,要带文件和行号Luna/high
精确的机械改动,有客观检查Luna/medium
材料全给好的纯提取、格式化Luna/low

纯机械的活如果现成的脚本或工具就能做,直接用工具,不用派 agent。

xhigh 和 max 不当默认,也不当「没做好就往上加一档」的自动升级,只留给一个说得出名字的难点。三轮里 xhigh 没有稳定地比 high 做得更好,还额外带来了需要 review 的决定。

表之外还有几条派活的规则,我觉得比表还要紧一点:

  1. 每个 subagent 的任务要写全:要什么结果、看哪些资料、能改哪些文件、交什么证据、什么情况下停下来回来。给不带历史的 agent 派活,省掉约束去省 token 是亏的。
  2. 混用模型要用不带历史的 fork。我这次用的 codex 原生 subagent 工具里,带完整历史 fork 出来的 subagent 会继承父 agent 的模型和 effort,这种 fork 也不接受单独指定模型。要显式换成 Sol 或 Luna,就不带历史,或者只带有限的几轮。这是当前这个工具的行为,别的接口不一定一样。另外每个 role 的配置文件里也可以单独写死模型和 effort,只改全局默认值是不够的;已经在跑的 agent 也不会因为你改了配置就换模型。
  3. 别让 Astra 把便宜模型干过的活再干一遍。派出去的活如果 coordinator 回头又从头查一遍、又从头写一遍,那等于付了两次钱。评价路由省不省,要把 review、返工和协调的成本都算进去,只看单价没意义。
  4. 搜不到不等于不存在。「repo 里没有 X」这种结论,要附上它搜了哪些范围。第一轮 Luna/high 说没有的那个测试文件,其实就在那里。
  5. 验收要走真实的调用方路径。模型自己写的测试全绿,只能说明它自己搭的路是通的。第三轮两份没修好的提交,自测都是绿的。

不同用法怎么选

这是我自己的用法测出来的,换一种用法答案可能不一样:

  1. 一个 session 自己干到底,很少派 subagent:主 agent 用 Astra/high 就行,这篇大部分内容跟你关系不大。
  2. 大量派 subagent、用量烧得快的(跟我一样):可以参考上面的表。先把翻代码、清点、机械改动这类活挪到 Sol/medium 和 Luna,写实现挪到 Sol/high,coordinator 和验收留在 Astra。
  3. 涉及权限、钱、数据库迁移的:判断留给 Astra。Sol/high 可以去实现一个已经定好的方案,但方案本身和最后的验收不要一起外包出去。
  4. 想省心直接全开 xhigh 的:不太建议。这几轮里它没有更稳地做对,这次两份 xhigh 提交还都多做了没要求的改动,你还是得去看。

限制

最后把限制列一下:

  1. 每条路线每轮只跑一次,三轮加起来的题数也不是从所有工程任务里随机抽的样本。不能拿来估计失败率,也分不清 effort 的影响和随机波动。
  2. 没有后端模型的独立凭证,也没有单次计费数据,所以没有测出来的提速、省 token 或省钱的数字。换便宜模型是为了省用量,这是预期,实际效果要用一段时间再看。
  3. 第一轮那个 EOF 取消用例是看完代码补的;第三轮的 runner 适配层是交完之后补的兼容处理;第二轮的范围问题有指令冲突的干扰。上面都单独标出来了。
  4. 题目的需求都写得比较明确。模糊需求下的排查、架构设计、几个小时的长任务、线上安全这些,这次都没测。
  5. 三轮一共开了 8、27、27 个 worktree,全部删掉了,各个 repo 的主 checkout 和测之前一样。实验里写的业务代码一行都没有提交或部署,最后落地的改动是 codex 的分工配置。

后面如果有新模型出来,或者我想把某类活再往下挪一档,这套流程可以直接拿来再跑一遍,只是每次都得再确认一下,评分器测的是不是真实的那条路径。

和 AI 讨论这篇文章
ChatGPTClaude
Claude Code 折腾记第 18 篇 · 共 18 篇
← 上一篇下一篇 →

订阅更新

新文章发布时发到你的邮箱,不发别的。


© Xingfan Xia 2024 - 2026 · CC BY-NC 4.0