可验证性就是能力边界
生码只占研发链路 20% 到 30%,口径是需求到上线里写代码这段的耗时占比,继续优化它的边际收益已经很低。Agent 能走多远,取决于你能给它多少真实反馈——构建、测试、部署、日志、监控,缺一样它就只能靠训练分布猜。
上下文预算的切法定下来之后,真正顶到面前的是另一问:这一次做对了没有,由谁来判定。切预算是在有限的上下文容量里决定带什么进来,定判定是在同样的容量里决定信什么——两个动作挤的是同一份容量,决定的却是两件不同的事。
讨论 Agent 能接管研发流程哪一段的时候,我的答案跟模型无关:它能走多远,取决于你能给它多少真实反馈。构建能不能跑、测试有没有过、部署在什么状态、日志里写了什么、监控指标有没有变化——这些东西模型权重里生成不出来。拿不到反馈的地方,它只能靠训练分布猜,猜对了是运气,猜错了也不知道。这是一条能算出来的边界:可验证性就是能力边界,下面每一节的收益都不在模型那一侧。
生码不再是瓶颈之后#
先看一个算术问题。假设生码在整个研发链路里占 20% 到 30%,口径是「从需求到上线」里写代码这一段的耗时占比。那么即便把生码这一节加速到趋近于零,端到端的总加速上限也只有 1/(1−p):p 取 0.2 时是 1.25 倍,p 取 0.3 时是 1.43 倍。
继续在生码上加码,边际收益已经很低了。剩下七成时间都花在等环境、等联调、等发布窗口、等人工确认上。
反过来,把等待的那一头做成接口,收益是立刻能看见的。腾讯技术工程那条 SRE 错误码排查线上,一次错误码排查从原来的 3 到 8 小时压到分钟级,口径是告警从产生到给出定位结论的处理时长中位数。
治理动的是排查过程:那几步原本要人去页面上翻的动作变成了可调用、可返回的接口。治理后这条链路缩短到「分钟级排查加 0.5 到 2 天修复」,日均数百条去重告警里,high 置信度结论占约 90%,失败率约 1%(含重试后仍失败)。
在 1 小时对 3 周的比例里,模型能力根本不是变量。一次 C 端动线改造的对照更直观:本地改码加验证大约 1 小时,之后跨内部平台发布、联调,再撞上一次 618 封网,上线总共用了 3 周。
失败的时候先停下来读那次失败,读完再决定要不要重跑。更隐蔽的一种浪费算不进生码占比那个算式,因为它压根没产生代码:一个任务失败了,不诊断就重跑,跑一整个周末,最后把使用额度耗尽,什么都没留下。
哪些投入不会随换代归零#
精细的提示词编排、为绕开模型弱点写的兼容逻辑、手工维护的长篇规则,这三类投入会在下一代模型发布时归零。它们解决的只是「当前模型不够强」这个暂时问题,模型一换代就把它内化掉。我只有一个判据:看这项投入在换代后是获益还是作废,按这条判据,很多当下看起来很硬的工作都落在作废那一侧。
这些是模型能力的乘数:同一个环境里,模型越强,产出越多。真正会复利的在另一侧——内部系统有没有可调用的接口、程序能不能读部署和日志、测试能不能真实反映线上行为、监控指标能不能定位到具体代码路径。
| 投入 | 模型换代后 |
|---|---|
| 提示词与精细编排 | 被模型内化,归零 |
| 为绕开弱点写的兼容 | 没有意义 |
| 内部系统的可调用接口 | 继续复利 |
| 测试与判据 | 继续复利 |
| 部署、日志、监控的可读性 | 继续复利 |
判据还有一条资产属性:它会随模型换代升值。套不进去的投入,通常已经站在作废那一侧;用法很简单:把「如果这个模型突然强三倍」套上去,答案立刻就出来了。同一套界面校验流程我动过两次,动的是跑视觉比对用的模型档位。
写死在某个模型上的提示词没有这个性质,同样的题集和判据,明年会给出更好的结果。两次调档都要求先把角色切干净才敢动档。第一次往下调一档,单轮 token 成本以降档前一档的单价为分母降约 64%,省下来的是钱。第二次往上调一档跑同一批用例,整轮耗时以升档前为分母降约 64%,省下来的是时间。
让便宜的信号最先回来#
研发环境里的每个环节都要做成可调用的接口,页面只是给人看的那一层。每做一个接口,我固定配四样东西:入参出参的结构、不能无限等的超时、最小够用的权限、以及调用留痕的审计。
四样里超时最要紧,缺任何一样,这个接口在生产上都用不住。Agent 不会自己放弃,只会一直等下去,把整条链路的时间预算耗光,最后以一个看不出原因的结果收场。
覆盖的顺序我按反馈速度排:构建、单测、类型检查这些秒级返回的先做,集成和契约测试紧随其后,部署状态与日志查询排在后段。监控还要更靠后,因为它返回的是统计值,Agent 直接读容易把一次抖动当成一次回归。完整拆开是三层:
| 层 | 典型环节 | 挡住什么 |
|---|---|---|
| 秒级 | 编译、格式、类型、单测 | 语法与结构错误 |
| 分钟级 | 集成、契约、浏览器、真实接口数据 | 跨模块与真实环境行为 |
| 人工 | 业务价值、体验、伦理 | 规则判不了的部分 |
三层不能互相替代。只有秒级检查,Agent 会写出能通过类型检查、却完全走错业务路径的代码;只有人工验收,人的时间会变成唯一的吞吐瓶颈。
便宜的信号先回来,省的不只是时间。同一个错误在秒级被发现,修复成本是几秒钟;拖到人工验收才发现,修复成本是一次完整的上下文重建——把链路重新组装一遍的时间,往往比改代码本身长一个数量级。
非功能验证要单独占一格。功能测试回答「能不能用」,非功能验证回答「敢不敢发」。产物校验、性能退化、安全边界这三项,在一次 AI 生成的大改里最容易丢掉——代码看着对,构建产物却是旧的,或者压根没生成。
安全这一块要左移到生成阶段,提前到评审之前做完。跳过预览直接生成代码、把未验证的改动直接推到主干,这两件事在 AI 提效之后发生频率高了一个量级,因为生成的速度让人失去了逐条看的耐心。浏览器这一块有个额外约束:UI 改动的功能验证必须真的打开浏览器看,光靠单测判不了「页面能不能用」。但截图和 DOM 快照非常占上下文,我的处理方式是按需取、用完即弃,不做成默认采集。
跑通了不等于真的能用#
这一层有个路线上的分歧值得说清楚。规范驱动这条路的问题是规格会过时,写得越细,跟实际系统的偏差越大;做法是先把意图写成详细规格,再让 Agent 照着做。
规范驱动和环境验证驱动这两条路并行,环境验证驱动这条该优先投入,因为它会复利,规格那条不会。环境与验证驱动只求一件事:让环境能给出真实的判断,不追求把意图写全。规格写三行、环境能验证,就够了;规格写三十页、环境不说话,照样是猜。
小米零售那条 AI 问数线上有个很能说明问题的对照。早期把指标口径、维度、过滤条件写成详细规范交给模型,「达成率」这类问句的指标选择准确率停在 65% 上下,口径是同一批问句里选对指标的比例。
同一套知识,写三十页规范不如让它跑一次。规范的正确用法是定义接口,接口的活儿交给能执行的东西。后来把同样的信息做成可执行的取数接口,让模型自己构造查询、自己执行、自己看返回结果对不对,准确率提到接近 98%。
内部循环是 Agent 自己那一圈:写码、跑测试、看失败、改。内环的目标是速度,不是可信度。
顺序上有个反直觉的做法值得试:先写测试或先写类型定义,再让 Agent 去实现。先立判据,后要实现,它就没有空间用「差不多对」的东西交差。我自己更常用另一种写法——让它先给出方案,我确认之后再落码,这一步省下的大头是返工。
把产出直接铺在它面前,省掉人转述这一层,这个习惯值得养成。直接把数据集、文档、配置文件放在它能访问的位置,它会自己检索、自己比对、自己发现矛盾。人做中间层转述,信息在传递中已经损过一遍。
能力本身也要分层。我按使用频率和风险分四处放:长期稳定的行为约束放进系统提示,可复用的操作流程做成按需加载的能力,单次动作交给工具,而展示形态单独抽出来。分层的意义是把「每次都带着的东西」压到最小,把「偶尔才用的东西」放到检索得到的位置。
测试由谁写,也值得定清楚。让 Agent 自己写测试,速度快,但它会照着自己的实现写,等于用同一个思路验证同一个思路。我的做法是让它写,人再审一遍断言——重点看断言有没有真的约束住行为,覆盖率那一项排在后面。
后台执行和并行是内环里最实用的两个开关。长任务丢到后台跑,主线程继续干别的;互不依赖的探查任务并行发起。但要看住输出量——几千行的构建日志原样回灌,会把上下文吃干。我的做法是让命令只输出结论和关键行,不回显全文。
内环里有个常被忽略的做法:失败立刻修,不要攒着。攒三个失败一起改,等于让它在错误的基础上继续推理三层。
外环的目标是可信度,它的前提是不信任内环的结论。做法很机械:在一个干净容器里重新克隆、重新构建、重新跑测试,由独立的检查者评审,通过后入库。外环不复用内环的任何中间状态,因为它要验的就是「换一个环境还能不能成」。
两个环之间有一条硬规则:只传事实,不传声明。内环不能把「我改好了」传过来,只能把「改了哪些文件、跑过哪些命令、结果是什么」传过去。声明可以错,事实不会错。落到实践里就是一句话:把复现路径写清楚,让每个接手的人照着跑一遍,不用别人转述「我验证过了」。
失败必须有名字,这是另一条硬要求。合并失败、推送失败、冲突失败是三种不同的失败,需要的处理完全不同。把它们都记成「失败」,重启流程时会从错误的分支开始。
交付链路上新出现两个旧流程里没有的角色,它们都不需要人在场。一个跑在后台执行:由事件触发,产出直接进代码库或工单;另一个嵌在持续集成里:读失败日志、定位到具体改动、给出候选修复。这两个角色撞上的失败,程序必须能读出来,人不用去翻日志。
跑得起来却没人用,是发布分层要拦住的那种失败。我给发布划了四段:可预览的内部版本、内部真实用一段时间、放给愿意尝鲜的外部用户、全面放开。让一个改动跑起来,和让它真正进入使用,这两件事的失败方式完全不同:跑不起来一眼看得见。
不分四段发布,平均成功率会盖住降级率这个数,直到全面放开那天才一起爆出来。分层的价值还有一个更硬的读数:降级率。降级只是让整体成功率好看一点,不算失败。阿里全球化商品中心那条智能答疑 Agent,把感知层当成独立指标来测——用户的问题超出所有 Skill 处理范围时,Agent 有没有触发知识库检索兜底。
| 阶段 | 谁在用 | 这一阶段主要在找什么 |
|---|---|---|
| 预览 | 开发者自己 | 功能是否跑通 |
| 内部使用 | 团队内部 | 真实工作流里会不会卡住 |
| 小范围外部 | 愿意尝鲜的用户 | 边界场景与预期落差 |
| 全面放开 | 全部用户 | 稳定性与成本 |
错误码本身就是常被漏掉的一种事实来源。一次调用失败,返回体里往往已经写明了是排队、限频还是参数错,只是没有人把它提取成结构。把这些分类做成可读的字段再交给 Agent,它就不用靠猜去重试。系统里已经有的信息,不需要模型去推断。
该由框架拿走一整类错误#
把规则下沉到框架层的时候,判据只有一条:这是事实还是代理指标? 禁止读取某个路径是事实,可以硬拦截;「编辑之前必须读过规范」只是代理指标,只能做提醒,不能做拦截。读过不等于遵守,这一点在模型身上和人身上一样。
误报还会反向逼出更差的实现。我见过为了满足「读过规范」这个提醒,Agent 去读一遍文件就立刻关掉——形式合规,实质没变。把代理指标当事实用,拿到的就是这个。
顺着这条判据往下走,可以消掉一整类错误:有些错误不该靠 Agent 记住,该让框架直接拿走。凡是在框架层能被确定性消掉的判断,都不该以规则的形式挂在 Agent 的上下文里。
声明式 Mock 是最能说明问题的一个例子。传统写法要求开发者在测试里显式清理状态,漏掉清理是我观察到的一类高频错误。同样高频的还有把测试数据伪造得过于规整、把真实边界掩盖掉。漏清理这一类问题,在改成声明式声明加自动还原之后从结构上消失了,Agent 不需要记任何规则;测试数据伪造得过整是另一类,只能靠断言自己守住。
排超时这类问题,框架层也能直接拿走。以前排查一次调用超时,要在日志里人工判断是排队、限频还是根本没回包;现在直接把分类结果、对应的处置动作和代码路径一起返回给 Agent,一次就定位了。声明式 Mock 和超时分类这两个例子指向同一件事:错误的结构信息已经存在于系统里,只是散在各处没人提取。框架层要做的不是约束 Agent,是把这些信息交给它。
题集和判据才是验收核心#
这里有一组对照,口径是同一个人负责的 7 个服务、统计窗口 3 个月:1800+ 次提交,覆盖生产代码、测试代码和前端三类改动。产出规模是够大的,同期事故并没有同比例下降。
原因是一个乘法关系:单次错误率 × 产出规模 = 事故数量。单次错误率降到一半,产出规模涨到十倍,事故是原来的五倍。低错误率乘以高产出,不等于安全。
人工审查抓不到两种失效。一种是编码损坏:中文文档里成片出现乱码替换字符,逐行看代码完全看不出来。另一种是空覆盖:覆盖率数字在涨,但有人把断言写松了,实际什么都没验证。这两种都得靠机器查,人眼扫一遍是扫不出来的。
产出规模本身也会制造新的失效。我见过一次:让模型写三份分析文档,它干了两周多,交回一份只有长度没有内容的报告,篇幅是够了,一半内容在重复同一件事。换成把任务拆成几个可验证的单元、每个单元单独验收,一周就做完了。大产出带来的满足感会掩盖一件事——没有人定义过什么叫做完。
判据缺失才是质量侧更常见的失效:输出越来越长、往代码里塞无关符号和表情、评审里漏掉的严重问题。一批评审意见里,最要紧的几条始终没有人处理。这几样都出在判据上,缺的正是判据:没有人定义「什么叫做完了」,于是「跑通了」就成了终点。
规模上来之后,回答「测试到底能不能发现 bug」的不再是更多的测试,而是题集和判据。一个只堆平台工程量、不动题集判据的项目做了 107 天,改动量与前面那组 7 个服务的对照相当,整条链路只留下两次可复现的跑数。同期一个轻量做法只做了 16 组配置对比,每组跑一遍关键子集,直接改写了检索策略。
后来换成 16 道题各跑 3 遍,48 次完整跑合计约 19 小时机时,单次约 24 分钟(19×60÷48≈24)。184 个变异体里 12 个是等价变异体,改动不改变语义,永远杀不掉,不进分母;剩下 172 个里杀死 105 个,105÷172≈61%。
宏平均变异击杀率也是约 61%,口径是先算每题击杀率再取平均,跟 105÷172 这个合计比互为印证。两个数字用的是同一批跑数,只是聚合方式不同。
变异算子全部取自真实评审里见过的失效模式:比较符翻转、and 与 or 互换、删掉兜底的 or 0、把状态写入注释掉。判据也换了:只有测试真的失败,才算杀死一个变异体,跑通不算。这条判据把「看起来在测」和「真的在测」区分开了。
变异体也不是一次性定案。三级验证跑下来:模型初审先剔掉明显等价的那些,测试执行定生死,人工只复核两边结论不一致的,终裁以人工那层为准。等价变异最后落两条价值判断——日志差异不算可观测,可杀性以自然构造为准。
回到开头那个问题。Agent 能接管哪一段,看环境里有多少是真的、可读的、可调用的。缺一样,它就退回猜。
可验证性落在自己这一侧,好处是边界不在别人手里。每多做一个带 schema 和超时的接口,每多写一条能杀死变异体的判据,边界就往外推一点。按开头那条判据,这些都落在获益一侧,不会因为模型换代而作废。
判据齐了只是把边界画出来,能把手放出去多少还看另一件事:这次动作能不能撤销。改一行代码能回滚,发一条消息不能。
落点很具体:写失败的测试让它改到通过,把描述换成能直接跑的脚本,把证据铺在它面前让它自己检索验证,把复现路径写清楚让人照着跑一遍。让环境先有判断,再让模型动手。