Agent 评测体系综述:从一条用例到一套飞轮

本文综合四份资料:《深入理解 AI Agent》第 7 章(下称”第 7 章”)、美团《Agent 评测漫谈》(下称”漫谈”)、美团《Agent 评测白皮书·评测全览》(下称”白皮书”)、helson《Agent 评测·模块十》(下称”模块十”)。四份资料视角不同:第 7 章偏原理与公开基准,像教科书;漫谈与白皮书是企业团队两年落地经验,偏组织与体系;模块十是面试向的可执行模板,偏”照着做”。文中数字均为来源自述,未经第三方核实;模块十的阈值是经验值(confidence: low)。

关于”工程化演示”小节:各节末尾的代码是我按来源中的设计写的示意伪代码,不是来源原文;函数名、字段名、库名均为虚构,阈值标”示例值”的是我设的占位数字,需按自己项目调整。每段演示都注明了它落实的是哪份来源的哪条做法。

一、为什么要评测:评的不是模型,是系统

四份资料的出发点一致:Agent 的效果判断不能靠”感觉变好了”。模块十把原因归为三个传统软件没有的难题:同一输入多次结果不同(非确定性),模型、Prompt、工具一变行为就悄悄漂移(黑盒化),多步调用中前面的小偏差会一路放大(错误级联)。白皮书则从项目层面给出了反面教材:大多数 Agent 项目死在”停在 Demo””卡在扩量””说不清业务价值”三处,共同原因是缺少可靠的判断机制。来源:[[helson-Agent评测八股-模块十]]、[[美团-Agent评测白皮书-评测全览]]

更关键的共识是评测对象变了。漫谈说评测对象是”模型 + 系统 + 工具 + 流程”;第 7 章说是”模型 + Harness 的组合体”,同一模型换个 Harness 表现可能差很多。所以当分数不好时,要先分清是模型能力不够还是 Harness 设计有缺陷。第 7 章给出两种实验来区分:

  • 模型替换:固定 Harness 只换模型。换强模型分数不涨,瓶颈多半在 Harness;分数随模型大幅波动,瓶颈在模型。
  • 消融:固定模型,逐个关掉 Harness 组件,看哪个组件真有贡献。

来源:[[深入理解AIAgent-第7章-Agent的评估]]、[[美团-Agent评测漫谈]]。相关概念:[[Agent-Harness]]

一句话定义(模块十):Eval = 输入 → 执行 → 捕获 Trace 与产物 → 一组检查规则 → 可对比的分数。漫谈则把它写成研发公式:观测 + 评测 = 持续迭代。

二、什么算成功:先定口径,再谈分数

2.1 Pass@k 与 Pass^k

第 7 章和模块十都把这对指标列为核心:

指标 含义 衡量 适用
Pass@k k 次中至少 1 次通过 能力上限 科研探索、漏洞挖掘、可人工挑选的任务
Pass^k k 次全部通过 稳定可靠性 退款、支付、权限变更、生产部署

若单次成功率 p 独立,Pass@k = 1 − (1 − p)^k,Pass^k = p^k。p = 0.6、k = 5 时,前者约 99%,后者约 7.8%,差距说明”偶尔能做成”和”每次都做对”是两回事。第 7 章还强调报告要写清 k 的口径(同一任务独立采样,还是流水线上连续 k 个任务);会产生副作用的操作不能”重试到成功”,要在沙盒里采样并把每次失败计入可靠性。来源:[[深入理解AIAgent-第7章-Agent的评估]]、[[helson-Agent评测八股-模块十]]

2.2 评什么:四层与五类

漫谈的”四层”与模块十的”五类”可以对齐来看:

漫谈四层 模块十五类 模块十优先级
结果层(任务是否完成、输出是否可用) Outcome 结果;Evidence 证据(结论能否对应到材料或工具返回) P0
风险层(越权、误操作) Safety 安全(含一致性 pass^k、幻觉率、越权) P0
过程层(规划、步骤是否稳定) Trajectory 过程 P1
效率层(耗时、Token、调用次数) Cost 成本 P1

模块十多出的 Evidence 一项值得单独记:它对应第 7 章所说的”做对了但说错了”——环境状态全对,告诉用户的金额、状态却错了(见第六节)。来源:[[美团-Agent评测漫谈]]、[[helson-Agent评测八股-模块十]]

2.3 达标线与业务风险挂钩

同一个 0.8 的通过率,对退款客服意味着五分之一用户拿不到退款,对漏洞挖掘 Agent 则相当可观(第 7 章)。模块十把这种差别落成了容忍阈值:关键决策类 0%、辅助分析类 ≤10%、创意生成类 ≤40%(经验值)。白皮书则用”分层门禁”表达同一意思:安全、数据准确性类 Rubric 一票否决,体验类设阈值。

工程化演示:从试验结果算 Pass 指标与上线判断

落实:第 7 章的 Pass@k / Pass^k 定义与”写清 k 的口径”,模块十的”任一 Trial 不达标则用例 0 分”与按 Agent 类型的容忍阈值。估计公式用的是从 n 次试验中无放回抽 k 次的组合估计,这一点四份来源没有给出,属于我补充的常见做法(HumanEval、τ-bench 论文的写法,待回查原文)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
from math import comb

def pass_at_k(n, c, k):
"""n 次试验 c 次通过,估计 Pass@k:抽 k 次至少 1 次通过"""
if n - c < k:
return 1.0
return 1 - comb(n - c, k) / comb(n, k)

def pass_hat_k(n, c, k):
"""估计 Pass^k:抽 k 次全部通过(c < k 时 comb 为 0)"""
return comb(c, k) / comb(n, k)

def summarize(results, k):
"""results: {task_id: [True, False, ...]},每个任务至少 n >= k 次独立试验"""
rows = []
for task_id, trials in results.items():
n, c = len(trials), sum(trials)
rows.append({
"task": task_id,
"pass@1": c / n,
f"pass@{k}": pass_at_k(n, c, k),
f"pass^{k}": pass_hat_k(n, c, k),
"case_ok": c == n, # 模块十:任一次不达标,该用例记 0 分
})
report = column_means(rows)
report["k_semantics"] = "同一任务独立采样" # 第 7 章:报告必须写明 k 的口径
return report

