前面几篇把循环拆得差不多了:它怎么转、什么时候该停、工具报错之后怎么办。剩下一块拼图是:它跑起来之后,你怎么知道它到底行不行。
这块难,是因为智能体和你以前测的过程序不一样。普通函数有标准答案,输出对不上就是错。智能体没有唯一正确的走法——同一个任务跑两次,可能调了不同的工具、换了顺序、绕了条远路,但都对;也可能走了一条看着很顺的路,最后拿到一个错的终态。
所以别急着问「它对不对」,先把它拆成三个能分别回答的小问题:
- 任务真的完成了吗? 看的是环境的终态,不是它最后那句话。
- 过程合规吗? 有没有做不该做的事、跳不该跳的步骤。
- 如果失败,坏在哪一步? 多步任务里,出错的那一步常常不是报错的那一步。
三个词后面会反复用,先说清。一次运行(trial) 是把同一条任务完整跑一遍;轨迹(trajectory,也叫 transcript) 是这次运行的完整记录,每一步的模型输出、工具调用、参数、工具返回都留在里面;环境终态(outcome) 是运行结束时环境里真实变成了什么样,比如数据库里到底有没有那条记录。任务(task) 是一条具体的测试用例,判据(grader) 是给它打分的逻辑,把这两样和一个能自动跑完的流程凑齐,就是一个 评测集(eval suite)。
第一判据是环境终态
最容易踩的坑是拿它最后那句话当结论。订票智能体在结尾说「您的航班已预订成功」,这句话本身不证明任何事情——真正的判据是那条预订记录在不在数据库里1。
这正是上一篇那个道理的另一种出现方式:验证器要验得住。 一次成功的写库只能证明格式合法、字段齐全,证明不了写进去的航班号就是用户要的那个。所以终态检查要尽量贴着事实走,查那条记录本身,而不是查它的日志。
光看终态会漏掉两类失败
终态检查是必要的,但会放过两种东西:
- 运气型成功:它跳过了核对步骤,但凑巧落到了正确答案上。这次过关,下次输入一变就露馅。
- 危险的过程:结果看起来完全正确,但中间它没等用户确认就把全部预订删了。这类动作不改变最终可见的结果,除非你专门去查「不该变的地方变了没有」。
所以还要看轨迹。但看的方式很关键:不要拿它去逐字比对一条参考路径。
Anthropic 在总结大量团队实践后给了一条反直觉的建议:逐字检查工具调用顺序的做法太脆,会惩罚那些设计者没预料到、但同样有效的解法;应该判断它 产出了什么、有没有违规,而不是它 走的哪条路1。
具体做法是把轨迹上的检查分成两档,按成本从低到高排。
第一档,确定性的断言,能直接写成代码,便宜、快、不会漂移:
- 必须发生的事:要办成这个任务,至少得调用过
book_flight。 - 绝不能发生的事:没拿到用户确认,就不许出现
delete_booking。这类禁忌序列一旦触发就直接判失败,不管终态多漂亮。 - 关键参数:搜索航班的出发地填的是不是
SFO,日期是不是用户说的那天。 - 上限:步数、token、耗时、花销有没有超过事先定好的线。
- 终止是否正确:是提前放弃了,还是成功后还在继续跑。
至于顺序,只有在 顺序本身就是规则 的时候才锁死:先查退款政策、再执行退款,这个前后关系是业务要求;如果两个查询先做哪个都行,就别管它。LangChain 的 agentevals 把这件事做成了几个严格度档位:strict 要求顺序和集合都对,unordered 只要求集合对、顺序不限,subset 禁止它多调参考之外的无关工具,superset 只要求关键工具都出现过、允许它多做几步3。
确定性的查完,剩下的才交给模型评委
拿模型给模型打分(LLM-as-judge)只在一种情况下值得用:这个维度必须读懂意思才能判。用法上有三条纪律:让它给出结构化的结论——哪条标准通过、哪条没通过、证据是哪句话;允许它回「信息不足,判不了」,否则它会硬编一个理由;启用之前先用一批人工标好的样例跟它对一遍,对不齐就先改评分标准1。没校准过的评委比没有评委更危险,因为它会给你一串看着像数字的东西。
从一次运行到一个数字
同一个任务跑多次,结果本来就会变。所以值得记的不是「这次过没过」,而是这条任务的成功率。两个指标长得有点像,方向却相反:
- pass@k:跑 次,至少成功一次的概率。往上走,试得越多越容易中。适合「多出几个方案,有一个能用就行」。
- pass^k:跑 次,全部成功的概率。往下掉,k 越大越难。适合用户每次都指望它对的场景。
k=1 时两个数相等,都等于单次成功率;k 一大,它们讲的完全是相反的故事1。
举个能自己算的例子:单次成功率 75%,跑三次全对的机会是 。也就是说,一个「平均四次里对三次」的智能体,连着三次都靠得住的概率不到一半。
这不是纸上推导。τ-bench 用模拟用户和真实数据库状态测了一批函数调用智能体:零售域里最好的组合单次成功率约 61%,但同一个任务跑八次都成功的比例掉到不足 25%2。平均成功率看着还行,一致性很差。
这和上一篇里「每步 99%、十步只剩 90%」是同一笔账,只是乘的地方换了:那次是步数把折扣叠起来,这次是多次运行把折扣叠起来。用户不会「平均」体验你的智能体,他遇到的是其中一次。
一小套评测集怎么搭
不需要几百条,十几条选得准的用例,比几百条随手写的更有用。搭法就这几步:
- 从真实失败里取案例,不要凭想象写。 每次线上出问题,把当时的输入存下来,变成一条新用例。
- 每条用例记住这几样:输入、期望的环境终态、必须调用的工具、禁止的动作、步数与花销上限、这次的失败属于哪一类。
- 每次运行从干净环境开始。 上一次留下的文件、缓存、登录状态会让结果失真,甚至凭空抬高成功率1。
- 分成两个集合:能力集 装它现在还做不好的任务,通过率本来就该低,用来看改进有没有用;回归集 装它已经能做好的任务,通过率应接近 100%,用来发现「改了这里坏了那里」。能力集里爬上去的任务,可以毕业进回归集1。
- 挂进 CI,每次提交自动跑一遍,通过率掉了就拦下来。
- 给数据集打版本。 模型、提示词、工具 schema、评委提示里任何一个变了,都要用同一个数据集版本在改动前后各跑一次;不然你比较的不是两次改动,而是两套测量工具。
两个会骗你的地方
通过率 0% 通常说明题目坏了。 一条任务跑一百次全失败,更可能是题目描述有歧义,或者判据暗中假定了一个没写出来的文件路径,而不是模型不行1。所以每条任务最好先有一个已知能过的参考解,证明它可解、判据也没配错。
只看分数不看轨迹,你迟早会被判据骗。 任务失败时要读那条轨迹,才能分清「它真做错了」和「判据把有效解法判成了失败」1。另外要记住,被评测的不只是模型,还有跑智能体的那套代码和环境;两边环境不一样时,61% 和 75% 之间可能什么信息都没有1。
回到最开始那个问题:判断一个智能体行不行,第一眼看环境终态,第二眼在轨迹上查「必须发生的」和「绝不能发生的」,第三眼用多次运行把它压成 pass^k 这类衡量稳定性的数字,最后拿它守住一套会跟着代码一起演进的评测集。这四步做完,你对每次改动的判断就不再是感觉,而是一个能比较的数。