做 Agent 做久了会遇到一个尴尬的事:一个任务跑成功了,你很难说清它是为什么成功的。
是因为模型足够强?是因为上下文恰好没爆?是因为预算没卡住所以一路堆 token 堆过去的?还是因为记忆恰好命中了?如果分不清,下一次失败你也不知道该改哪里。
评测的意义不在于给系统打一个分数,而在于让结果可解释—成功是因为什么,失败是因为什么,改了某一处会怎样。这篇文章记录我在给 SAGE 设计评测时遇到的核心难处,以及四层边界各自该怎么量。
四层各评什么
SAGE 的运行底座可以分成四层,每一层都容易混淆“评了什么”和“该评什么”:
| 层 | 容易评的 | 该评的 |
|---|---|---|
| Context | 上下文用了多少 token | 预算控制是否因果—超预算的调用有没有真的被挡住 |
| Memory | 状态机是否正确 | 记忆是否真的有用—能不能召回“上次怎么解决的” |
| RAG | 能不能检索到答案 | 检索不到时是否诚实 abstain,答案是否被证据支撑 |
| Harness | 能不能跑起来、能不能确定性跑 | 能不能解决问题 |
四层共同的坑是:把“控制层是否正确”当成了“能力层是否够强”。状态机正确不等于记忆有用,检索能命中不等于生成诚实,能跑起来不等于能解决问题。
Context:预算是不是因果的
Context 层最该评的是预算的因果性。一个任务在预算内完成,和“预算控制真的生效了”是两回事。我在评估时发现四个值得量的边界:
| 边界 | 现象 |
|---|---|
| 多套预算并存 | 字符计的上下文管理、字符计的投影截断、token 计的累计预算三套系统,单位、强制力(报告/投影/事后检查)、作用域(每次调用/累计全程)都不同,无单一权威 |
| 事后预算 | 运行预算在模型响应返回后才累加用量再检查,超预算的那次调用已经发生、已经计费;是防失控的护栏,不是真正的预算控制 |
| 用量缺失归零 | 供应商不返回用量元数据时按零计,门禁静默失效—不报错、不停机,只是悄悄变成空操作 |
| 压缩只在回合开始 | 压缩阈值只在用户回合开始触发,单回合内连续多次工具调用可中途失控;相同任务仅因回合结构不同,成功率就剧烈变化 |
这几个边界都不是 bug,而是设计权衡。但如果不评,它们就会把能力分数污染掉:你可能测出“模型在 A 任务上成功率 80%”,却不知道其中一部分是靠超预算堆出来的。
Memory:状态机正确不等于记忆有用
Memory 层是我觉得反差最大的一层。
SAGE 的记忆生命周期设计得很扎实:写入有 CAS 乐观锁、提案有审批、运行之间有隔离、事实变更有冲突检测。作为状态机,它经得起确定性测试—状态转移、隔离、审批不变量都能锁住。这在 Agent 系统里其实少见,很多框架的记忆连基础的并发隔离都做不到。
但状态机正确,和记忆“有用”,是两回事。
我去翻实际产出的情景记忆,发现里面主要是元数据:这次运行用了哪些工具、终端状态是什么、证据有几条。它记录了“发生了什么”,没有记录“学到了什么”—哪个方案试过失败了、哪个坑踩过、哪个 workaround 管用。
于是出现一个错位:生命周期测试全部通过,但你问记忆“我们以前遇到过这个问题吗”,它召回不出来有用的经验。
所以 Memory 该评的不是生命周期,而是经验内容质量。我的做法是设计一个探针任务:先跑一个“方案 A 失败、找到 workaround”的任务,过一段时间再让 Agent 遇到同类问题,看它能不能从记忆里召回那个 workaround。这测的是记忆的智能,不是状态机的正确。更进一步,还可以做受控三臂对比:
| 臂 | 检索方法 |
|---|---|
| no-memory | 不启用记忆 |
| current | 词法 + 近因 |
| hybrid | 词法 + 近因 + embedding |
三臂固定模型、工具、token 预算、延迟和数据切分,唯一变量是检索方法—这样记忆带来的增益才说得清。
RAG:评错了层
RAG 层的问题更直接:它评错了层。
现有的知识评测只评检索—给定问题,能不能检索到正确片段。而且有一组“无答案”问题,准确率是零。
但“无答案准确率零”是个语义不明的数字。它可能意味着系统总是硬编一个答案(该 abstain 的时候不 abstain),也可能意味着系统 abstain 了但评测把它判错了。没有弃答指标,你分不清。而用户真正关心的不是“能不能检索到”,是“检索不到的时候,系统会不会老老实实说不知道,而不是编一个”。这是诚实性,不是召回率。
所以 RAG 该评三层:
- 检索协议本身:能不能召回正确片段,用 nDCG、Recall 这类标准检索指标,并补人工标注的相关性。
- 弃答诚实性:无答案时是 abstain 还是幻觉。这一层要先消歧—把“无答案准确率零”拆成“总是幻觉”还是“abstain 被判错”。
- 生成质量:给出的答案有没有被检索到的证据真正支撑,引用是不是站得住。
把检索和生成拆开评,才能定位问题出在哪一层。否则一个端到端的“准确率”会同时掩盖检索召回不足、弃答失败和生成幻觉三种完全不同的问题。
Harness:有“能跑”的门禁,缺“能解决”的门禁
Harness 层其实已经有了一个扎实的发布门禁:它会检查新旧运行时是否一致、真实供应商能不能跑通、浏览器、时间线、沙箱、回滚。这是运行时层面的门禁,做得很好—它保证系统不会退化成跑不起来。
另外还有一套确定性场景 runner,用脚本化的客户端跑十几个固定路径—但它是 informational 的,明确不作门禁。它证明的是“系统能不能确定性跑”,不是“系统能不能解决问题”。
缺的恰恰是第三种:能力门禁。用真实模型跑真实任务,看它到底解不解决得了问题。这部分要借助外部基准:状态管理用 STATE-Bench,工具调用用 BFCL,轨迹质量看工具选择准不准、有没有冗余调用、工具报错后能不能恢复。安全上还要做红队—经工具结果的提示注入、权限边界、沙箱逃逸,用 garak 这类工具压一遍。
怎么让结果可解释:契约层和智能层交叉标签
讲完四层各自该评什么,核心方法其实就一句话:把契约层和智能层分开评,再用交叉标签做归因。
| 层 | 契约层(确定性,CI 可跑,不依赖模型) | 智能层(真实模型 / LLM 判官) |
|---|---|---|
| Context | 超预算调用比例、用量缺失放过比例、中途失控回合频率 | 任务成功率 |
| Memory | 状态机、隔离、冲突检测 | 经验召回率 |
| RAG | 检索 nDCG、Recall | 答案正确性、引用蕴含 |
| Harness | 运行时一致性、沙箱、回滚 | 真实任务解决率 |
契约层测的不是模型能力,是控制层是否忠实;智能层测的才是能力。两层分开还不够,关键是用交叉标签把它们连起来。每跑一次真实任务,都打上这次运行触发了哪些契约层缺陷。于是任务成功率可以被切成两半:
任务成功率 ├─ 未触发任何契约缺陷的成功率 ← 这是真正的能力 └─ 触发了预算/记忆/检索缺陷的成功率 ← 这是靠失控换来的前者是真正的能力,后者是靠失控换来的。这就是因果可解释:你不是得到一个“80% 成功率”的黑箱数字,而是知道其中多少是能力、多少是控制层失守。没有这一步,所有能力评测都会被控制缺陷污染成噪声。
顺序:先修破坏因果的部分,再测剩余的
最后一个判断是顺序问题:先建评测适配器,还是先修预算语义?
我的结论是先做最小的预算修复,再建评测。原因是:如果在“事后预算、用量缺失归零”这种破损语义上包一层评测 runner,测出来的数字会把模型能力和控制缺陷缠在一起—正是要避免的不可解释。
但“全部修好再测”也不对,那是无测量地盲改。所以解法是:只修掉破坏因果性的两个廉价缺陷—用量缺失时不归零(要么 fail-closed 要么用 tokenizer 估算),把事后检查改成调用前的预算预估—其余边界原样保留,转成契约层里可观测的指标。修完因果性的部分,再建评测 runner(我选 Inspect AI 作为外层 runner,它包装 SAGE 现有运行时,不替换它),让它顺便把预算边界的事件记下来。
这样评测从一开始就不报告混淆数字,而那些没修的边界也没有被藏起来—它们变成了仪表盘上的读数,而不是黑箱里的噪声。
收尾
给 Agent 做评测,最大的诱惑是早点跑出一个分数。但一个混了能力和控制的分数,比没有分数更危险—它会让你自信地改错地方。
Context 评预算的因果性,Memory 评经验的有用性,RAG 评检索不到时的诚实,Harness 评解决问题的关键能力。用契约层让缺陷可见,用智能层测能力,用交叉标签把两者连起来归因。评估的目的从来不是打分,是让每一次成功和失败都说得清原因。
References / 相关链接
如果这篇文章对你有帮助,欢迎分享给更多人!
部分信息可能已经过时