AI Agent效果评测实战:搭建完成后才是真正的挑战
上次我们聊了如何在 WebView 中运行 MCP Agent,天气查询、邮件发送等功能总算调试通过。结果老板走过来,轻飘飘地问了一句,瞬间让人陷入沉默——

“效果怎么样?”
当时第一反应是:能运行啊,你不是亲眼看到了吗?但转念一想,不对。他问的压根不是“能不能运行”,而是“运行得好不好”。而我手头没有任何数据支撑,根本说不清楚。
仔细想想,花了两周搭建的 Agent,上线后却面临一个尴尬的现实。别人问“它可靠吗”,只能凭感觉回答“还行吧”——这简直是在开盲盒。
反常识的是:编写 Agent 其实只占四成功力,剩下的六成,全部花在搭建评测体系上。
翻车一:人工评测——跑了 10 次,自己先崩溃了
最初的思路非常朴素:设计十个八个任务,逐一运行,看看能否顺利完成。
任务内容还挺具体:
- “查一下下周北京天气”
- “发邮件给张三说会议改期”
- “查文档里 API 密钥过期时间,然后发邮件通知相关人”
看起来挺像回事吧?结果运行第一轮就傻眼了。
同一个 prompt 运行两次,第一次成功了,第二次 LLM 在 tool call 阶段却选了个完全不同的工具。同一个任务,两次结果竟然不一样!
当时心里那个纠结——是 LLM 抽风了还是网络抖动?要不,再测试第三次?第四次?
紧接着,更大的问题来了:“这算完成了吗?”
有些任务的结果是模糊的。比如“发邮件给张三说会议改期”——Agent 调用了通讯录找到了张三的邮箱,撰写了邮件正文,点击了发送。SMTP 到底发送成功没有?日志里显示 200 返回,但收件箱里真的收到了吗?需要人工核验,每测一次都得去翻一次收件箱。
测试了大概七八个任务后,心态开始松懈——“嗯,这个大概成功了吧,下一个。”到最后,测着测着就失去了耐心,开始睁一只眼闭一只眼。
人工评测的致命伤其实就三个:不可复现、样本量太小、测试后期连自己都糊弄自己。
翻车二:让 LLM 自己评判——它疯狂放水
人工不行,那就自动化吧。
第一版自动化评测的逻辑特别天真:让 LLM 自己查看工具调用日志,自行判定“这个任务完成没有”。
结果呢?它任何情况都说“完成了”。
最离谱的一次——故意把 SMTP 服务搞挂后再运行测试。Agent 调用了 send_email 工具,返回了 200,但邮件根本没发送出去(因为 SMTP 挂了但 HTTP 层返回的还是成功)。然后让 LLM 评判,它看了一眼日志说:“工具调用成功,邮件已发送”。
它只看“工具有没有返回成功”,根本不管“实际执行结果对不对”。
这个坑后来怎么填的?我们搞了一个“双判”机制。
一层是规则校验——不光看工具返回码,还得验证执行结果。发邮件?不光要看 API 返回没返回 200,还得真的去查发件箱确认邮件是否到达。这步是硬逻辑,可靠但死板。
另一层是 LLM 评审——把完整的执行链路(用户意图 → LLM 推理 → tool call → 工具返回 → 执行结果)丢给另一个更强的 LLM 去判定。这步灵活但不可靠(刚被它骗过)。
当规则层和 LLM 层结论一致时,直接出结果。不一致的,标黄让人工介入。两边互相兜底,谁也別想糊弄谁。
这套方案虽然不完美,但至少把“瞎放水”的问题解决了七八成。
翻车三:精心设计了 20 个测试用例,上线瞬间被打脸
前两个坑爬出来之后,花了一整天设计了 20 个测试用例,覆盖了各种常见场景。运行一轮,全绿。
当时那个得意——评测体系建好了,Agent 效果稳定了,上线!
结果上线第一天,第一个真实用户的问题就把 Agent 干懵了。
用户问的是:“帮我查一下上周那个项目文档里提到的 API 密钥,顺便看看有没有过期,如果过期了帮我发邮件给相关的人。”
测试集里根本没有这种“查 A 时顺带发现 B 有问题所以执行 C”的多跳推理场景。全是单步任务。测试集和真实场景的差距有多大,不上线永远不知道。
后面怎么调整的?两个方向同时改。
一个方向是从真实对话里提取测试用例——上线前三天,所有用户对话都打日志,挑出有代表性的手动加入测试集。别自己闭门造车设计用例了,用户教你的才是最真实的。自己设计的用例是你以为用户会问的,而真实用户问的永远是你没想到的。
另一个方向是分场景评估——单步任务归单步任务,多步推理归多步推理,条件分支归条件分支,分开统计成功率。别只看一个“总体成功率”,那是自欺欺人。整体八成好看吧?但其中一个场景可能只有两成——平均数把你骗了。哪个场景拉了垮,一眼就能看出来。
代码:评测框架核心就仨东西
聊了这么多理论,来点实际的。这套评测框架的核心说白了就三个东西:任务定义 → 执行器 → 判定器。
任务定义长这样——就是一个 JSON 对象,告诉系统“你要测什么”:
const testCase = {
id: "case-001",
description: "查下周北京天气",
input: "下周北京天气怎么样?",
expectedTools: ["get_weather"], // 预期调用的工具列表
expectedResult: /北京.*下周.*晴/, // 输出内容用正则匹配(可选)
validation: "rule+llm" // 双判模式
};
判定器是核心,就是前面说的“双判”逻辑。代码其实没几行:
async function judge(result, testCase) {
const rulePass = ruleCheck(result.toolCalls, testCase.expectedTools);
const llmPass = await llmJudge(result.fullLog, testCase.input);
if (rulePass && llmPass) return "pass";
if (!rulePass && !llmPass) return "fail";
return "needs_review"; // 不一致,标黄让人工看
}
有意思的是 needs_review 这个分支——两边结论一致的直接过,不一致的标出来等人看。一开始觉得这个分支应该很少,结果实际运行起来大概有两三成的 case 会落到这里。这也说明“双判”不是多余的,两条腿走路确实比一条腿稳。
实际实现里还加了超时处理、重试策略、结果缓存,但上面这段已经能说明核心思路了。
收尾:没有评测体系,你都不知道自己长啥样
翻了这仨大跟头之后,有个感受特别深刻——
没有评测体系,你根本不知道改了一个 Prompt 是让 Agent 变好了还是变差了。自我感觉“好像好了一点”?那极可能是错觉。跑一遍评测,数字说话。
现在的习惯是:每次迭代前先运行一遍基线。改完 Prompt 或者 Tool 定义,再运行一遍同样的测试集。数字涨了就是涨了,跌了就是跌了,不靠感觉。
听起来麻烦,但养成习惯之后其实也就几分钟的事。比起你上线之后被用户骂、再去排查、再修、再上线——这几分钟的投入太值了。
你们的团队怎么评测 Agent 的?有没有碰到比这个更离谱的翻车?最近在攒素材写续篇,说不定下篇就有你的故事。
