Agent评测·三层质量门 2026.10.02
你的 Agent demo 跑得飞起,上线三天被 dealer 骂到退车——因为「只看最终答案」的评测,根本测不出工具选错、推理断裂、多轮忘记约束这三件要命的事。本期把 Motorway × AWS 的生产评测蓝图拆成一套三天能跑通的质量门。
为什么是现在
- 你的客服 Agent 单次回答「看起来对」,但 10 次里有 1 次选错工具、把退款金额算错——你靠人工抽查永远发现不了。
- 你的搜索 Agent 在单轮测试里 90% 正确,可真实用户连问三轮后,它把第一句说的「只要红色」忘得一干二净。
- τ-bench 早就量过:benchmark 上 pass@1 有 60% 的 Agent,跨多次重复试验的一致性可能只有 25%。单次测试通过 ≠ 生产里可靠。
读完你能做到:用开源库 strands-agents-evals 把「最终答案对不对」拆成工具选择 / 推理连贯 / 输出质量三层独立判分,再用 pass^k(连跑 k 次全过) 替代 pass@1 度量稳定性,并把它接进 CI 当质量门——不达阈值直接拦下发布。
四要素卡(先拿结论)
| |
|---|
| 场景 | 生产环境里上线工具调用型 Agent(经销商库存搜索、客服退改签、技术支持排障);要在真实流量下量化「选对工具 + 推理连贯 + 答案正确」三件事,并防回归、防随机性。 |
| 条件 | Python≥3.10;被评 agent 可调用(Strands / LangChain / CrewAI / 自研皆可,只需能给定 input 吐出 output + trajectory);工具 schema 已知可写确定性 grader;可选 LLM-judge(Anthropic / OpenAI / Bedrock 兼容);≥ 几十个代表性 case(含多轮);CI 能跑 strands-evals。 |
| 方法 | 一句话——把「只看最终答案」拆成三层独立判分(L1 工具选择/参数确定性 grader >95%;L2 推理连贯 LLM-judge rubric >85%;L3 输出质量 LLM-judge + 人审 >90%),并用 pass^k 替代 pass@1 度量稳定性;三层分 + pass^k 设成 CI 质量门,不达标禁发布。 |
| 效果 | 业务收益(Motorway 真实拍卖业务):错误结果 1/8 → 1/50、工具选择准确率 87% → 98%、任务完成率 96%、多轮上下文保留 94%、月事故 12 → 2、问题定位 hours → minutes。技术指标:pass@1=90% 仅等价于 pass^3≈73%(0.9³);τ-bench 60% pass@1 → 25% 一致性。代价:LLM-judge 自身有方差需人审校准;多轮 case 构造 + k 次重跑抬升评测成本(API / 算力)。 |
采用方:Motorway(英国二手车拍卖平台,8,000 经销商 / 2,500 车日拍,日均真实资金流)已落地;AWS 将其写成生产蓝图并开源 strands-agents-evals;Bedrock AgentCore Evaluations 提供线上监控。
一、技术背景与问题定义
现有做法卡在哪
绝大多数团队评 Agent 的方式,和评语言模型一样:跑几个任务,看最终输出,假设它 work。这在 Agent 上会系统性漏掉三类失败:
- 选对工具参数也对,但最终答案似是而非——lexer 没验证事实;
- 单次成功、三次里挂一次——非确定性让单轮测试形同赌博。
Motorway 的经销商库存搜索 Agent 就是典型:demo 全过,上线后每 8 个查询就有 1 个错误结果,对拿搜索结果做采购决策的经销商来说是信任崩塌。根因不是模型笨,是评测只看了最终答案这一层。
本文对象的核心主张
AWS × Motorway 给出的解法是一套三层质量门(Three-Layer Quality Gates)+ pass^k 一致性度量:
- 把「答案对不对」拆成三个彼此独立、可单独失败的判分层;
- 每层用最便宜且最可信的判分器(能确定的用代码,不确定的用 LLM-judge,主观的加人审);
- 用 pass^k 把「单次通过」换算成「连续 k 次全过」,直接当发布红线。
它落地在开源库 strands-agents-evals(github.com/strands-agents/evals,PyPI 同名,Apache-2.0 友好),框架无关——LangChain / CrewAI / 自研都能用;纯本地跑评测不依赖 AWS,AgentCore 只是可选的线上监控。
读者画像与采用方
- 算法 / 评测负责人:要把「Agent 靠不靠谱」变成可拦发布的数字;
- 应用开发者:在上线前用 CI 挡住工具选错、上下文丢失类回归;
- 真实采用方:Motorway 生产落地;AWS 官方生产蓝图;Bedrock AgentCore 线上评估。
二、实现路径与步骤【本期重点,照抄落地】
目标:三天内跑通「三层判分 + pass^k + CI 门」最小闭环。以下用 strands-agents-evals 实跑,judge 用 Anthropic 兼容端点(换 OpenAI / 本地 vLLM 同理)。
步骤 1:环境与依赖
# 1) Python 3.10+ 虚拟环境python -m venv .venv && source .venv/bin/activate # Windows: .venv\Scripts\activate# 2) 装评测库(同时装出 strands-evals CLI)pip install strands-agents-evals# 3) 被评 Agent 用的 SDK(以 Strands 为例;LangChain 自行替换)pip install strands-agents# 4) 配置 judge 模型(环境变量,二选一)export ANTHROPIC_API_KEY=sk-...# 或走本地 vLLM:export str依据框架,judge model 指到 openai 兼容 base_url
- 验证成功:
strands-evals --help 打出 5 个子命令(run / validate / report / diagnose / generate)即装好。 - 耗时与成本:装包 < 5 分钟;评测本身零 GPU,只烧 judge 模型的 API(见步骤 6)。
步骤 2:构造 case 集(评测数据)
case 要覆盖单轮 + 多轮,且每层都要能判:L1 需要「期望工具 + 期望参数键」,L3 需要「期望答案」。
# cases.pyfrom strands_evals import Casecases = [# 单轮:工具选择 + 参数可确定性校验 Case[str, str]( name="dealer_search_color",input="只给我红色、预算 2 万英镑以内的车", expected_output="返回红色且 ≤20000 英镑的车列表", metadata={"layer": "tool_use", "expected_tool": "search_inventory","expected_args": {"color": "red", "max_budget_gbp": 20000}}, ),# 多轮:第二句收紧约束,测上下文保留 Case[str, str]( name="dealer_search_refine",input="先给我 2020 年后的电动车;啊不对,把年份放宽到 2018", expected_output="返回 2018 年后电动车,不含 2020 限制", metadata={"layer": "multi_turn", "expected_tool": "search_inventory","expected_args": {"min_year": 2018, "fuel": "electric"}}, ),]
- 数据来源:从生产日志里抽真实 query(优先踩过坑的);多轮 case 用「用户中途改口」覆盖 filter-accumulation 失败。
- 验证成功:
strands-evals validate cases.json(先序列化)能过 schema 校验。
步骤 3:三层 evaluator(核心判分)
# evaluators.pyfrom strands_evals.evaluators import OutputEvaluator, TrajectoryEvaluatorfrom strands_evals.extractors import tools_use_extractor# ---------- Layer 1:工具选择/参数 —— 确定性代码 grader(不靠 LLM) ----------deftool_usage_grader(agent_messages, expected_tool, expected_args): traj = tools_use_extractor.extract_agent_tools_used_from_messages(agent_messages) used = {t["name"]: t.get("args", {}) for t in traj}if expected_tool notin used:return0.0, f"未调用期望工具 {expected_tool},实际={list(used)}" got = used[expected_tool] missing = [k for k in expected_args if got.get(k) != expected_args[k]]if missing:return0.0, f"参数不符:{missing}"return1.0, "工具与参数均正确"# ---------- Layer 2:推理连贯 —— LLM-judge(主观,用 rubric) ----------reason_evaluator = OutputEvaluator( rubric="评估 Agent 中间步骤是否连贯、是否支撑最终结论。""1.0=推理链清晰自洽;0.5=部分跳跃;0.0=前后矛盾或瞎编步骤。", model="us.anthropic.claude-sonnet-4-20250514-v1:0", include_inputs=True,)# ---------- Layer 3:输出质量 —— LLM-judge + 人审校准 ----------quality_evaluator = OutputEvaluator( rubric="最终回答是否准确、完整、回答了用户问题。""1.0=准确完整;0.5=部分正确;0.0=错误或答非所问。", model="us.anthropic.claude-sonnet-4-20250514-v1:0",)
- 关键参数与取值理由:
- L1 用代码 grader而非 LLM——工具选择客观可验证,LLM 在这里只会增加方差和成本;
- L2/L3 的
model 选小一点的 judge(sonnet 级够用),不要拿被测模型当 judge(自赞偏差); include_inputs=True 让 judge 看到上下文,避免断章取义。
- 验证成功:单 case 跑通,report 里 L1 给 0/1、L2/L3 给 0~1 分数与 reason。
步骤 4:跑 Experiment(Python API)
# run_eval.pyfrom strands import Agentfrom strands_tools import search_inventory # 你的工具from strands_evals import Experimentfrom cases import casesfrom evaluators import tool_usage_grader, reason_evaluator, quality_evaluatordefget_response(case): agent = Agent(tools=[search_inventory]) out = str(agent(case.input))# L1 需要 trajectory,从 agent.messages 抽 score, reason = tool_usage_grader( agent.messages,case.metadata["expected_tool"],case.metadata["expected_args"], )return {"output": out, "_l1": (score, reason)}experiment = Experiment[str, str]( cases=cases, evaluators=[reason_evaluator, quality_evaluator], # L1 在 get_response 内联算)report = experiment.run_evaluations(get_response)report.run_display()
CLI 等价写法(CI 友好):
strands-evals validate cases.jsonstrands-evals run cases.json --agent my_pkg.agents:build_agent --display
- 验证成功:终端 Rich 表打印每层 overall_score;L2/L3 < 阈值时红旗标出。
步骤 5:pass^k 一致性(接 agent_eval_passk.py)
把每个 case 连跑 k 次的 pass/fail 落 CSV,再算 pass^k:
# 同一 case 跑 5 次(temperature>0),记 pass/failpython - <<'PY'import csv# 伪代码:对 case 循环 5 次,调 get_response,写 results.csv# 列:trial_id, case_id, pass(0/1)PYpython agent_eval_passk.py results.csv 3
内置演示直接看效果:
python agent_eval_passk.py# case pass@1 pass^3# search-1 0.667 0.000 <- 三次必挂一次,单轮测试漏判# search-2 1.000 1.000# search-3 0.667 0.000
- 为什么 k=3:Motorway 把 pass^3>80% 当高危路径红线;高 stakes 业务可上 k=5。
- 验证成功:pass^3 显著低于 pass@1 的 case 被标红,进入修复队列。
步骤 6:CI 质量门
# .github/workflows/agent-eval.yml 片段-name:Agent三层质量门run:| strands-evals validate cases.json strands-evals run cases.json --agent my_pkg.agents:build_agent \ --fail-on "layer1<0.95,layer2<0.85,layer3<0.90" \ --display python agent_eval_passk.py results.csv 3 # pass^3<0.80 视为失败
- 阈值(Motorway 实测可达成):L1 工具 >95%、L2 推理 >85%、L3 输出 >90%、pass^3 >80%、多轮上下文保留 >90%。
- 验证成功:任一红线未达,CI 非零退出,发布被拦。
关键超参表
常见报错与规避
strands-evals: command not found → 装在 venv 里但 CI 没 activate;用 python -m strands_evals.cli 或显式 activate。- L1 抽不到 trajectory → 工具调用需走
agent.messages;自研框架请实现 tools_use_extractor 等价抽取,否则 L1 退化成 LLM-judge,失去确定性。 - LLM-judge 两次结果不一致 → judge 自身非确定;固定 judge
temperature=0 + 人审校准,别把 judge 分当绝对真理。 - pass^k 全 1 但线上仍翻车 → case 集不代表真实流量(clean prompt 偏多);补对抗/残缺/冲突输入,按 ZDNet 建议每工作流 ≥100 条。
- CI 超时 → k 次重跑 × case 数 × judge 调用;先对小子集跑 pass^k,全量留夜间。
跑起来要花多少
- 算力:零 GPU(judge 走 API;自托管 vLLM 另算)。
- API 费用:L2/L3 每 case 1
2 次 judge 调用;50 case × k=3 ≈ 300 次调用,sonnet 级约 **$0.52/轮**(远低于全量 benchmark 横扫)。 - 数据量:最小闭环 30~50 条代表性 case(含 10 条多轮)即可挡住大头回归;生产建议每工作流 ≥100 条。
- 时间:首次搭建 1 天,日常 CI < 15 分钟出报告(Motorway 把问题定位压到 minutes 级)。
三、原理讲解:为什么三层 + pass^k 有效
1. 三层解耦各自失败面。 最终答案正确 ≠ 过程正确。三层把「选工具 / 想清楚 / 答得对」拆成独立随机变量,单层失效不互相掩盖:
final_correct = tool_correct ∧ reasoning_ok ∧ output_correctP(final_correct) ≤ min(P(tool), P(reason), P(output))
所以只报总分,会把「工具选错但答案蒙对」的 Agent 误判为健康。分层后任何一层掉档都单独亮红灯。
2. 确定性 grader 优先。 L1 工具选择是客观事实(调了没、参数对没),用代码判分方差=0、成本≈0;把 LLM-judge 留给真正主观的 L2/L3,是用最低成本买最高可信度的分配。
3. pass^k 把随机性显式化。 Agent 非确定,单次成功是伯努利试验:
pass^k = P(连续 k 次全过) ≈ (pass@1)^k # 独立假设pass^3 | pass@1=0.9 = 0.729 # 单次 90% → 三次仅 73%
单轮测试用 pass@1 当可靠性,等于用 (pass@1)^k 的乐观上限骗自己。pass^k 直接给出「用户连续 k 次都拿到正确结果」的概率——这才是生产该守的数字。
4. 工程取舍(效果 vs 成本 vs 稳定性)。 三层 + pass^k 多花的是 judge API 与 k 倍重跑;换回的是「上线前拦住 1/8 错误率」的确定性。Motorway 数据证明这笔账划算:错误率砍到 1/50、月事故 12→2。代价是 LLM-judge 方差需人审校准,且 case 集必须代表真实流量,否则门禁只是心理安慰。
不为炫技:上面 4 点就是复现和调参要记住的全部。公式控制在 15 行内,多一个都不用背。
四、效果验证与适用边界
量化结果(方法 | 环境/数据 | 指标 | 基线 | 本文结果 | 来源)
| | | | | |
|---|
| | | | 1/50 | |
| | | | 98% | |
| | | | 96% | |
| | | | 94% | |
| | | | 2 | |
| | | | 0.73 | |
| | | | ~25% | |
| | | | 可复现 | github.com/strands-agents/evals |
业务收益单列(哪个环节省了什么)
- 信任环节:经销商搜索错误率 1/8 → 1/50,直接止住 dealer 流失(拍卖平台日均真实资金流)。
- 运维环节:问题定位 hours → minutes,月事故 12 → 2,on-call 成本骤降。
- 发布环节:CI 质量门拦下工具选错 / 上下文丢失类回归,避免「demo 过、生产翻」。
- 口径:上述为 Motorway 公开生产数据;你的业务需按自身流量重测,数字不直接平移。
决策清单(什么条件下成立 / 失效)
- 成立:工具 schema 已知、能构造代表性 case(含多轮)、CI 能跑评测、业务容不得「偶尔错」。
- 失效 / 退化:
- case 集只覆盖 clean prompt → 门禁虚高,上线仍翻(补对抗/残缺输入);
- 把被测模型当 judge → 自赞偏差,分层分失真;
- 只看 pass@1 不设 pass^k → 随机性被掩盖,高 stakes 业务踩雷;
- L1 没有确定性 grader(工具未知)→ 退化成全 LLM-judge,成本与方差双升。
- 换业务 / 模型要重验的前提:工具集变更、prompt 大改、模型升级、流量季节性漂移——每次都要重跑旧 case 集(EvoHarnessBench 已证明:往 harness 加工具/技能本身就会让旧任务退化)。
- 已知局限与合规:LLM-judge 非确定,需人审校准留痕(EU AI Act 高风险的合规证据);judge 成本随 k 线性增长,全量留夜间跑。
复现清单
① 上手三步走(可执行)
pip install strands-agents-evals → strands-evals --help 验证安装。- 写 30~50 条 case(含 10 条多轮改口),L1 配确定性
tool_usage_grader,L2/L3 配 LLM-judge rubric。 strands-evals run cases.json --agent ... --fail-on "layer1<0.95,layer2<0.85,layer3<0.90";同 case 连跑 3 次写 CSV,python agent_eval_passk.py results.csv 3 看 pass^3。
② 踩坑记录
strands-evals 找不到 → venv 没 activate,改用 python -m strands_evals.cli。- L1 抽不到 trajectory → 自研框架要实现等价工具抽取,否则失去确定性。
- judge 两次不一致 → 固定
temperature=0 + 人审,别当绝对真理。 - pass^k 全 1 仍翻车 → case 集不代表真实流量,补 ≥100 条/工作流。
③ 实用评分卡
④ 关键源码 / 论文链接
- 评测库:https://github.com/strands-agents/evals[1] (PyPI
strands-agents-evals) - 生产蓝图:AWS ML Blog《Evaluating AI Agents: a production blueprint with Strands and AgentCore》(2026-07)
- 可部署样例:sample-evaluating-agents-on-aws-with-strands-and-agentcore(GitHub)
- 一致性研究:τ-bench(Sierra)、pass^k 定义见本期
agent_eval_passk.py
今日反问
如果最终答案正确就能拿高分,为什么把「选对工具」「想清楚」「答得对」拆成三层独立判分后,反而能拦住那些单轮测试全程绿灯、上线却频频翻车的 Agent?
引用链接
[1]https://github.com/strands-agents/evals