# 上线判断(模块十经验值):允许多少比例的用例不稳定
TOLERANCE = {"critical": 0.00, "assistive": 0.10, "creative": 0.40}

def release_ok(report_rows, agent_type):
unstable = 1 - mean(r["case_ok"] for r in report_rows)
return unstable <= TOLERANCE[agent_type]

两个容易漏的细节:有副作用的工具(退款、发邮件、删数据)在采样时必须指向沙盒或可回滚环境;infra_error(见下文执行器)不进分母,否则评测系统的故障会被算成 Agent 的不稳定。

三、任务从哪来:Task、环境与评测集

3.1 一条 Task 由什么组成

漫谈与白皮书借用 Anthropic 对 Task 的定义:具有明确输入和成功标准的单个测试。短程 Agent 的评测样本是(query, ground_truth, answer),长程 Agent 变成(prompt, expected_behavior, trace)——参考答案从”一个字符串”变成”对执行过程的描述 + 最终环境状态”。来源:[[美团-Agent评测漫谈]]、[[美团-Agent评测白皮书-评测全览]]

第 7 章用 τ²-bench 的一条电信客服任务把这件事拆得最细。一条任务包含四部分:给 Agent 的工单、给用户模拟器的行为规范、初始状态、评分标准。其中有三处设计值得借鉴:

  1. 显式建模用户知道什么:known_info 里只放姓名、号码、所在国,真正的故障原因(飞行模式开着、漫游关着)用户并不知道,Agent 只能通过提问和引导去发现。
  2. 防止模拟用户被 Agent 带偏:规定情绪(第一次修复失败后表现不满)、验收口径(测速必须是 excellent)和事实锚定(关于设备的回答必须以工具返回为准)。没有事实锚定,评估就退化成两个模型互相确认”已解决”。
  3. 多维校验:终态断言、关键动作、必须告知的信息,按 reward_basis 聚合。

来源:[[深入理解AIAgent-第7章-Agent的评估]]

3.2 评估环境的五要素

第 7 章把可重复运行的评估环境归纳为五项,缺一不可:数据集(任务文件)、可重置的环境状态、原子化的工具接口(工具抽象得太高,规划能力就被工具吸收、测不出来)、评分标准、执行协议(终止信号、轮数上限、模拟用户耐心)。模块十从工程角度补了同一件事:每个用例在隔离环境执行、执行前重置(如 git checkout . && git clean -fd)。白皮书则强调”固定”:固定样本、固定标准、尽量贴近生产的固定执行环境;最容易被破坏的是环境,可用”同一批样本在离线环境和线上影子流量各跑一遍”检验保真度。

3.3 评测集的来源与分类

把四份资料的分类放在一起,可以得到一张完整的图:

维度 分类 出处
来源 公开基准(粗筛模型、借设计手法)/ 自建业务集(选型与 Harness 决策)/ 生产轨迹回流(最准,上线后成为主体) 第 7 章
用途 黄金集(好的下限)/ 错题集(踩过的坑)/ 挑战集(能力边界)/ 必过集(核心功能) 白皮书
粒度 端到端评测集(要不要拉警报)/ 过程评测集(警报响了找谁),过程集可按知识库、Skill 继续拆 白皮书
目标 能力测评(爬坡,起步 20–50% 通过率)/ 回归测评(守门,接近 100%,只加不减) 模块十
场景 触发 / 核心逻辑 / 产物质量 / 异常容错,每类有正负向用例 模块十
决策边界 端到端回归 / 轨迹前缀回归 第 7 章

几条值得记住的设计要点:

  • 负向触发用例不能少:只测正例,Agent 会学成”什么都触发”,而过度触发比不触发更难被发现(模块十)。
  • 不要过早拆细:先跑端到端和核心模块的过程集,遇到”知道坏了不知道哪坏了”再细化(白皮书)。
  • 防泄漏:GAIA 让题目必须组合多个信息源并配专有附件;AndroidWorld 用参数化模板每次生成新实例;Terminal-Bench 在题面埋金丝雀 GUID 检测泄漏(第 7 章)。
  • 质量控制是长期工作:SWE-bench Verified 从 1699 条人工审核中只留下 500 条;OSWorld 发布后又修了 300 多个问题(第 7 章)。

工程化演示:一条 Task 的定义文件

落实:τ²-bench 的”工单 + 用户模拟规范 + 初始状态 + 多维评分”四段结构(第 7 章),模块十的四场景与正负向,白皮书的端到端/过程分集与(prompt, expected behavior)定义,”做对了也要说对”的告知断言。示例场景是虚构的售后退款。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
id: refund-partial-001
suite: e2e/售后退款 # 端到端集;过程集如 process/skill/order_query
tier: regression # capability(爬坡)| regression(守门)
scenario: core_logic # trigger | core_logic | artifact | error_handling
polarity: positive # 负向触发用例填 negative,期望是"不调用本 Agent / 本 Skill"

input:
prompt: "上周买的耳机左耳坏了,想退一半的钱"
user_sim: # 有交互对象时才需要
known_info: "订单号 A1029;只知道左耳没声音" # 用户不知道的事 Agent 只能去问、去查
instructions: >
第一次给出的方案若不是退款,表示不满;只有退款记录生成才算解决;
关于订单的回答必须以工具返回为准,不得编造。
max_turns: 20

init_state: # 每次试验前重置到同一起点
- {env: db, op: load_fixture, file: fixtures/order_A1029.sql}

expected:
outcome: # 终态断言:查环境,不信 Agent 自述
- {assert: refund_exists, order: A1029, amount: 149.50}
actions: # 关键动作必须发生
- {tool: query_order}
- {tool: create_refund}
forbidden: # 禁止动作,触发即一票否决
- {tool: create_refund, where: "amount > 149.50"}
communicate: # 告知断言:环境对了,话也要说对
- "告知退款金额 149.50 元"
- "告知预计到账时间"

