1. 一条评估任务的解剖
先看实物。下面是一条完整的评估任务,为便于阅读做了删节。
{
"id": "[mobile_data_issue]airplane_mode_on|user_abroad_roaming_enabled_off",
// 提供给 Agent 的工单
"ticket": "用户手机无法上网,状态栏显示 'No Service'。客户 John Smith,
号码 555-123-2002,当前在法国。速度测试结果为 excellent 才算解决。
不更换套餐,必要时愿意充值 2.0 GB 流量。",
// 提供给用户模拟器的行为规范
"user_scenario": { "instructions": {
"known_info": "You are John Smith with phone number 555-123-2002.
You are currently abroad in France.",
"unknown_info": null,
"task_instructions":
"…express mild frustration after the first unsuccessful attempt.
You will consider the issue resolved only when speed test returns
excellent internet speed and nothing else. …
Whenever the agent asks you about your device, always ground your
responses on the results of tool calls. …
Never make up the results of tool calls."
}},
// 运行前将两侧状态重置到同一起点
"initial_state": { "initialization_actions": [
{ "env_type": "user", "func_name": "turn_airplane_mode_on" },
{ "env_type": "user", "func_name": "turn_roaming_off" },
{ "env_type": "assistant", "func_name": "enable_roaming",
"arguments": { "customer_id": "C1001", "line_id": "L1002" } }
]},
// 评分标准
"evaluation_criteria": {
"actions": [
{ "requestor": "user", "name": "toggle_airplane_mode" },
{ "requestor": "user", "name": "toggle_roaming" }
],
"env_assertions": [
{ "func_name": "assert_mobile_data_status", "expected_status": true },
{ "func_name": "assert_internet_speed",
"expected_speed": 200, "expected_desc": "excellent" }
],
"communicate_info": null,
"nl_assertions": null,
"reward_basis": ["ENV_ASSERTION"]
}
}这条任务跑一次会经历这些:Agent 收到工单,看到"用户说上不了网";它查运营商侧的数据库,发现漫游已开通、套餐正常——故障不在它能看到的地方;它必须通过提问引导用户查看设备;用户模拟器按 known_info 只知道自己叫 John Smith、在法国,它不知道飞行模式开着;Agent 问到设备状态,模拟器按事实锚定要求必须先调用工具查询再回答;最后测速结果达到 excellent,env_assertions 通过,任务算解决。
四处设计值得说明。
用户的认知范围被显式建模。 known_info 只有姓名、号码、所在国家三项。飞行模式开启、数据漫游关闭这两个真正的故障原因不在其中——用户并不知情,无法主动陈述,Agent 只能通过提问和引导用户查询来获取。
这是渐进式信息透露在任务定义层面的实现:它不靠一句「不要一次说完」的提示词去约束模拟器,而是把用户的知识范围建模为独立字段。多数基准在任务开始时给出完整需求,而真实用户的初始表述往往只是「我上不了网」。把需求澄清到可执行的程度,本身就是 Agent 必须具备的能力。
模拟用户被约束,防止被 Agent 说服。 task_instructions 含三类约束:情绪设定(首次修复失败后表现轻度不满)、验收口径(仅当测速为 excellent 才认定解决,poor、fair、good 均不接受)、事实锚定(关于设备状态的任何回答必须以工具返回为依据)。
第三类最关键。缺少事实锚定约束时,模拟用户会顺应 Agent 的引导确认问题已解决,评估随之退化为两个模型之间的相互确认。
两侧信息都不完整。 飞行模式与漫游开关属于用户侧,运营商侧的漫游开通属于 Agent 侧。这一划分决定了故障的形态——运营商侧已开通,用户设备侧关闭,Agent 查询数据库只能得到「配置正常」。故障位于数据库不可见的一侧,只有引导用户查询才能发现。
多维度的过程与结果校验。 env_assertions 检验终态,actions 检验关键动作是否发生,communicate_info 与 nl_assertions 检验必要信息是否已告知用户。每条任务可以检查四个维度中一个或多个。
2. 从这次运行看评估对象
上面这条任务跑一次,涉及的东西比"输入加输出"多得多。
| 对象 | 在这次运行里是什么 | 少了它会怎样 |
|---|---|---|
| 任务 | 那份 JSON | 每次跑的任务不同,结果无法比较 |
| 尝试 | 这一次运行 | 无法区分"偶尔成功"与"稳定成功" |
| 轨迹 | 每一步的工具调用与返回 | 只知道失败,不知道哪一步开始错 |
| 结果 | 终态:数据可用、测速达标 | 只能看模型自己说做完了没有 |
| 评分器 | env_assertions 与 actions | 只能人工看输出,无法规模化 |
| 套件 | 这类任务与其他任务的分组 | 只能看单条,看不出分布 |
六个对象之外还有一层:判定这些对象的三层验证。
结果层读环境状态:数据可用、测速达标。不依赖模型自述,最可靠。
过程层读动作序列:该查设备的时候查了吗,该问的时候问了吗。它区分"达成"与"以允许的方式达成"。
质量层读表达:是否耐心、是否解释清楚、是否给了合规的替代方案。
三层的可靠性依次下降,成本依次上升,而且上层依赖下层。构建顺序应当自下而上——常见错误是一开始就做质量层,因为"它最能反映体验",结果量表分数无法解释:涨了不知道是不是真的好。
2.1 与输出评测的根本差别
传统模型评测是「一次输入对一次输出」。上面这条任务不是这样:它是一个任务、多次尝试、一条轨迹、一个业务结果。两者的分子分母都不同。
差别的直接后果是三件事无法回答:为什么失败、从哪一步开始失败、换个环境还成不成立。
要回答这三件事,评估必须从「比较输出」变成「重复运行一台装置」。装置这个词是准确的:给定同一个初始状态,同一个 Agent 应当得到可比较的结果。这要求环境、工具、评分、协议都可控。
3. 四个组成部分各自的职责
第 1 节那份 JSON 有四个顶层字段,它们不是随意分组的,各自回答一个问题。
| 字段 | 回答什么 | 谁读它 |
|---|---|---|
| 工单 | 要解决什么 | Agent |
| 模拟器规范 | 对面那个人知道什么、怎么表现 | 用户模拟器 |
| 初始状态 | 从哪开始 | 环境 |
| 评分标准 | 什么算成功 | 评分器 |
四者的独立性很重要:改评分标准不影响模拟器行为,改模拟器规范不影响初始状态。这个独立性让"调整难度"与"调整判据"变成两件可以分开做的事。
3.1 工单要写到什么程度
工单不能太具体。如果它把"先查运营商侧再问用户设备"也写进去,任务就不再考察诊断能力,只考察照做能力。
工单也不能太模糊。"用户上不了网,帮他解决"这种写法在多数实现里会导致 Agent 漫无目的地试。第 1 节那份工单的写法值得参考:它给了验收口径(测速 excellent)、约束(不换套餐)、以及用户的授权范围(必要时愿意充值 2.0 GB)。这三样让 Agent 知道"能做什么"与"做到什么程度",但没有告诉它"怎么做"。
判据是:工单应当让一个有能力的人能独立完成,但不告诉他方法。
3.2 模拟器规范的三分法
known_info、unknown_info、task_instructions 三分不是形式。
known_info 是用户「知道且会主动说」的。 第 1 节里只有姓名、号码、所在国家。这三项在对话里会被用户自然提到。
unknown_info 是用户「不知道」的。 第 1 节里是 null,因为故障原因(飞行模式开着)用户确实不知道。这一栏留空不是省略,是明确声明"这一部分不能由用户提供"。
task_instructions 是行为规则。 情绪、验收口径、事实锚定都在这里。
三栏分开的价值在于:如果只写一段自然语言描述用户,写的人很容易把"用户知道故障原因"混进去,因为写的人自己知道。分成三栏强制写的人对每项信息做一次归属判断。
3.3 初始状态要能被两次重置
initialization_actions 的三个动作分别属于用户侧与 Agent 侧。两边的状态都要重置。
一个容易被忽略的要求:重置要幂等。如果第一次运行后某个动作留下了残留,第二次重置时同一个动作可能失败。做法是让每个动作都写成"设置成某个值",不用"切换"。
第 1 节的写法是 turn_airplane_mode_on 与 turn_roaming_off——名字里带了目标状态,所以重复执行结果相同。如果写成 toggle_airplane_mode,第二次执行就会把它切回去。
3.4 评分标准的四层
actions、env_assertions、communicate_info、nl_assertions 四层,reward_basis 决定用哪几层聚合。
| 层 | 检查什么 | 用代码还是模型 |
|---|---|---|
actions | 关键动作是否发生 | 代码 |
env_assertions | 终态是否正确 | 代码 |
communicate_info | 必须告知的信息是否告知 | 代码(字段匹配) |
nl_assertions | 自然语言表述是否满足要求 | 模型 |
前三层都能用代码判,第四层只能靠模型。分层的价值在于:能代码判的部分不交给模型,减少偏差来源。
reward_basis 是聚合规则。第 1 节只用了 ENV_ASSERTION,意味着这条任务只看终态。如果要把"必须告知用户"也算进成功条件,就要把 communicate_info 加进去。
4. 五个组成要素
从上面这条任务可以抽出评估装置的五个要素。
| 要素 | 内容 | 缺了会怎样 |
|---|---|---|
| 数据集 | 初始状态、工单、模拟器规范、验收标准打包为一条 | 每次运行任务不同,结果无法比较 |
| 环境状态 | 数据库中的客户、线路、套餐、账单,加设备侧开关 | 上次残留影响这次,同一用例有时过有时不过 |
| 工具接口 | 两侧各自的原子操作 | Agent 只能用自然语言描述动作,规划被工具吸收 |
| 评分标准 | 四层检查加聚合规则 | 只能人工看输出,无法规模化 |
| 执行协议 | 交互顺序与终止条件 | 对话可能永不结束,或在不该结束时结束 |
第三项有一条要求容易被忽略:工具必须是原子操作,不存在「解决用户的上网问题」这类高层抽象。抽象层次过高会使评估退化为对单次函数调用的考察,规划与推理环节被工具本身吸收。
反过来说,工具也不能过细到把业务逻辑拆成十几个步骤——那样评估的是「能否照着清单执行」,也不考察规划。
合适的粒度是「一个有业务含义的操作」:查用量、改配置、发通知、转人工。每一步都有明确的业务语义,但都不包含「判断该做什么」这层。
4.1 两侧各有自己的工具
对话型任务里,工具要备两套:Agent 侧能查询与修改业务状态,用户模拟器侧能修改设备状态。
这个划分不是形式上的对称,它决定了故障的形态。前面那条任务里,运营商侧已经开通漫游,用户设备侧的漫游开关是关的——Agent 查询数据库只能得到「配置正常」的结论。故障位于数据库不可见的一侧,只有引导用户查询才能发现。
如果只有 Agent 侧工具,这类故障无法构造。任务会退化成「查一下配置,发现正常,改一下配置」的线性流程,中间没有信息不对称。
双侧工具还带来一个验证维度:用户改变状态后,Agent 必须重新调用工具才能获知结果。这验证了「Agent 是否真的读到了用户侧的操作结果」,防止它凭之前的状态推断。
4.2 需要隔离的第三类环境
前两类之外还有一类:任务会在真实系统上产生副作用,必须隔离。
隔离环境的关键是「重置成本」。一个能在一秒内重置的沙盒,可以支持每次尝试前都重置;一个需要五分钟准备的环境,只能支持一批任务重置一次,后者会让任务之间有残留影响。
判据是:如果一个任务失败了,下一次跑同一任务能否从同一状态开始。不能,说明重置成本过高,需要改环境设计。
第五项在对话型任务里尤其重要。正常终止信号是模拟用户输出结束标记,此外还有轮数上限,以及模拟用户因耐心耗尽而主动结束——沟通效率过低本身即计为失败。
4.3 执行协议为什么是独立要素
前四个要素都是「静态配置」,只有执行协议定义「怎么跑」。它决定三件事:谁先说话、什么算一轮、什么情况下强制停止。
三个设计点容易被忽略。
轮数上限要有。 没有上限时,两个模型可能永远互相确认,评估跑不完。上限值要按任务复杂度定,不能取一个全局常数——一个三十步的任务与一个三步的任务用同一个上限,前者会被误判为失败。
耐心耗尽要算失败。 用户模拟器因为沟通效率过低而结束对话,这个结局应当记为失败,不能记为「未完成而排除」。否则评估会奖励那些反复追问的 Agent——它们把任务拖到超时而免于计分。
强制停止后的状态要保留。 轮数用尽时,环境状态是那一刻的快照。这个快照仍然可以按结果层判定——它可能刚好达成,也可能差一步。按快照判定比按「超时即失败」更准确。
5. 两类环境与四种形态
对话型任务必须有用户模拟器。另一大类任务没有对话对方:代码生成、数据分析、数学求解,Agent 自始至终只与工具交互,正确性由能否通过执行验证决定。
这两类的结构差异:
| 维度 | 人机交互型 | 工具调用型 |
|---|---|---|
| 交互对象 | 模拟用户 | 无 |
| 考察重点 | 沟通策略与引导 | 可观测状态变更的正确性 |
| 评分主体 | 环境断言加自然语言断言 | 测试代码 |
| 终止条件 | 用户确认或轮数耗尽 | 给出答案或轮数耗尽 |
工具调用型环境按「是否保持跨轮状态」与「是否隔离」两个维度再分层:
| 形态 | 状态 | 工具调用 | 典型用例 |
|---|---|---|---|
| 单轮 | 无 | 无 | 单轮问答、数学题 |
| 工具 | 无 | 多轮 | 搜索加信息综合 |
| 有状态工具 | 有 | 多轮 | 修改数据库记录后验证 |
| 沙盒 | 有加隔离 | 多轮 | 代码执行与测试 |
分层的实际用途是选型:任务只需要一问一答,不要引入沙盒;任务会改数据库,就必须有状态与重置。
6. 五个基准的设计对照
同一个五要素框架,填法可以差很远。几个公开基准的填法对照,能看出取舍空间。
| 基准 | 被测能力 | 任务来源 | 环境扮演者 | 验证器 |
|---|---|---|---|---|
| 对话型客服 | 人机交互与工具调用 | 人工编写加组合生成 | 用户模拟器加业务数据库 | 四层检查按聚合规则判二元 |
| 代码修复 | 软件开发 | 真实议题,人工筛选 | 代码仓库加测试套件 | 修复前失败修复后通过,加未破坏原有 |
| 手机界面操作 | 图形界面操作 | 参数化模板实例化 | 真实设备模拟器 | 最终界面状态断言 |
| 桌面操作 | 图形界面操作 | 从预置中间状态启动 | 真实虚拟机 | 百余个独立评估函数 |
| 终端操作 | 命令行编程 | 人工编写 | 容器 | 文件系统检查加真实执行 |
| 信息搜集 | 通用助手 | 人工编写加专有附件 | 开放网络 | 精确字符串匹配 |
六个基准覆盖了四类环境形态(有交互对象、有状态、有隔离、开放环境)。它们的验证器深度也不同:从界面状态断言到百余个评估函数。
一条观察:验证器越深的基准,任务数越少。原因是每个任务的准备与验证成本都更高。这是评估集设计里的一个固定取舍——深度与广度不可兼得。
7. 难度分层与诊断价值
评估集需要覆盖不同难度,不只是为了「区分度」,更是为了诊断。
一个公开基准把全部任务分三级,并给出人类与模型的表现对照:
| 级别 | 所需能力 | 人类 | 模型 |
|---|---|---|---|
| 一级 | 一到两个工具 | 93.9% | 30.3% |
| 二级 | 多步思考 | 91.8% | 9.7% |
| 三级 | 复杂组合 | 87.3% | 0% |
这一分层的价值在最后一列的形状:模型从三成掉到零,人类只掉六个百分点。三条曲线的差距不同,指向不同的改进方向——一级失败指向基础工具使用,二级指向多步规划与信息整合,三级指向长序列思考。
如果只看总体通过率,这三个方向会被平均成一个数。
7.1 难度分层的两个操作要求
每级任务数不能太少。 三级只有五条任务时,一条的差别就是 20 个百分点,噪声淹没有效信号。
难度要按能力定义,不按步骤数定义。 「需要十步」不等于「困难」——十步重复操作可能很简单。「需要多步且每步依赖前一步的结论」才是困难。
后者与前者在实践中经常被混淆,导致某些基准的「困难」任务其实只是冗长。
8. 指标口径:能力上限与可靠性
上一节确定在哪里测,这一节确定怎么算成功。
同一个成功率数字,在不同场景下含义完全不同。若它对应退款客服,意味着每五名用户有一人未拿到退款;若它对应漏洞挖掘,五次中命中四次已属可观。差别在业务对成功率的要求。
两个指标分别回答两个问题。
能力上限。 同一任务运行 \(k\) 次,只要至少一次通过,任务就算通过:
业务可靠性。 同一任务连续运行 \(k\) 次,要求每一次都通过,且不能触发安全、合规或幻觉等一票否决项:
两者的差距在 \(p\) 中等时最惊人。取 \(p = 0.6\)、\(k = 5\):
前一个数字说「几乎总能至少成功一次」,后一个说「连续五次都不出错仍然很难」。前者适合探索型任务的能力展示,后者才接近支付、退款、权限变更、生产部署的可靠性要求。
报告必须写清 \(k\) 的口径:是同一任务的 \(k\) 次独立采样,还是生产流水线上连续 \(k\) 个任务。对会产生副作用的操作,不能简单地重试直到成功,而应在沙盒或可回滚环境中采样,并把每一次失败都记入可靠性指标。
9. 采样预算:任务数与重复次数
两笔独立的预算,不能互换。
任务数决定分辨力。设允许误差 \(\varepsilon\)、置信水平对应 \(z\)(95% 时约 1.96)、成功率先验 \(p\):
取 \(p = 0.5\)(最保守)、\(\varepsilon = 0.05\):\(n \approx 384\)。
这个数字让很多团队吃惊——他们通常用几十个任务就下结论。几十个任务只能分辨 15 到 20 个百分点的差异,分辨不了 5 个百分点。\(\varepsilon\) 放宽到 0.1 时 \(n \approx 96\),所以任务数取决于要分辨多大的差异。
重复次数决定单任务结论的稳定性。采样温度、工具时序、环境状态都会带来随机性。
两个预算的差别:
| 配置 | 知道什么 | 不知道什么 |
|---|---|---|
| 任务少、每任务多次 | 这批任务上很稳 | 换一批是否成立 |
| 任务多、每任务一次 | 覆盖广 | 单任务结论是否靠运气 |
实用配比按场景分:
| 场景 | 任务数 | 重复 | 理由 |
|---|---|---|---|
| 回归集 | 多(200 以上) | 少(1–2) | 关注是否退化,覆盖优先 |
| 关键任务 | 少(10–20) | 多(5–10) | 关注稳定性,方差优先 |
| 能力上限 | 中(50) | 多(10 以上) | 需要 pass@k,样本要够 |
第三个场景的成本最高:50 个任务乘 10 次是 500 次运行。所以能力评测通常离线做,不进每次发布的回归。
10. 数据集构造
四件事决定数据集能不能用。
任务族划分。 按任务形状分组(单轮问答、多轮检索、代码修改、文档生成),不按业务领域分。同一族内的任务在失败模式上相似,聚合才有意义。
难度分层。 每族内分易、中、难。只看总通过率会掩盖「难任务全崩」——总通过率 82% 可能是易题 100%、难题 20%。
保留集。 划出一部分任务,只用于发布前评测,不参与任何调优决策。保留集的独立靠使用纪律维持,不靠物理隔离:如果它被用来选择提示词、选阈值、选模型,它就不再是保留集。
检查方法两条:看它有多久没被用于决策,以及它与训练集的文本重叠度。重叠超过 10% 说明已被污染。
反例集。 专门收集已知的失败模式。它不追求通过率,追求「不能退化」——例如「曾经越权过一次的那类任务」必须永远通过。
10.1 任务从哪里来
三个来源,各自的成本与代表性不同。
| 来源 | 代表真实使用的程度 | 成本 | 局限 |
|---|---|---|---|
| 人工编写 | 低到中 | 每条几十到几百元 | 容易写出「教科书式」的任务 |
| 真实日志回放 | 高 | 需要脱敏与重放环境 | 分布偏斜,需聚类去重 |
| 参数化模板 | 中 | 一次编写,多次实例化 | 只覆盖模板考虑到的维度 |
第一个来源的问题是它反映作者的想象,而作者的想象往往比真实使用更规范——真实用户会写错字、给错信息、中途改需求。
第三个来源的价值在于可扩展,但它只能覆盖模板设计时想到的参数。模板里没有的维度永远不会出现。
实践中常见组合是「真实日志提供分布、人工补齐少见情形、模板扩展数量」。三者各补另一方的缺口。
10.2 参数化模板的三重收益
模板化不只是省事。
回放失效。 参数每次不同,背诵固定操作序列没有用。
近乎无限实例。 一个模板可以生成成千上万个实例,适合做重复采样的稳定性检验。
可做对照实验。 固定部分参数、只改其余参数,可以精确测量某个因素的影响。这是模板化最被低估的用途——它让「某条件是否影响成功率」变成一个可测的问题。
11. 完整评测的配置
把前面各节的参数合到一处,看一次可用的评测需要定下哪些东西。
评测配置:
任务集:
回归集: 240 条(四族各 60 条,每族内易中难 20/20/20)
保留集: 80 条(冻结后不参与任何调优)
反例集: 30 条(要求 100% 通过)
陷阱集: 20 条(要求 0% 上当)
采样:
回归: 每任务 1 次
关键任务: 每任务 5 次
能力评测: 每任务 10 次(离线)
判据:
关键用例: 全过
总体通过率: 不低于基线减 2 个百分点
单任务族: 不低于基线减 10 个百分点
成本与时延:
单任务成本: 不超过基线 1.2 倍
首词时延: 不超过基线 1.3 倍四组参数各自的来历。
任务数 240。 按 \(\varepsilon = 0.065\) 反推(\(n \approx 1.96^2 \times 0.25 / 0.065^2 \approx 227\),取整到 240 便于四族均分)。这个精度能分辨 6 到 7 个百分点的差异。
保留集 80。 少于回归集,因为它的用途不同——它只需给出「有没有整体退化」的判断,不需要分辨细粒度差异。
重复 5 次。 关键任务的单次结果有随机性,5 次能看出波动范围。少于 3 次时,一次偶发失败与真实退化无法区分。
单族 10 个百分点。 四族各 60 条,单族的标准误比总体大。10 个百分点的判据比总体宽松,正是为了容纳这个更大的噪声。
11.1 配置之外的两件事
配置文件写不进去的两件事,但它们决定这套配置有没有用。
谁来看结果。 如果没人按族看结果,那「单族不塌陷」这条判据永远不会触发。
保留集多久轮换。 保留集不是永久不变的。使用一段时间后,它的题目会泄漏到调优过程里(因为人看过结果)。可行做法是每季度换掉三分之一,换掉的题目降级进回归集。
| 失败模式 | 症状 | 成因 | 修的位置 |
|---|---|---|---|
| 用测试集调参 | 分数持续上升,线上无改善 | 看到失败就改提示词,改完跑同一批 | 保留集,调参只看训练子集 |
| 评分器与实现互相泄漏 | 评测通过但真实失败 | 评分器检查内部实现细节 | 只检查外部可观察行为 |
| 只看总分不看维度 | 总分不变,某族全崩另一族涨 | 聚合时平均掉了 | 按族分别报告,关键族设硬性要求 |
| 任务分布偏离真实使用 | 通过率高但投诉集中 | 用例以单轮为主,实际多轮 | 按真实调用形状配比 |
第四行是最容易犯的:200 个用例里 180 个是单轮问答、20 个需要多轮工具调用,而真实使用里八成是后者。通过率的分母与真实分布严重不匹配。
第三行最隐蔽,因为它在总分上看不出来。一个具体的例子:某次改动让代码修改类任务全崩、文档生成类大幅改善,总分持平。只看总分,这次改动会被当成「无影响」合并进去。