Agent 评测与可观测
单轮 LLM 应用评「回答对不对」,Agent 要评一整条轨迹:想了什么、调了什么工具、结果怎么用、最终产物成不成、花了多少钱。这是 Agent 上线最难也最必需的一环——没有它,「偶尔很聪明、经常很抽风」的 Agent 无法迭代。本页给出轨迹级评测方法论(评测集/验证器/judge)、可观测设计(trace/指标/失败分析)与「评测-可观测-门禁」闭环;评测的技术底座与单步概念见 RAG 与 Agent、Prompt 工程、AI 工程化。
一、为什么 Agent 的评测是「新问题」
| 维度 | 单轮问答评测 | Agent 评测 |
|---|---|---|
| 评测对象 | 一段回答文本 | 轨迹(推理 + 工具调用序列 + 外部副作用) + 终态产物 |
| 确定性 | 低随机性 | 采样随机 + 工具时序随机,同任务两次跑结果可能不同 |
| 答案空间 | 可预设金标 | 任务开放,无法穷举「正确答案」 |
| 状态副作用 | 无(纯生成) | 会写库/发信/改文件,不可随意重放 |
| 关键指标 | 正确性 | 正确性之外还有:步数效率、成本、安全合规 |
- 结论:Agent 评测 = 结果评测(终态对不对)+ 过程评测(轨迹好不好)+ 副作用评测(环境被改对了吗),三者缺一不可。
二、评测指标的分层
结果层:任务到底成了没有
| 指标 | 含义 | 备注 |
|---|---|---|
| 任务成功率 / pass@1 | 一次运行完成且产物通过验证 | 最基本指标 |
| pass@k | k 次独立运行至少成功一次 | 衡量「上限能力」,与成本相关 |
| 部分成功(partial) | 完成部分子步骤 | 需要子任务拆解定义 |
| 产物验证通过率 | 终态产物被程序化检查判定正确 | 优先用验证器,别听模型自评 |
过程层:轨迹质量(决定可维护性)
- 工具选择正确率:该调
A时有没有调A; - 无效/冗余调用率:幻觉工具名、重复调同一工具、无用探测;
- 步数效率:达到同等结果的平均步数(越少越好,直接关联成本);
- 计划合理性:计划与实际执行的偏差、是否频繁返工(再规划次数);
- 约束违反率:是否做了任务明确禁止的动作(如「只读任务」里去改了数据)。
安全与健壮层
- 注入/越狱逃逸率(工具描述被恶意内容污染时是否会越权,方法见 Prompt 工程 安全节);
- 失败恢复率:工具报错后能否正确换路,而不是原地重试到耗尽;
- 越权动作拦截率:权限墙外动作是否都被拦下(见 AI 工程化 权限墙)。
效率层
- 每任务成本(token 用量)、每任务耗时、预算达成率——Agent 的 token 账单是单轮的 N 倍,成本必须进评测指标(计量方法见 AI 工程化 成本节)。
三、评测集怎么搭:让结果「可自动判定」
一句话原则:能用确定性验证器判定的,不要用 LLM judge。
- 第一步,把任务模板化:
text
任务模板:给定仓库 {repo} 与需求 {req},产出满足 {tests} 的补丁
实例化 1:repo=mini-calc, req=支持负数相加, tests=[calculator_test.py]
实例化 2:repo=mini-calc, req=输出改为 JSON, tests=[calculator_test.py]模板 + 参数实例化 → 一套模板生成几十个任务,保覆盖且可扩展;
- 第二步,为每类任务定义验证器(verifier),验证器优先检查「环境被改成了什么」而非「模型说了什么」:
| 任务类型 | 验证器示例(断言对象是产物/环境) |
|---|---|
| 代码修改 | 跑测试套件通过 |
| 数据操作 | 数据库事务回滚后检查目标行数据变化 |
| 发消息/通知 | 注入 mock 邮件/消息网关,断言发送对象与内容 |
| 写文件 | 断言输出文件存在、内容满足规则 |
- 第三步,状态重置与沙箱(可重复的前提):
- 每个任务用干净的临时环境:数据库事务包裹回滚、网络 API 打桩、临时目录隔离;
- 同一任务跑 N 次(测 pass@k / 测稳定性)时必须每次从同一起点开始;
- 第四步,准备参考轨迹(golden trajectory):人工或人工修正的「最优路径」,用于过程评测与对 judge 的校准;
- 第五步,分集管理:冒烟集(十几个,每次改动秒级回归)→ 回归集(覆盖主要能力,发布门禁用)→ 扩展集(持续从线上失败案例回流,见第七节)。
四、LLM-as-judge:只有没法规则化时才用
- 适用的地方:开放式任务的结果质量(方案好坏)、轨迹的过程评价(步骤是否合理)、人类偏好对齐;
- 关键:judge 只看「判定所需的证据」——给完整轨迹会太长且引入噪声;先抽取证据(终态产物 + 关键工具调用 + 成本/步数)再判;
text
# 结果级 judge prompt 骨架
任务:{task}
参考验收标准:{rubric 里的可判定条件}
Agent 最终产物:{product}
请逐条按 rubric 给出 通过/不通过 与一句话理由。
注意:只依据给出的产物文本判断,不要脑补产物没有的内容。
# 过程级 judge prompt 骨架(可选)
轨迹摘要:{agent 的关键动作序列,已脱敏}
判定维度:工具选择是否合理 / 是否有效推进 / 是否违反约束
输出:每条维度 pass/fail + 证据行号- Rubric 化:先写「什么算对」的可判定条件(如:包含编号引用、金额格式正确、调用了且只调用了该调的工具),避免 judge 凭感觉;
- 双判与校准:两个不同 prompt/模型判不一致时人工复核;定期用已知样本测 judge 自身的稳定性;
- judge 陷阱:偏好更长更花哨的输出、对自我生成轨迹有偏、容忍模型「复述正确结论但过程瞎编」。
4.1 可运行的 judge 示例(deepseek-chat, 2026-09-04)
把上面的 prompt 骨架落成可运行脚本并实测:4 个构造样例(1 合格 / 3 各有缺陷),judge 对 rubric 逐条判 pass/fail 并给一句话理由,与人工期望 4/4 一致。执行:
export DEEPSEEK_API_KEY=sk-... && python3 本文件。
python
# LLM-as-judge 最小实现 —— 真实可跑版(DeepSeek deepseek-chat)
# 依赖: python3 + requests; 环境变量 DEEPSEEK_API_KEY
import os, json, requests
API = "https://api.deepseek.com/chat/completions"
MODEL = os.environ.get("DEEPSEEK_MODEL", "deepseek-chat")
TASK = "给客户写一封邮件, 解释上月账单多收 58 元是系统汇率四舍五入所致, 并为造成困扰致歉。"
RUBRIC = [
"解释了多收 58 元的具体原因(汇率/四舍五入)",
"对造成的困扰表达了道歉",
"给出了明确的下一步行动(如自动抵扣/退款/联系支持)",
"金额一致(58 元), 未出现与事实矛盾的数字",
]
def judge(product):
content = ("你是质检评测员。任务:\n" + TASK +
"\n验收标准(rubric, 请逐条判定 通过/不通过):\n" + json.dumps(RUBRIC, ensure_ascii=False) +
"\nAgent 最终产物:\n" + product +
"\n只依据上面产物文本判断, 不要脑补产物没有的内容。输出 JSON(不要其它文字):\n"
'{"items":[{"no":1,"pass":true,"reason":"一句话理由"},...],"verdict":"pass"}'
' 其中 items 与 rubric 顺序一一对应, verdict 为 "pass"(全部通过)或 "fail"(任一不通过)。')
r = requests.post(API,
headers={"Authorization": "Bearer " + os.environ["DEEPSEEK_API_KEY"]},
json={"model": MODEL,
"messages": [{"role": "user", "content": content}],
"response_format": {"type": "json_object"}, "stream": False}, timeout=60)
r.raise_for_status()
body = r.json()
return json.loads(body["choices"][0]["message"]["content"]), body["usage"]["total_tokens"]
# (name, 产物, 人工期望): 三个缺陷样例各自只漏/错一个要素
CASES = [
("合格样例", "尊敬的客户:经核查,多收的58元系汇率四舍五入所致。给您带来困扰深表歉意,该款3个工作日内自动退回,如有疑问拨打400-000。", "pass"),
("未解释原因", "尊敬的客户:非常抱歉给您带来困扰,我们已记录您的反馈并会尽快处理,感谢理解。", "fail"),
("无致歉无下一步", "经查,系统在汇率换算环节存在四舍五入误差,导致本次账单金额偏高。", "fail"),
("金额写错", "您好!关于多收的50元,是系统汇率四舍五入造成的,为此致歉,下月账单自动抵扣。", "fail"),
]
if __name__ == "__main__":
assert os.environ.get("DEEPSEEK_API_KEY"), "请先 export DEEPSEEK_API_KEY=sk-..."
agree = 0
for name, product, expected in CASES:
j, toks = judge(product)
v = j.get("verdict", "?")
# 容错: json_object 只保证合法 JSON, 字段名可能换词(no→?, reason→explanation...)
rows = []
for it in j.get("items", j.get("rubric", [])):
ok = it.get("pass", it.get("passed", it.get("result") == "pass"))
rows.append(("✓" if ok else "✗") + " " + str(it.get("reason", it.get("explanation", ""))))
agree += (v == expected)
print(f"== {name} | judge={v} 期望={expected} {'一致' if v == expected else '不一致'} | tokens={toks}")
for r in rows:
print(" ", r)
print(f"一致性: {agree}/{len(CASES)}")
# ---- 实测输出(2026-09-04, deepseek-chat) ----
# 合格样例 judge=pass 期望=pass (四条 rubric 全 ✓, tokens≈384)
# 未解释原因 judge=fail 期望=fail (✗未解释原因; ✓道歉; ✗无下一步行动)
# 无致歉无下一步 judge=fail 期望=fail (✓解释; ✗无道歉; ✗无下一步)
# 金额写错 judge=fail 期望=fail (✗写 50 元而非 58 元 → 金额矛盾)
# 一致性: 4/4- 实测观察:①json 模式只保证「合法 JSON」不保证字段命名,judge 的解析必须容错,别假设
no/reason/verdict一定原样返回;②rubric 里把「金额写错」这种机器可判的硬条件放进判定清单很有效——它等价于给 judge 一把「事实核查尺」,比让 judge 凭感觉打质量分稳得多;③正式使用时仍需双判 + 已知样本校准(见上文),单次样例的 4/4 只能说明构造的用例区分度高,不能当作 judge 稳定性的结论。
五、面向 Agent 的基准与工具(了解现状即可)
- 用公开基准看自己 Agent 的「段位」,但别刷榜当 KPI——基准环境 ≠ 你的生产环境:
| 基准 | 评测什么 | 你的 Agent 是否适用 |
|---|---|---|
| GAIA | 通用助手任务的难度分级 | 通用任务粗定位 |
| AgentBench | 多环境(网页/代码/游戏等)通用智能体 | 跨域粗定位 |
| tau-bench(τ-bench) | 真实业务场景的工具操作(客服/零售) | 贴近工具型 Agent,可借鉴其「用户模拟器」设计 |
| SWE-bench | 真实 GitHub issue → 代码补丁 | 代码 Agent 必看 |
| WebArena / Mind2Web | 网页操作与浏览 | 浏览器 Agent 参考 |
基准榜单排名会持续变动,选基准前先查其最新维护状态与提交方式;最重要的是借鉴它们的评测设计(任务构造、验证器、重置策略),而不是照搬榜单。
- 评测/追踪工具的形态:市面上有轨迹追踪 + 评测集成平台(如 LangSmith、Langfuse 等),概念上一律等价于「记录 trace + 跑 verifier/judge + 出报表」——先用 OpenTelemetry GenAI 语义约定 打底避免被平台锁定。
六、可观测:让「看不见的轨迹」可见
6.1 只记「一层调用」是不够的
- 单步日志能看出「这步调了哪个模型」,看不出「这次任务为什么绕了 30 步」;
- Agent 必须有任务级 trace:从任务开始到结束,串起规划、每一步 LLM 决策、每一次工具调用的因果关系与时间线。
6.2 事件模型与 span 设计
| Span 类型 | 记录什么 | 关键字段 |
|---|---|---|
| task span | 一次任务全生命周期 | task_id、目标、终止原因、耗时、成本 |
| plan span | 计划生成/再规划 | 计划内容、再规划次数、触发原因 |
| llm span | 每次模型决策 | 模型、输入摘要、输出、token 用量 |
| tool span | 每次工具调用 | 工具名、参数摘要与结果摘要(截断),成功/失败、耗时 |
- 记录策略:
- 脱敏先行:prompt、工具参数/结果可能含 PII——记录前按字段脱敏 + 截断,详情只留摘要(见 AI 工程化 日志策略);
- 因果关系:用 span 父子关系把「这次思考 → 那次调用 → 结果回填」串成树,这是复盘的最小单元;
- 成本累计:在 task span 上聚合 token 与金额,一眼看出「哪个任务烧钱」。
6.3 指标与日志
- 聚合指标:成功率、平均步数与步数分布、工具错误率 Top N、成本/任务、超时率、预算耗尽率(终止原因分布);
- 关键事件日志:任务起点与目标、终止原因(validated / validation_failed / step_budget_exhausted / tool_error_max / human_abort / refused)、产物哈希——「终止原因分布」是 Agent 健康度的第一仪表盘。
6.4 失败分析工作流
- 从线上 trace 捞失败样本,按失败类型聚类(超步数 / 验证不过 / 工具错 / 越权拦截);
- 对代表性样本重放 trace,定位断点:是目标理解错、工具没选对、结果没用好、还是验证器太严;
- 把典型失败样本回流到评测集(第七节),修一次防一类;
- 定期人工抽样复核 trace,防「指标在涨、体验没涨」——评测与真实用户感受的偏差只能靠抽查校准。
七、评测-可观测-门禁:上线闭环
开发期:冒烟集/回归集 + verifier/judge
↓ 通过
发布期:canary 小流量 → 对比成功率/成本/终止原因 → 逐步放量
↓ 持续
运行期:线上 trace + 指标监控 → 失败聚类
↓ 回流
失败样本进回归集 → 下次改动必须先过它
↓
模型升级 / prompt 变更 / 新增工具 / 框架升级 —— 全部重跑回归集,过不了不让上线- 门禁要覆盖的四类改动:换模型、改系统提示/工具描述、加删工具、升框架——任何一类都可能静默改变轨迹行为;
- 线上漂移:任务分布会变,定期用线上真实样本重标注扩充评测集(见 AI 工程化 评测门禁);
- 成本护栏与门禁联动:每任务成本预算当二级门禁——质量过了但烧钱翻倍的改动一样要打回。
八、踩坑清单
- 只评终态不评过程:结果对但乱调 50 个工具的 Agent,上线后维护与账单都会爆;
- 验证器缺位,全上 judge:没有确定性断言,评测结果跟模型抽风一样随机;
- 任务不可重放:评测时污染了共享状态,同任务两次结果没法比;
- 判「完成」听模型自评:Agent 说完成就记成功——必须有产物级验证;
- judge 不给证据只丢全轨迹:判不准又费 token;先抽证据、写 rubric;
- 没有冒烟集:每次改 prompt 全量跑回归,迭代慢到放弃评测;
- 可观测只到单步:出了问题无法重放整条轨迹,等于没有日志;
- trace 裸存敏感数据:工具参数/用户文本直接落盘,合规事故;
- 终止原因不统计:只报成功率,不知道失败是「预算耗尽」还是「验证不过」,无从下手;
- 失败样本不回流:同一个坑反复踩,评测集和线上真实分布渐行渐远;
- 拿基准榜单当上线标准:SWE-bench 高分不等于你的业务任务好——评测集永远要贴合自己的任务分布。
关联阅读:Agent 怎么设计才能被评测、被救回,见 Agent 架构通论;单轮答案评测与 LLM-as-judge 基础见 Prompt 工程;评测门禁与可观测基建(OTel GenAI、成本计量、权限审计)见 AI 工程化;结果用 RLHF/DPO 等反馈信号进模型本身的路线见 LLM 原理 对齐节与 模型微调。