rubric: rubrics/refund_reply.yaml
veto: [hallucination, policy_violation]
trials: 5
budget: {max_steps: 30, max_tokens: 60000, timeout_s: 300}
meta: {origin: online_badcase#8812, owner: 售后组, created: 2026-10-23}

meta.origin 记录用例从哪来(公开基准改编 / 手写 / 线上回流),后面统计”生产回流占比”就靠它。

工程化演示:评测执行器

落实:第 7 章的五要素(可重置状态、原子工具、执行协议、用户模拟器)与”先查评测系统”,模块十的隔离环境与执行前重置,白皮书的”固定环境”。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
def run_suite(suite, agent_cfg, seeds=(0, 1, 2, 3, 4)):
records = []
for task in suite.tasks:
for seed in seeds[:task.trials]:
env = Sandbox.create(image=suite.env_image) # 每次试验一个全新隔离环境
tracer = Tracer(task=task.id, seed=seed,
agent_version=agent_cfg.version,
model=agent_cfg.model,
tools_version=env.tools_version)
status = "finished"
try:
env.reset(task.init_state)
agent = build_agent(agent_cfg, tools=env.agent_tools, tracer=tracer)
user = (UserSimulator(task.input.user_sim, tools=env.user_tools, seed=seed)
if task.input.user_sim else None) # 双控:用户也有自己的工具
with deadline(task.budget.timeout_s):
interact(agent, user, task.input.prompt,
max_steps=task.budget.max_steps,
stop_signal="###STOP###") # 执行协议:终止条件
except DeadlineExceeded:
status = "timeout" # 算 Agent 失败
except InfraError as e: # OOM、镜像拉取失败、下游测试环境挂了
status = "infra_error" # 不算 Agent 失败,进评测系统排障队列
alert_eval_oncall(task.id, e)
outcome = env.snapshot() if status != "infra_error" else None
env.destroy()
records.append(TrialRecord(task, seed, status, tracer.spans, outcome))
return records

关键点只有三条:终态从 env.snapshot() 取,而不是从 Agent 的最后一句话取;每次试验都从同一 init_state 起跑;评测系统自身的故障单独归类,不污染 Agent 的分数。

3.4 预期从哪来

这是四份资料分歧最大的地方,放在第八节详细讨论。简言之:模块十主张”先跑一次、人工确认、快照即基线”;第 7 章主张轨迹前缀任务的答案是”可接受动作集合 + 禁止动作”,而非唯一答案;白皮书的 expected behavior 是对执行过程的描述。

四、谁来打分:确定性优先,模型兜底,人做校准

4.1 评分器的分工

模块十的排序是:确定性评分器 > Rubric 评分器(LLM-as-a-Judge)> 人工评分器。第 7 章用”验证方式谱系”讲同一件事:能写成程序断言的继续用断言,只有确实无法机械判定的维度才交给 LLM。确定性检查更便宜、更稳定,适合长期跑在 CI 里。

确定性验证的精髓是核实机器可复核的事实,而非 Agent 的自述。SWE-bench Verified 同时检查 FAIL_TO_PASS(修好了)和 PASS_TO_PASS(没弄坏);OSWorld 用 134 个评估函数去连数据库核实 SQL 是否真执行。来源:[[深入理解AIAgent-第7章-Agent的评估]]

人工不负责逐条打分,而负责高杠杆环节。模块十列了六个场景,其中最核心的是校准 LLM 评委:抽 100–200 条与人工对齐,一致率 ≥85% 才可用;另一个常被忽略的是诊断异常通过率——0% 或 100% 通常是任务或评分器坏了。来源:[[helson-Agent评测八股-模块十]]

4.2 Rubric 的三种写法

三份资料各给了一种 Rubric 设计,适用场景不同:

写法 做法 优点 出处
二元化 模糊指标下钻成细项,每项只答是/否/未知;用 unknown 占比反查 Rubric 是否合理 人人、人机一致率最高;美团自报某业务人机一致率从 62% 提到 92% 漫谈
分档加权 每维度四档且每档写可验证行为;分必要/重要/可选/陷阱项;幻觉等设一票否决 诊断信息丰富,能看出错在哪个维度 第 7 章(引 Scale AI 四准则)
负分制 初始 100 分,结果错 −100,每步偏离 −10,超时扣分,≥80 达标 扣分项可枚举、可解释,便于回溯 模块十

三者底层思路相同:把”好不好”拆成可判断的事实。漫谈举的反例最直观:让模型给”口语化”打 0–10 分是错误示范;改成”是否以’您’称呼骑手””是否用了’甭客气’’明儿见’这类口语词””是否含’吧’’呢’等语气词”三条是/否判断。

4.3 LLM 评委的已知偏差

第 7 章列了三种偏差及对策:

  • 长度偏差:偏爱更长的回答。对策是在 Rubric 中惩罚冗长、配对比较时先控制长度、定期审计分数与长度的相关性。
  • 位置偏差:偏爱先出现的候选。对策是交换顺序各评一次,两次一致才计入。
  • 同源偏好:Agent 与评委同一家族时,Agent 会学会钻评委的盲点(古德哈特定律)。对策是用不同家族的多个模型评判,日常用单模型,定期做多源审计。

模块十补充了工程约束:评委用固定版本模型、用 JSON Schema 约束输出。漫谈强调的是组织约束:只有人机一致率达标的机器评分才叫”自动化评测”,否则只是”机器标注”。相关面试卡:[[用Agent评测Agent的循环风险]]

工程化演示:确定性优先的评分管线

落实:模块十的”确定性 > Rubric > 人工”与固定版本评委、JSON 输出;漫谈的二元化 Rubric(是/否/未知);第 7 章的一票否决、”核实可复核事实而非自述”、配对评判交换顺序。

1
2
3
4
5
6
7
8
9
10
11
12
# rubrics/refund_reply.yaml —— 每条只问一个可判断的事实
items:
- id: amount_correct
question: "回复中的退款金额是否与工具返回的 149.50 元一致?"
weight: essential
- id: eta_told
question: "回复是否告知了预计到账时间?"
weight: important
- id: no_fabrication
question: "回复中是否出现了工具返回里没有的订单信息?(出现即答 yes)"
veto: true # 幻觉:一票否决
invert: true # yes 代表违规
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
def grade_trial(task, rec):
if rec.status == "infra_error":
return None # 不计分,不进分母
checks = []

# 1) 确定性检查:终态、关键动作、禁止动作、预算
checks += [assert_state(rec.outcome, a) for a in task.expected.outcome]
called = [s.name for s in rec.spans if s.kind == "tool"]
checks += [Check(f"action:{a.tool}", a.tool in called) for a in task.expected.actions]
checks += [Check(f"forbidden:{f.tool}", not violated(rec.spans, f), veto=True)
for f in task.expected.forbidden]
checks.append(Check("budget", rec.step_count <= task.budget.max_steps))

# 2) 代码判不了的才交给 LLM:逐条二元判断
evidence = tool_results(rec.spans) # 给评委看工具返回,让它对照事实
for item in load_rubric(task.rubric).items + as_items(task.expected.communicate):
checks.append(judge_binary(item, rec.final_reply, evidence))

# 3) 聚合:任一 veto 失败即 0;unknown 不计分但单独统计
if any(c.veto and c.result is False for c in checks):
return Score(0, checks, passed=False)
score = weighted_score(checks)
return Score(score, checks, passed=score >= task.pass_line)


def judge_binary(item, reply, evidence):
out = llm(model=JUDGE_MODEL_PINNED, # 固定评委版本,换版本要重新校准
temperature=0,
prompt=BINARY_TMPL.format(question=item.question, reply=reply, evidence=evidence),
schema={"verdict": ["yes", "no", "unknown"], "quote": "string"})
ok = {"yes": True, "no": False}.get(out.verdict) # unknown -> None
if ok is not None and item.get("invert"):
ok = not ok
return Check(item.id, ok, veto=item.get("veto", False), evidence=out.quote)


def pairwise(a, b, rubric):
"""配对比较:交换顺序各评一次,防位置偏差"""
r1 = judge_pair(first=a, second=b, rubric=rubric) # "first" | "second" | "tie"
r2 = judge_pair(first=b, second=a, rubric=rubric)
if (r1, r2) == ("first", "second"):
return "A"
if (r1, r2) == ("second", "first"):
return "B"
return "tie_or_human" # 两次矛盾:记平局或送人工

若要用模块十的负分制,只需把第 3 步换成”从 100 起扣”:结果断言失败 −100,每条步骤/工具偏离 −10,≥80 达标。两种聚合方式可以共用前两步的检查结果。

工程化演示:人人一致与人机一致校准

落实:漫谈的”人人一致、人机一致”与”用 unknown 占比反查 Rubric”,模块十的”抽 100–200 条、一致率 ≥85%”。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def agreement(xs, ys):
pairs = [(x, y) for x, y in zip(xs, ys) if x is not None and y is not None]
return sum(x == y for x, y in pairs) / len(pairs)

def calibrate(rubric, samples, labeler_a, labeler_b, threshold=0.85):
"""samples:从线上/回测中抽 100–200 条;labeler_a/b:两名标注员背靠背标注结果"""
report = {}
for item in rubric.items:
ha = [labeler_a[s.id][item.id] for s in samples]
hb = [labeler_b[s.id][item.id] for s in samples]
m = [judge_binary(item, s.reply, s.evidence).result for s in samples]
human_human = agreement(ha, hb)
human_machine = agreement(ha, m)
unknown_rate = sum(v is None for v in m) / len(m)
if human_human < threshold:
status = "改标注指南或拆细 Rubric" # 人都对不齐,先别谈机评
elif human_machine < threshold or unknown_rate > 0.10: # 0.10 为示例值
status = "改评委提示词 / 补边界示例"
else:
status = "可规模化"
report[item.id] = dict(hh=human_human, hm=human_machine,
unknown=unknown_rate, status=status)
return report

顺序很重要:先人人一致,再人机一致。人与人都对不齐的条目,说明 Rubric 本身还不够具体,这时调评委提示词是白费功夫。

五、跑几次才算数:稳定性与统计显著性

模块十的做法是工程化的:每个用例跑 N 次(建议 5),任一次低于 80 分该用例记 0 分,用来暴露不稳定;上线时再按 Agent 类型套容忍阈值。白皮书同样要求多次试验取通过率。

第 7 章补上了统计学的部分,这是另外三份资料都没讲的:

  • 成功率的标准误约为 √(p(1−p)/n)。100 个用例、70% 成功率时,95% 置信区间约 ±9 个百分点,所以”新模型 73% 对旧模型 70%”不足以支持切换。
  • 同一批任务比较两个配置,用配对分析(逐题记录谁赢,McNemar 检验或配对 bootstrap),而不是两个成功率直接相减。
  • 每个配置跑 3–5 个随机种子;预期收益只有 2–3 个点而评估集只有几十题时,先扩样本。
  • 同时验证多个假设要收紧阈值或独立复跑。

判断标准可以浓缩成一句:分差要超过噪声、在配对分析中成立、并且能复现,才值得据此换模型或发版。来源:[[深入理解AIAgent-第7章-Agent的评估]]

工程化演示:两个版本谁更好

落实:第 7 章的配对分析(配对 bootstrap / McNemar)、多随机种子、多重比较收紧阈值与独立复跑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
def compare(cfg_a, cfg_b, suite, seeds=(0, 1, 2)):
# 同一任务、同一种子各跑一次:共享任务与随机条件,才叫"配对"
a, b = {}, {}
for t in suite.tasks:
for s in seeds:
a[t.id, s] = grade_trial(t, run_one(cfg_a, t, s)).passed
b[t.id, s] = grade_trial(t, run_one(cfg_b, t, s)).passed

# 先在任务内对种子取均值,再以任务为单位做配对 bootstrap
ma, mb = mean_by_task(a), mean_by_task(b)
deltas = [mb[t] - ma[t] for t in ma]
boot = sorted(mean(random.choices(deltas, k=len(deltas))) for _ in range(10_000))
lo, hi = boot[250], boot[9_750] # 95% 区间

# 单种子、二元结果时也可用 McNemar:只看"一赢一输"的不一致对
n_a_only = sum(a[k] and not b[k] for k in a)
n_b_only = sum(b[k] and not a[k] for k in a)
p_mcnemar = mcnemar_exact(n_a_only, n_b_only)

if lo > 0:
verdict = "B 更好(需独立复跑一次确认)"
elif hi < 0:
verdict = "B 更差"
else:
verdict = "差异在噪声内,不据此决策;如需分辨更小差异,先扩样本"
return dict(delta=mean(deltas), ci95=(lo, hi), p_mcnemar=p_mcnemar, verdict=verdict)

# 一次验证 m 个假设(例如同时试 5 个提示词版本)时,把显著性阈值收紧到 0.05 / m

报告里同时给出逐任务的胜负表:总体差异不显著时,常常能看到”某一类任务稳定变好、另一类稳定变差”,这正是第 7 章”简单任务迁移新模型、复杂任务保留原模型”那种差异化决策的依据。

六、失败之后:归因与回归

6.1 定位首个错误

白皮书说一个 Bad Case 只能说明”这里出了问题”,归因才回答”为什么”;归因高度依赖 Trace。第 7 章把归因的目标说得更精确:找首个导致任务偏离的错误,后面的错误多是连锁反应,不能把最后一个报错当根因。归因记录要结构化(步骤号、工具名、证据、主因与次因、是否可恢复、置信度),并且”规则先筛、LLM 再定位”比把全部轨迹喂给 LLM 更便宜也更准。

第 7 章的两个案例很有代表性:

  • 做对了但说错了:τ²-bench 基线中,带信息告知要求的失败里约三分之一是环境状态正确、告知用户的信息错误。只检查终态的评估会漏掉这类问题。
  • 根因归属容易错:AndroidWorld 里一个纯文本 Agent 要从图片录入开销,它在第 8 步自己写下”看不到图片内容”,却继续编造了四条数据。根因不是”模型不会 OCR”,而是 Harness 根本没给它图像观察通道。记错根因,就会去换模型而不是补观察通道。

来源:[[深入理解AIAgent-第7章-Agent的评估]]。相关面试卡:[[Agent-Badcase定位]]、[[根因定位失败案例分析]]

工程化演示:结构化归因记录与”规则先筛、LLM 再定位”

落实:第 7 章的归因记录字段(首错步号、主因/次因、可恢复性、置信度、版本快照)与三类可规则预筛的嫌疑;白皮书的两条出路。下面的记录用的就是第 7 章 AndroidWorld 的例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
trace_id: tr_20261023_0042
task: expense-entry-from-image
verdict: fail # 验证器:应写入的记录不存在
first_error:
step: 8
kind: assistant_message # 不是工具报错,是一条思考
quote: "I cannot actually see the content/details of the expenses in the image"
primary:
category: observation_gap # 观察通道缺失
owner: harness # 根因责任方:harness | model | tool | eval
recoverable: true
confidence: 0.8
secondary: # 后果,不另算根因
- {category: fabricated_data, step: 11}
route: agent_loop # agent_loop(修 Agent)| eval_loop(修 Rubric / 评测集)
snapshot: {agent_version: v1.8.2, tools_version: t0.9, env_ref: snap/0042, trace_ref: traces/0042.jsonl}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
SUSPECT_RULES = {   # 第 7 章:这几类可以先用规则筛出嫌疑轨迹
"claimed_not_run": lambda r: claims_tests_passed(r.final_reply) and not ran_command(r.spans, "test"),
"touched_asserts": lambda r: diff_touches(r.diff, ["assert", "@skip", "mock("]),
"api_no_migration": lambda r: touches_public_api(r.diff) and not has_migration(r.diff),
"said_wrong": lambda r: any(not traceable(f, tool_results(r.spans))
for f in extract_facts(r.final_reply)),
}

def attribute(rec):
hints = [name for name, rule in SUSPECT_RULES.items() if rule(rec)]
att = llm_locate_first_error(
spans=rec.spans, hints=hints, taxonomy=load_taxonomy(), # 错误分类表可作为提示词或 Skill
instruction="找出最早且能解释后续失败的错误;重复失败记为后果;引用步骤号与原文")
if att.confidence < 0.6: # 示例值
human_review_queue.put((rec, att))
if att.primary.owner == "eval": # 验证器 bug、用例歧义、Rubric 判错
att.route = "eval_loop"
return att

owner: eval 这一档是专门留给”评测本身错了”的。没有这一档,所有失败都会被默认算到 Agent 头上,也就是白皮书担心的”把 Agent 优化成迎合评测标准”。

6.2 负向 Case 的两条出路

白皮书提醒:负向 Case 不一定说明 Agent 不行,也可能是 Rubric 判错了。所以一条 Bad Case 要走两条路之一——修 Agent 后回测上线(Agent 迭代 Loop),或修 Rubric 并把 Case 收进错题集(评测体系迭代 Loop)。只走第一条,Agent 会被优化成”迎合评测标准”。第 7 章从另一个角度说了同样的话:看到分数下降时,先检查评测系统本身(资源不足导致进程被杀、验证器 bug、用例与生产脱节),再动 Agent。模块十的”0% pass^N 先查任务定义”也是这条原则。

6.3 把修复写成回归任务

第 7 章区分两层回归:

  • 端到端回归:从初始状态跑完整任务,检查终态、输出和安全条件,保证整体不退化。
  • 轨迹前缀回归:冻结首错之前的上下文、对话、工具返回和环境状态,只要求 Agent 给出下一步动作;答案是”可接受动作集合”(如”先读仓库规则””先询问用户””拒绝危险操作”)加禁止动作。成本低,能隔离单个决策问题;对高可靠 Agent 往往比端到端更重要。

模块十和漫谈从流程角度补充:线上每个 Bad Case 都要”复现 → 记录轨迹 → 分析根因 → 修复后入库”,回归集只加不减,每次 CI 都跑。

工程化演示:把首错写成轨迹前缀回归任务

落实:第 7 章的轨迹前缀回归(冻结首错前状态、只评下一步、可接受动作集合 + 禁止动作)。例子对应第 7 章 Coding Agent 分类里的”仓库里有两个同名配置文件时直接选一个”。

1
2
3
4
5
6
7
8
9
10
11
12
id: prefix/coding/ambiguous-config-003
type: trajectory_prefix
derived_from: tr_20260815_0117 # 源自哪条失败轨迹
frozen:
context: traces/0117.jsonl#steps[0:6] # 首错(第 7 步)之前的全部消息与工具返回
env_state: snap/0117_step6.tar
ask: next_action # 只评下一步可观察动作
acceptable: # 可接受动作是集合,不是唯一答案
- {kind: ask_user, must_mention: ["config/app.yaml", "deploy/app.yaml"]}
- {kind: tool, name: read_file, path_any: ["README.md", "CONTRIBUTING.md", "docs/config.md"]}
forbidden:
- {kind: tool, name: edit_file} # 没澄清就改
1
2
3
4
5
6
7
8
9
10
11
12
def grade_prefix(task, agent_cfg, seeds=(0, 1, 2)):
ctx = load_frozen(task.frozen)
results = []
for s in seeds:
action = next_action(agent_cfg, ctx, seed=s) # 只推理一步,成本远低于端到端重跑
if matches_any(action, task.forbidden):
results.append(("fail", "forbidden", action))
elif matches_any(action, task.acceptable):
results.append(("pass", None, action))
else:
results.append(("fail", "not_in_acceptable", action)) # 送人工看是否该扩充可接受集合
return results

同一个修复,建议同时产出两条用例:一条端到端(防止整体退化)、一条前缀(精确守住这个决策点)。模块十的”基线快照 + LCS 步骤对齐”可以用在流程固定的过程评测集上,前缀任务则适合路径开放、但某个决策点必须做对的场景。

七、从用例到体系:两条 Loop 与基础设施

7.1 美团的完备体系

白皮书把完备的评测体系概括为”四个模块、三种能力、两条 Loop、一套资产”:

  • 四个模块:离线评测(守住已知,做变更门控)、在线评测与监控(发现未知)、Case 挖掘与归因(枢纽)、观测基建(地基)。
  • 三种能力:发现问题、定位问题、驱动演进。
  • 两条 Loop:Agent 迭代 Loop 与评测体系迭代 Loop,共享线上真实样本,在 Case 挖掘与归因处、在评测集处两次咬合。
  • 一套资产:覆盖各功能模块的端到端与过程评测集。

在线部分里,监控回答”系统有没有在正常工作”,评测回答”工作得好不好”。白皮书举的例子:Skill 调用成功率 100% 但每次都返回错误数据,监控是绿的,评测是红的。在线评测有三种手段:影子模式(上线前)、AB(上线后确认业务价值)、巡检(日常盯基本盘)。Case 挖掘要多种来源搭配,因为各自有盲区;随机采样命中率最低,却是唯一能发现”根本没想到的问题”的手段。来源:[[美团-Agent评测白皮书-评测全览]]

漫谈则把执行链路拆成五环节数据飞轮:采集 → 清洗 → 评测 → 质检 → 分析归因,再加线上 AB 与持续观测。来源:[[美团-Agent评测漫谈]]

工程化演示:CI 中的分层门禁

落实:白皮书的”分层门禁、接入 CI/CD 才有约束力”,模块十的触发时机(PR 自动、模型升级手动、定时巡检)与能力/回归分开,第 7 章的成本护栏。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
GATES = [
# name 检查 是否阻断
Gate("veto", lambda r: r.veto_failures == 0, block=True),
Gate("regression", lambda r: r["regression"].pass_hat_k >= r.baseline.pass_hat_k - r.noise,
block=True),
Gate("cost", lambda r: r.mean_tokens <= 1.2 * r.baseline.mean_tokens, block=False), # 1.2 为示例值
Gate("latency_p95",lambda r: r.p95_latency <= 1.5 * r.baseline.p95_latency, block=False), # 1.5 为示例值
Gate("capability", lambda r: True, block=False), # 爬坡集只出报告
]

WATCHED = ["prompts/", "skills/", "agent/", "tools/", "model.yaml", "knowledge/"]

def on_pull_request(pr):
if not pr.touches(WATCHED):
return "skip"
rep = run_and_grade(suites=["veto", "regression", "capability"], cfg=pr.agent_cfg,
baseline=load_baseline("main"))
failed = [g for g in GATES if not g.check(rep)]
comment(pr, render_report(rep, failed)) # 报告分层:总览 → 用例 → 单次 Trial 扣分明细
return "block" if any(g.block for g in failed) else "pass"

# 其他触发:模型升级 -> 手动全量;每日 cron -> 巡检集对线上版本;回归集只加不减

回归门禁写成”不比基线差超过噪声带”,而不是”必须比上次高”,这样同时兼容了模块十”回归集守底线”和白皮书”离线不做打榜式发版”的立场。

工程化演示:线上 Case 挖掘与双 Loop 分流

落实:白皮书的四类挖掘来源搭配、随机采样不可省、两条 Loop 分流与错题集;第 7 章的”脱敏后回流为评估用例”。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def daily_case_mining(date):
cands = []
cands += feedback(date, thumbs_down=True, user_correction=True) # 线上反馈
cands += monitor_anomalies(date, metric="skill_fail_rate") # 监控异常
cands += rule_audit(date, rules=AUDIT_RULES) # 规则挖掘(如金额与工具返回不一致)
cands += random_sample(date, rate=0.005) # 随机采样,示例值;发现没想到的问题
for case in dedupe(cands):
case = redact(case, fields=PII_AND_SECRETS) # 先脱敏,再入 Case 池
if case.is_good: # 用户明确满意、专家确认
golden_set.add(case)
continue
att = attribute(case)
if att.route == "eval_loop": # 评测集没覆盖 / Rubric 判错
wrong_answer_set.add(case)
open_ticket("eval", att) # 改 Rubric、补用例
else: # Agent 本身的问题
open_ticket("agent", att)
regression_set.add(to_e2e_task(case))
regression_set.add(to_prefix_task(case, att.first_error))
publish_weekly_stats(by_source=True, by_category=True) # 观察用户结构变化

监控与评测要放在一起读,白皮书给了判别方法:完成率下降的同时某个 Skill 失败率暴涨,多半是工程问题;Skill 都正常、但某类新任务占比快速上升,则是用户结构变了,需要扩评测集。

7.2 观测是前提

四份资料都把 Trace 放在地基位置。模块十的要求最具体:结构化(JSONL/JSON)、内容完整(每步工具名、入参、返回值、时间戳)、格式稳定(版本间字段一致),并且要在设计阶段就纳入。第 7 章补了标准:Trace 沿用分布式系统的 span 树,可用 OpenTelemetry 加 OpenInference 语义约定,让采集与分析解耦。白皮书给了务实的起点:”最小可归因”——至少拿到完整的模型输入输出和 Skill 调用记录,能凭 Trace 复现一个 Bad Case;当发现”想自动判断某件事但数据里没有”时再补埋点。相关面试卡:[[Agent日志与Trace系统设计]]

工程化演示:最小可归因的 Trace 事件

落实:模块十的 Trace 三要求(结构化、完整、格式稳定),第 7 章的 span 树与提示词版本化,白皮书的”最小可归因”。

1
2
3
4
5
6
7
8
{"trace_id": "tr_20261023_0042", "span_id": "s7", "parent_id": "s1",
"kind": "tool", "name": "query_order",
"input": {"order_id": "A1029"},
"output_ref": "blob://traces/0042/s7.json",
"start": "2026-10-23T10:02:11.120Z", "end": "2026-10-23T10:02:11.480Z",
"error": null,
"attrs": {"agent_version": "v1.8.2", "model": "<pinned-model-id>",
"prompt_hash": "9f3c1e", "skill": "after_sale", "user_hash": "u_7ad2"}}
1
2
3
4
5
6
7
8
9
10
# 最小可归因清单:缺任何一项,都无法凭 Trace 复现一个 Bad Case
MIN_ATTRIBUTABLE = {
"llm": ["完整输入(含系统提示渲染结果或其 hash)", "完整输出", "模型 ID", "token 用量"],
"tool": ["工具名", "入参", "返回值或其引用", "错误", "起止时间"],
"run": ["agent_version", "tools_version", "环境/配置快照引用"],
}

def render_system_prompt(cfg):
text = TEMPLATE.render(**cfg.sorted_items()) # 第 7 章:同一配置永远渲染出同一文本
return text, sha256(text)[:6] # hash 写进每个 llm span,变更可追溯到提交

用户标识只存哈希,原文放在受控存储里,这样 Trace 才能放心地流向评测与归因(对应第 7 章的”隐私感知分析”)。

7.3 产品内建的评估基础设施

第 7 章以 OpenClaw 为例,描述了最成熟的一层——把评估嵌进产品工程:

  • 一个总开关禁用多个主要特性,得到”裸模型”基线,定期消融以发现”特性债务”(曾经有效、模型进化后已不必要的特性)。
  • AB 测试用多臂而非二元;区分机制指标与目标指标(例如缩短计划文件长度是机制,降低会话成本才是目标,缩短计划可能反而导致更多编辑循环);设护栏指标作为不能变差的底线。
  • 编译时与运行时双层特性开关,兼顾实验、渐进发布与紧急熔断。
  • 系统提示可确定性渲染并版本化,每次变更像代码一样跑评估集回归。

来源:[[深入理解AIAgent-第7章-Agent的评估]](文中称结合公开分析与从业者分享,原始出处待核实)

工程化演示:消融矩阵与 AB 指标分层

落实:第 7 章的总开关消融、特性债务、多臂 AB、机制指标 / 目标指标 / 护栏指标。

1
2
3
4
5
6
7
FEATURES = ["thinking", "context_compaction", "auto_memory", "background_tasks"]

def ablation_matrix(base_cfg, suite):
variants = {"bare": base_cfg.disable(*FEATURES)} # 裸模型基线
variants.update({f"-{f}": base_cfg.disable(f) for f in FEATURES})
# 每个变体都和完整版做配对比较;关掉后分数不降的特性,就是"特性债务"候选
return {name: compare(base_cfg, cfg, suite) for name, cfg in variants.items()}
1
2
3
4
5
6
7
8
9
# 一个多臂 AB 实验的指标声明(例:缩短计划文件长度)
experiment: plan-length-v3
arms: [control, short_plan_soft, short_plan_medium, short_plan_strict] # 多臂,看剂量效应
mechanism_metrics: [plan_tokens] # 我直接改变的东西,不是目标
goal_metrics: [session_cost_usd] # 我真正关心的东西,以它为准
guardrail_metrics: # 不能变差的底线,越线即停
- {metric: task_success_rate, max_drop: 0.01}
- {metric: edit_check_edit_loops, max_increase: 0.05}
exposure_log: once_per_session # 每会话最多记一次曝光,避免污染

7.4 从报告到改进:一个最小示范

第 7 章的 AndroidWorld 案例是”评估驱动迭代”的范本。总分 88% 下藏着一个小而集中的失败簇(4 个 Wi-Fi 任务失败 3 个)。三轮实验每轮只改一个变量:H1 加导航提示,成功率不变,说明问题不在提示词;H5 换成 UIAutomator 元素树,25% → 100%,但 token 约 2.5 倍;H5C 删掉无语义容器节点,成功率不变、token 降到对照组约一半。结论有两层:提示写得再详细,也补不回 Agent 根本没看到的信息;输入也不是越多越好。作者特别强调,4/4 只说明值得进入 116 任务 × 5 种子的完整复测,不能写成”系统 100%”。

八、四份资料的分歧与张力

以下几处不是谁对谁错,而是适用条件不同,使用时要注意:

  1. 基线快照 vs 可接受动作集合。模块十用”跑一次确认的快照”做预期,并用 LCS 对齐步骤序列扣分;第 7 章认为轨迹前缀任务的答案应是可接受动作集合,而不是唯一路径。前者实现简单、适合流程固定的 Agent;对路径开放的长程 Agent,按单一基线扣步骤分可能误伤合理的替代路径。待用真实项目验证。
  2. “Pass@k”一词的用法。白皮书说”对每个 Task 运行多次试验,也就是我们常说的 Pass@k,用通过率作为结论”,更接近”多次试验的平均通过率”;第 7 章与模块十把 Pass@k 严格定义为”至少一次通过”。引用时要说明口径。
  3. 离线分数是否作为发版门槛。模块十要求回归集通过率接近 100%,并引了一条”确定性任务 100%、非确定性 ≥95%”的 Anthropic 参考标准(出处未核实);白皮书 FAQ 则认为垂域 Agent 不必像基座模型那样”必须比上次分高才发版”,离线评测主要做回测,真实好坏看线上 AB 的业务指标。二者可以兼容:回归集是守底线的门禁,是否”更好”由线上判断。
  4. “测评”与”监控”的边界。模块十把测评定义为离线、可控、可重复,把线上归为监控;白皮书单列了”在线评测”(AB、影子、巡检)并把它和监控区分开。术语不统一,白皮书自己也承认行业划分各异。
  5. 人评与机评的比重。漫谈认为长程 Agent 场景会从人评主导走向机评主导,人负责标准与 Rubric 对齐;第 7 章认为失败归因可以借助 LLM,但不能完全依赖,因为归因常反映产品问题。二者都同意人的位置是”定标准、做校准”,分歧在归因环节的自动化程度。

九、落地路线:冷启动的最小闭环

综合白皮书”不要照着全景图从头建”与漫谈”先让飞轮转起来”的建议,结合模块十的模板与第 7 章的方法,一个从零开始的团队可以按下面的顺序推进:

  1. 先打 Trace,做到最小可归因:完整的模型输入输出与工具 / Skill 调用记录,能复现任意一个 Bad Case。
  2. 只选高频核心场景,定义少量关键指标;按四场景写 20–50 个种子用例,含负向触发;人工写少量、AI 辅助扩写。
  3. 评分先确定性、后 Rubric:能断言的写成断言;剩下的维度下钻成二元 Rubric,抽样和人工对齐,一致率达到约 85% 再规模化。安全与数据准确性设一票否决。
  4. 每个用例跑多次,同时报 Pass@1 与 Pass^k;比较两个版本用配对分析,分差小于噪声不下结论。
  5. 接入 CI/发布流程,回归集只加不减。
  6. 上线后建回流:线上纠正、点踩、规则审计发现的问题 → 归因到首个错误 → 判断是 Agent 错还是 Rubric 错 → 修复并写成端到端或轨迹前缀回归任务。
  7. 扩量阶段再补:过程评测集分层、在线巡检、成本监控、多源评委审计、特性消融。

工程化演示:评测仓库的最小目录结构

把上面各段演示放在一起,一个冷启动阶段够用的评测仓库大致如下,按需扩展,不必一次建全。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
eval/
├── suites/
│ ├── e2e/<功能模块>/*.yaml # 端到端集(用户视角,按功能模块分)
│ ├── process/<skill|kb>/*.yaml # 过程集(警报响了找谁),出问题再细拆
│ ├── prefix/*.yaml # 轨迹前缀回归
│ ├── capability/*.yaml # 爬坡集,稳定满分后"毕业"到 regression
│ └── veto/*.yaml # 安全 / 数据准确性一票否决集
├── fixtures/ # 初始状态与重置脚本
├── rubrics/ # 二元 Rubric,含边界示例
├── calibration/ # 人工标注样本与一致率报告
├── graders/ deterministic.py judge.py pairwise.py
├── runner/ sandbox.py user_sim.py tracer.py
├── analysis/ metrics.py compare.py attribution.py taxonomy.yaml
├── mining/ daily_case_mining.py redact.py
├── baselines/ # 各分支 / 模型版本的基线报告,随仓库版本化
└── ci/ gates.py

十、面试速答版

  • Agent 评测和模型评测有何不同? 评的是模型与 Harness、工具、流程的组合;不只看答案,还看过程、效率、风险;目标是稳定交付而非打榜。
  • Pass@k 和 Pass^k? 至少一次成功 vs 每次都成功;前者看上限,后者看可靠性,生产看后者;p=0.6、k=5 时约 99% vs 7.8%。
  • LLM-as-a-Judge 怎么可信? 结构化 Rubric(最好二元化)+ 固定模型版本 + JSON 输出 + 人工校准一致率 ≥85%;防长度、位置、同源偏差。
  • 分数下降先做什么? 先查评测系统(环境、验证器、用例),再看 Trace 定位首个错误,判断是 Agent 错还是 Rubric 错。
  • 如何证明效果提升? 固定环境与评测集,配对比较、多种子、超过噪声并可复现;最终看线上 AB 的业务指标。见 [[如何证明Agent效果提升]]。

关联

  • 概念:[[Agent评测]]、[[Agent-Harness]]、[[上下文工程]]
  • 面试卡:[[Agent自动评测体系]]、[[Agent-Badcase定位]]、[[如何证明Agent效果提升]]、[[用Agent评测Agent的循环风险]]、[[Tool调用准确率评估]]、[[没有用户反馈如何评估Agent]]
  • 本库自身也在用同样的思路:封存问题集相当于固定评测集,见 [[知识库迭代机制方案]]