1. Agent 评测到底在评什么:先把靶子立对
做 Agent 评测这件事,我踩过最大的坑不是技术难,而是一开始就没想清楚“评什么”。团队花了两周搭了一套评测流水线,跑了几百条用例,最后发现测的东西跟线上真实场景对不上,白干。所以这篇手册的第一件事,就是把评测目标拆清楚。
1.1 三层评测对象:结果、过程、系统
很多人一提 Agent 评测,脑子里第一反应就是“答对了没有”。这个思路在传统问答场景里没问题,但放到 Agent 上就太窄了。Agent 和普通 LLM 调用最大的区别在于:它是有多步决策的。一个 Agent 可能调了五次工具、走了八步推理,最后答案对了,但中间绕了巨大弯路;也可能答案错了,但过程其实很合理,只是某个工具返回了脏数据。
所以我习惯把评测对象分成三层:
- 结果层:最终输出对不对。比如任务完成率、答案准确率、结构化字段的匹配度。这是最直观的一层,也是老板最关心的一层。
- 过程层:中间步骤合不合理。包括工具调用是否必要、参数是否正确、推理链有没有跳步、有没有重复劳动。这一层决定了 Agent 是“碰巧对了”还是“稳定能对”。
- 系统层:整体运行指标。包括平均步数、token 消耗、端到端延迟、失败重试率、异常中断率。这一层直接关系到成本和用户体验。
三层缺一不可。只测结果,你会被“运气好的 Agent”骗;只测过程,你没法跟业务方交代。我的建议是:结果层作为准入门槛,过程层作为优化抓手,系统层作为上线红线。
1.2 不同阶段的评测重点完全不一样
同一个 Agent,在 demo 阶段、内测阶段、上线阶段,评测重点应该动态调整。我见过太多团队用一套评测集从头跑到尾,结果就是早期被细节拖死,后期被漏测坑死。
| 阶段 | 核心目标 | 评测重点 | 用例规模 | 通过标准 |
|---|---|---|---|---|
| Demo 验证 | 能不能跑通 | 结果层为主 | 20-50 条 | 完成率 > 60% |
| 内测迭代 | 稳不稳定 | 过程层 + 结果层 | 100-300 条 | 完成率 > 85%,工具调用正确率 > 90% |
| 上线前 | 能不能扛 | 系统层 + 全层 | 500+ 条 | 完成率 > 90%,P95 延迟达标 |
| 线上监控 | 有没有退化 | 抽样 + 回归集 | 持续采样 | 无显著下降 |
这张表不是拍脑袋来的,是我在几个项目里反复调整后总结的。Demo 阶段你要是追求 90% 完成率,基本等于自虐,因为很多边界情况还没覆盖。上线前你要是只看完成率不看延迟,用户分分钟教你做人。
1.3 评测集构建:别一上来就想着“大而全”
热词里有个词叫“agent评测集构建”,这确实是核心难点。我的经验是:评测集的质量远比数量重要。500 条精心设计的用例,胜过 5000 条随机爬来的问题。
构建评测集时,我一般按这个维度来分层:
- 核心路径:用户最常走的流程,占 40%。比如一个客服 Agent,核心路径就是“查订单-改地址-确认”。
- 边界情况:参数缺失、格式异常、多意图混杂,占 30%。这类用例最容易暴露 Agent 的鲁棒性问题。
- 对抗样本:故意诱导 Agent 犯错,占 15%。比如让它在信息不足时强行编造答案。
- 长尾场景:低频但真实存在的需求,占 15%。这类用例不追求覆盖全,但要有代表性。
注意:评测集不是一次性的。每次线上发现新问题,都要反哺回评测集。我习惯给每条用例打标签,方便后续按维度分析。
2. 评测方法选型:LLM-as-a-Judge 不是万能药
“怎么评”这个问题,本质上是在问:用什么手段来判断 Agent 的输出好坏。目前主流就三条路:人工评测、规则匹配、模型评测。热词里的 LLM-as-a-Judge 属于第三条,但它不是银弹。
2.1 三种评测手段的适用边界
先上一张对比表,这张表我建议直接贴在工位上:
| 手段 | 成本 | 速度 | 一致性 | 适用场景 | 不适用场景 |
|---|---|---|---|---|---|
| 人工评测 | 极高 | 极慢 | 中 | 主观质量、复杂推理 | 大规模回归 |
| 规则匹配 | 低 | 快 | 高 | 结构化输出、关键词 | 开放式生成 |
| LLM-as-a-Judge | 中 | 中 | 中高 | 语义相似、多维度打分 | 强事实性校验 |
人工评测的问题不用多说,贵且慢,但它在“主观质量”判断上仍然是金标准。规则匹配适合那些有明确答案的场景,比如“订单号必须是 12 位数字”。LLM-as-a-Judge 则填补了中间地带:它能理解语义,但又比人快得多。
我的实操策略是:规则匹配打底,LLM-as-a-Judge 做主力,人工评测做校准。具体来说,先用规则过滤掉明显错误的输出,再用 Judge 模型对剩余输出做多维度打分,最后每周抽 5% 的样本人工复核,用来校准 Judge 的偏差。
2.2 LLM-as-a-Judge 的坑与调优
用 Judge 模型评测,最容易犯的错就是“直接问模型好不好”。这种问法得到的分数基本没有区分度,模型倾向于给中间分。我试过很多 prompt 结构,最后稳定下来的方案是分维度打分 + 强制排序。
具体做法是:
- 把评测拆成 3-5 个维度,比如“事实准确性”“指令遵循度”“表达清晰度”。
- 每个维度给 1-5 分的明确标准,写清楚 1 分是什么样、5 分是什么样。
- 让 Judge 先输出每个维度的分数和理由,再给总分。
- 对于同一批样本,让 Judge 做两两对比,而不是绝对打分。
# Judge prompt 的核心结构示例 judge_prompt = """ 你是一个严格的评测员。请根据以下标准对 Agent 的输出打分。 维度1:事实准确性 1分:包含明显事实错误 3分:事实基本正确但有遗漏 5分:事实完全正确且完整 维度2:指令遵循度 1分:完全忽略用户指令 3分:部分遵循但有偏差 5分:完全遵循且无多余动作 请先输出每个维度的分数和理由,再输出总分。 """实操心得:Judge 模型的温度参数建议设成 0,否则同一批样本跑两次分数会飘。另外,Judge 模型最好比被测模型强一个档次,否则容易出现“看不懂所以给高分”的情况。
2.3 规则匹配的隐藏价值
很多人觉得规则匹配太 low,不屑于用。但我在实际项目里发现,规则匹配在回归测试中的价值被严重低估了。Agent 迭代频繁,每次改 prompt 或换模型,都可能引入退化。这时候用规则匹配跑一遍核心用例,几分钟就能发现明显问题,根本不需要动用 Judge。
我常用的规则包括:
- 输出格式校验:JSON 能不能解析、必填字段在不在。
- 关键词命中:关键信息有没有出现在输出里。
- 长度约束:输出是不是过长或过短。
- 工具调用序列:有没有调用不该调用的工具。
这些规则写起来快,跑起来更快,是评测流水线的第一道防线。
3. 评测落地:从脚本到流水线的完整实现
聊完“评什么”和“怎么评”,接下来是最硬核的部分:怎么把评测真正跑起来。我见过太多团队停留在“写了个评测脚本”的阶段,每次要评测就手动跑一下,结果就是评测频率越来越低,最后不了了之。真正的落地,必须做成自动化流水线。
3.1 评测流水线的整体架构
一条完整的 Agent 评测流水线,我一般拆成五个模块:
- 用例管理:存储评测集,支持标签、版本、优先级。
- 执行引擎:批量跑 Agent,记录每一步的输入输出。
- 评测器:规则匹配 + Judge 打分,输出结构化结果。
- 结果存储:把每次评测的结果存下来,支持对比。
- 报告生成:自动生成可视化报告,推送到群里。
这五个模块里,执行引擎是最容易被低估的。Agent 跑一次可能涉及多次工具调用、多次模型请求,如果串行跑 500 条用例,可能要几个小时。我的做法是用异步并发,但要注意控制并发数,避免把下游工具打挂。
import asyncio from semaphore import Semaphore async def run_evaluation(cases, agent, max_concurrency=10): sem = asyncio.Semaphore(max_concurrency) async def run_one(case): async with sem: result = await agent.run(case.input) return {"case_id": case.id, "output": result} tasks = [run_one(case) for case in cases] return await asyncio.gather(*tasks)注意:并发数不是越高越好。我试过设成 50,结果下游的检索服务直接超时,评测结果全是失败。后来稳定在 10-20 之间,具体看下游服务的承载能力。
3.2 评测结果的结构化设计
评测结果如果只是一堆文本,后续根本没法分析。我要求每次评测的输出必须是结构化的,至少包含这些字段:
| 字段 | 类型 | 说明 |
|---|---|---|
| case_id | string | 用例唯一标识 |
| run_id | string | 本次评测批次 ID |
| agent_version | string | Agent 版本号 |
| final_output | string | 最终输出 |
| steps | int | 执行步数 |
| tool_calls | list | 工具调用记录 |
| latency_ms | int | 端到端延迟 |
| token_usage | int | token 消耗 |
| rule_score | float | 规则匹配得分 |
| judge_scores | dict | 各维度 Judge 得分 |
| passed | bool | 是否通过 |
有了这套结构,后续做趋势分析、版本对比、问题定位都方便得多。我习惯把结果存到一张宽表里,然后用 SQL 直接查“哪个维度的得分在最近三次评测中持续下降”。
3.3 版本对比与回归检测
Agent 迭代最怕的就是“改了一个地方,坏了另一个地方”。所以每次发版前,我都会跑一次全量回归,然后跟上个版本做对比。
对比的核心指标有三个:
- 通过率变化:整体通过率下降超过 2% 就要警惕。
- 新增失败用例:上个版本通过、这个版本失败的用例,必须逐条分析。
- 修复用例:上个版本失败、这个版本通过的用例,确认是不是真的修好了。
我一般会生成一张对比表,直接贴到发版评审里:
| 指标 | 上个版本 | 当前版本 | 变化 |
|---|---|---|---|
| 整体通过率 | 87.3% | 89.1% | +1.8% |
| 核心路径通过率 | 92.0% | 93.5% | +1.5% |
| 边界情况通过率 | 78.5% | 76.2% | -2.3% |
| 平均步数 | 4.2 | 4.8 | +0.6 |
| P95 延迟 | 3.2s | 3.5s | +0.3s |
这张表一出来,问题就很明显了:整体通过率涨了,但边界情况退化了,而且步数和延迟都涨了。这时候就要去看边界情况的失败用例,大概率是新加的某个功能影响了鲁棒性。
4. 常见问题与排查技巧实录
评测做久了,遇到的问题五花八门。我挑几个最高频的,把排查思路和解决方法整理出来,方便你直接抄作业。
4.1 Judge 打分不稳定怎么办
这是最常被问到的问题。同一批样本,今天跑出来 85 分,明天跑出来 78 分,根本没法用。原因通常有三个:
- 温度参数没设成 0:这是最低级的错误,但真的很多人犯。
- Prompt 里有模糊表述:比如“回答得好就给高分”,什么叫“好”?模型理解不一致。
- 样本顺序影响:Judge 模型对上下文敏感,如果一批样本里前面都是高质量回答,后面一个中等质量的回答可能被压低。
解决方法:温度设 0,评分标准写具体,每批评测前打乱样本顺序,并且固定随机种子。
4.2 Agent 输出格式不稳定怎么评
Agent 有时候输出 JSON,有时候输出 Markdown,有时候还夹带解释性文字。这种情况下,规则匹配基本失效。我的做法是先做输出归一化:用正则或轻量解析器,把输出里的结构化部分抽出来,再拿去评测。
import re import json def normalize_output(raw_output): # 尝试提取 JSON 块 json_match = re.search(r'\{.*\}', raw_output, re.DOTALL) if json_match: try: return json.loads(json_match.group()) except json.JSONDecodeError: pass # 兜底:返回原始文本 return {"raw": raw_output}实操心得:归一化逻辑本身也要有测试用例。我见过归一化代码把合法 JSON 解析错的,导致评测结果全错。
4.3 评测集“过期”了怎么办
业务在变,评测集如果一直不更新,就会逐渐失去代表性。我的做法是每月做一次评测集审计:
- 统计每条用例的失败率,长期 100% 通过的用例考虑降权或移除。
- 检查用例是否还符合当前业务场景,过时的直接删。
- 从线上日志里采样新的真实问题,补充进评测集。
这个过程听起来麻烦,但比“评测集跑出来全是高分,线上却天天出问题”要好得多。
4.4 评测成本太高怎么控
Judge 模型调用是要花钱的,500 条用例每条调 3 个维度,就是 1500 次模型调用。如果每天跑一次,成本很可观。我的控本策略是:
- 分层评测:核心用例每天跑,全量用例每周跑。
- 规则优先:能用规则判断的,绝不调 Judge。
- 缓存复用:同一批样本的 Judge 结果缓存起来,没变化就不重复调。
- 小模型打底:先用小模型做初筛,只把有争议的样本交给大模型。
这套组合拳下来,评测成本能压到原来的三分之一左右。
4.5 常见问题速查表
| 问题现象 | 可能原因 | 排查方向 | 解决方法 |
|---|---|---|---|
| Judge 分数波动大 | 温度非 0、标准模糊 | 检查 prompt 和参数 | 温度设 0,标准量化 |
| 规则匹配误判 | 输出格式不统一 | 看原始输出 | 加归一化层 |
| 评测跑得慢 | 串行执行、并发低 | 看执行日志 | 异步并发,控制并发数 |
| 通过率虚高 | 用例太简单 | 分析用例分布 | 补充边界和对抗样本 |
| 版本对比无差异 | 评测集太小 | 看用例数量 | 扩充到 300+ 条 |
| 线上问题测不出 | 评测集过期 | 对比线上日志 | 定期审计和补充 |
这张表我建议打印出来贴在显示器旁边,遇到问题先扫一眼,能省不少排查时间。
5. 评测之外:那些容易被忽略的细节
评测本身是技术活,但真正决定评测价值的,往往是技术之外的东西。这部分我分享几个踩坑换来的经验。
5.1 评测指标要跟业务指标对齐
技术团队容易陷入“指标自嗨”:完成率从 85% 提到 92%,觉得很厉害,但业务方关心的可能是“用户投诉率有没有降”。如果评测指标跟业务指标脱节,评测做得再好也没人认。
我的做法是:每个技术指标都要能找到对应的业务指标。比如“工具调用正确率”对应“任务一次完成率”,“平均步数”对应“用户等待时长”。这样在汇报时,才能说清楚评测的价值。
5.2 评测报告要让人看得懂
我见过很多评测报告,满屏都是数字和术语,业务方看了直摇头。好的评测报告应该做到:一页纸说清楚结论,附录里放细节。
报告结构我一般这样组织:
- 第一页:整体通过率、关键指标变化、是否建议发版。
- 第二页:分维度得分、失败用例分类。
- 附录:具体失败用例、Judge 打分理由。
实操心得:报告里一定要有“结论先行”。老板没耐心看你分析过程,先把结论甩出来,再解释原因。
5.3 评测不是终点,而是起点
最后说一个心态问题。很多人把评测当成“发版前的最后一道关卡”,跑完就完事了。但在我看来,评测的真正价值在于驱动迭代。每次评测发现的失败用例,都是下一轮优化的输入。评测-分析-优化-再评测,这个循环转起来,Agent 才能持续变好。
我现在的习惯是:每次评测后,把失败用例按原因分类,然后直接开一个优化会,当场定下改进方案。这样评测就不是一个“检查动作”,而是一个“生产动作”。
5.4 关于 Agent 安全评测的补充
热词里提到了“agent安全”,这块确实越来越重要。除了常规的功能评测,我建议至少加上这几类安全用例:
- 越权操作:Agent 会不会执行超出权限的工具调用。
- 信息泄露:Agent 会不会在输出里暴露敏感信息。
- 诱导攻击:用户故意诱导 Agent 做出不当行为。
- 资源滥用:Agent 会不会陷入死循环,疯狂调用工具。
这些用例不需要多,但必须有。我一般放在单独的“安全评测集”里,每次发版前必跑。
6. 一套可直接复用的评测方案模板
聊了这么多,最后给一套可以直接抄的评测方案模板。这套模板我在三个项目里用过,基本不需要大改。
6.1 目录结构
agent-eval/ ├── cases/ # 评测集 │ ├── core.jsonl # 核心路径 │ ├── edge.jsonl # 边界情况 │ ├── adversarial.jsonl # 对抗样本 │ └── safety.jsonl # 安全用例 ├── evaluators/ # 评测器 │ ├── rule_based.py # 规则匹配 │ └── llm_judge.py # Judge 评测 ├── runner/ # 执行引擎 │ └── async_runner.py ├── reports/ # 评测报告 └── config.yaml # 配置文件6.2 配置文件示例
evaluation: concurrency: 10 timeout_seconds: 60 retry_times: 2 judge: model: "your-judge-model" temperature: 0 dimensions: - name: "事实准确性" weight: 0.4 - name: "指令遵循度" weight: 0.4 - name: "表达清晰度" weight: 0.2 rules: - type: "json_valid" weight: 0.3 - type: "keyword_hit" keywords: ["订单号", "金额"] weight: 0.3 - type: "length_limit" max_length: 2000 weight: 0.4 report: output_dir: "./reports" notify_webhook: "your-webhook-url"6.3 执行入口
import asyncio from runner.async_runner import AsyncRunner from evaluators.rule_based import RuleEvaluator from evaluators.llm_judge import LLMJudge async def main(): runner = AsyncRunner(config_path="./config.yaml") cases = runner.load_cases("./cases") results = await runner.run(cases) rule_eval = RuleEvaluator(config_path="./config.yaml") judge_eval = LLMJudge(config_path="./config.yaml") for result in results: result["rule_score"] = rule_eval.evaluate(result) result["judge_scores"] = await judge_eval.evaluate(result) runner.save_results(results, "./reports") runner.generate_report(results) if __name__ == "__main__": asyncio.run(main())这套模板跑起来后,每次评测只需要更新用例集,然后执行入口脚本,报告会自动生成并推送。我实测下来,500 条用例从执行到出报告,大概 15 分钟,完全能接受。
6.4 后续扩展方向
这套方案目前覆盖了功能评测和安全评测,后续还可以往这几个方向扩展:
- 多轮对话评测:现在的用例大多是单轮的,多轮场景需要单独设计。
- RAG 专项评测:如果 Agent 用了 RAG,需要单独评测检索质量和答案忠实度。
- 成本评测:把 token 消耗和工具调用成本纳入评测指标。
- A/B 评测:支持同时跑两个版本的 Agent,直接对比。
我个人在实际操作中的体会是:评测体系不是一次搭好的,而是随着 Agent 一起成长的。先跑起来,再慢慢完善,比一开始就追求完美要靠谱得多。踩过几次坑之后,你会发现,评测最大的价值不是“证明 Agent 行不行”,而是“告诉团队下一步该往哪走”。