这周最值得说的判断:Agent 代码的单测,重心不该放在模型输出上,而该放在模型输出之后那一段——解析、校验、状态流转,这些是确定性的,测得住。Anthropic 的 multi-agent 研究里观察到的翻车模式,Economist 那篇《AI agents lie, cheat and steal》,其实都在暗示同一件事:模型输出靠不住,但靠不住的旁边有一圈靠得住的代码,那才是单测的地盘。
先搞清楚为什么 mock 固定回答是死路
很多人写 agent 单测的第一反应:mock 一个固定模型回答,断言最终结果。比如 mock 返回「用户想退款」,然后断言 agent 调用了退款接口。测试绿了,人人开心。
问题在:这个测试从写出来那天就在倒计时。模型一升级、prompt 微调一句,真实输出分布变了——mock 没变,测试依然绿。于是你维护了一套「跑得开心但没有任何信号」的测试。比没有还糟,因为它给你虚假的安全感。
这周 Economist 那篇文章标题挺怂人听闻的,「agents lie, cheat and steal」。但内容算老话:模型输出是概率性的,今天给你合法 JSON,明天给你一段废话加个逗号。你把断言写在这种东西上,等于把地基打在流沙上。我劝团队里所有人都别干这事——mock 固定回答然后断言最终结果,一次都不行。
把模型调用抽成边界,边界内做真单测
具体做法其实简单:模型调用是边界,边界外全是不确定的;边界内——解析、校验、重试、状态机、工具调用的参数组装——全是确定性的。逻辑都在边界内,单测都往这里打。
「模型返回 JSON」这一步不要 mock 成固定字符串然后跳过,而是:真实模型的行为交给评测集,你在本地跑一个小评估集,看通过率。单测管的是「拿到任意合法输入,逻辑走对」;评测管的是「模型在这个 prompt 下给不给合法输入」。两层各管各的。
Anthropic 这周发的那篇 multi-agent 研究,我读下来最大的感受就是:它们观察到的系统性问题——消息传丢、格式没对上、状态没转移——几乎没有一件是「模型智商不够」,全是模型输出落地之后那层代码的责任。这恰恰是单测能兜住的地盘。多 agent 系统里那些「两个 agent 互相等对方」「状态停在半截」的 bug,写成单测就是「状态机在某个输入下没有转移」——几行代码就能复现,不需要任何模型参与。
但边界别抽得太薄
反面教材我也见过不少。有人把边界抽成「调模型 → 拿字符串」,解析留在边界外。表面看边界清晰,实际上 prompt 和解析的耦合被漏掉了。
最常见的翻车现场:prompt 里让模型返回 JSON,某天 prompt 改了个字段名,解析逻辑没跟上——单测全绿,因为 mock 的还是旧 JSON 形状,真实模型已经按新 prompt 返回了。到线上才发现全链路断裂。
解法是让边界厚一点:把 prompt 模板、schema 声明、解析器合成一个契约,当作一个整体测。或者干脆加一层「契约测试」:拿一段真实模型输出当 fixture,锁死「prompt 产出的形状」和「解析器期望的形状」必须一致。模型升级后跑一遍,形状变了立刻报警。这不是测模型,是测你和模型之间的约定。
这周其他素材,多数跟测无关
Muse Glimmer、Needle2 那些本地模型发布,跟单测没直接关系。danluu 那篇《最好的 coding agent 语言》讲的是 token 经济性,但里面有个观点我认同:agent 系统的稳定性,大头靠周边确定性代码撑着,模型只是其中一块。OpenAI 的 builder guide 也主要在讲模型选择,跟测试关系不大。Docker Sandboxes 倒是值得一句——agent 的真集成测试需要一个能反复回收的环境,沙箱解决的是「测试跑得安心」的问题,不是「测什么」的问题。别拿沙箱当单测的替代品,那是集成测试的另一层。
按我现在看,agent 单测的黄金法则就一条:凡是你能写死的,都写成死的。模型输出写不死,那就别写进单测。测试模型之外那一段,模型行为交给评测集。也许 6 个月后回头会被整体打脸,但现在我确实信这套。