最近在很多大模型(LLM)和 AI Agent 的技术讨论里,经常能听到一个词:Eval(评测)。
简单来说,就是写测试用例:
大模型每次返回的结果都不太一样,今天回答用中文,明天可能多吐出几个英文单词,这种充满随机性的东西,到底该怎么做评估?
其实,如果换一个大家熟悉的视角,这个问题一点也不神秘。
所谓的 Eval,本质上就是我们前端天天在写的自动化测试;而围绕 Eval 展开的工程实践,无非就是 TDD(测试驱动开发) 与 Sentry 错误监控闭环在生成式 AI 时代的一次自然延续。
一、 为什么 Agent 开发不能“凭感觉”?
先从我们最熟悉的前端场景说起。
假设你要在前端写一个商品价格计算的函数 calcDiscountPrice()。作为一个合格的前端工程师,你大概率会用 Vitest 或 Jest 补上几组单元测试:
// 传统前端单测:确定性断言
describe('calcDiscountPrice', () => {
it('满 100 应该正常减 20', () => {
expect(calcDiscountPrice(100, 20)).toBe(80);
});
it('折扣金额大于原价时,最低应为 0', () => {
expect(calcDiscountPrice(50, 100)).toBe(0);
});
});
如果不写这些单测,每次改完代码,你就只能自己在浏览器里手动输入价格,刷新页面看几眼。如果今天心情好点了三组数据觉得没问题,就直接发布上线了,这种做法在业内被戏称为“凭感觉开发”(Vibe Check)。
凭感觉开发最可怕的地方,在于回归盲区(Regression Blindness)——你为了修一个极端情况的 Bug,改动了某段逻辑,自己测了一下这个极端情况确实修好了,却根本不知道这次改动把另外 90% 的常规业务逻辑给破坏了。
在传统代码里,由于逻辑是确定性的,代码行数也不多,即使不写测试,靠人工点点有时候也能糊弄过去。
但到了 AI Agent 时代,大模型是一个巨大的黑盒。你修改了一条 Prompt(提示词),比如加了一句“请以最简短的语言回答”,你自测了三个问答,觉得回答确实变简练了;但你完全不知道,这句简短的要求可能导致原本需要输出完整 JSON 结构的几十个工具调用场景全部瘫痪。
没有测试套件的护栏,每一次提示词的微调、每一次底层模型的升级,都无异于在盲人摸象。
而 Eval,就是专门用来给大模型和 Agent 充当这道测试护栏的。
二、 面对随机性,Eval 到底怎么“测”?
既然要测,很多朋友的第二反应是:前端测试有 expect(a).toBe(b) 这种全等比较,但大模型输出的文本千变万化,我们怎么做断言?
在实际工程中,大家通常会把 Eval 拆分成三个由低到高、由浅入深的层级。
第一层,是确定性硬断言(Deterministic Asserts)。
虽然自然语言千变万化,但很多业务场景下的输出是有硬性契约的。
比如让 Agent 提取一段用户地址并输出 JSON,我们完全可以用标准的 JSON Schema 或者 Zod 进行校验:必须是合法的 JSON 格式、必须包含 province 和 city 字段、邮编必须是纯数字。
又比如让 Agent 编写 SQL,我们可以直接把它丢进真实的 SQLite 内存数据库跑一下 EXPLAIN,看看语法是否合法。这一层的运行成本极低,速度极快,不达标直接判错。
第二层,是轨迹与工具断言(Trajectory & Tool Asserts)。
这也是 Agent 与普通 Chat 机器人最大的区别所在。
Agent 往往需要自主规划多个步骤,调用不同的外部 API(Tools)。这时候,我们关心的重点是它的“行为轨迹”是否合规:
- 它有没有调用预期的工具?(比如查天气时,有没有调用
get_weather?) - 工具的入参格式对不对?(日期有没有格式化为
YYYY-MM-DD?) - 调用的时序合不合理?(是不是先查询了用户权限,再执行了退款操作?)
- 有没有陷入死循环?(有没有连续十次尝试同一个无效参数?)
只要校验了它的 Tool Call 序列,Agent 绝大部分的行为可靠性就已经被锁定了。
第三层,是模型裁判(LLM-as-a-Judge)。
当遇到无法用代码精确断言的开放式场景(比如“回答是否礼貌”、“文章润色是否通顺”、“逻辑推导是否严密”)时,我们就请出另一个更强大的模型(比如 Claude 3.5 Sonnet 或 GPT-4o)来充当“裁判”。
我们会给裁判模型一份非常详细的评分细则(Rubric)和评判标准:
# 裁判评分标准
- 事实一致性:回答中的数据必须与提供的上下文完全吻合(0/1)
- 语气克制:不能包含推销或过度夸张的修辞(0/1)
- 拒绝越权:如果用户要求查询他人的薪资,必须明确拒绝(Pass/Fail)
裁判模型根据标准逐项打分,并给出判定理由。
准备一个由几十条核心业务用例、历史缺陷样本组成的“黄金测试集”(Golden Dataset),每次改完代码或 Prompt,在本地跑一遍这三层评测,得到一个综合通过率(比如 96%),这就是一个最标准的 Eval 流程。
三、 从 TDD 到 EDD:先写评测,再写 Prompt
在敏捷开发里,大家对 TDD(测试驱动开发) 一定不陌生:
- 红灯(Red):先写一个必定失败的单元测试;
- 绿灯(Green):写出刚好能让测试通过的最简代码;
- 重构(Refactor):在测试用例的保护下,优化代码结构。
在 Agent 开发中,这套思维模式几乎被百分之百复刻了过来,社区里把它称作 EDD(Eval-Driven Development,评测驱动开发)。
很多没有经验的开发者做 Agent 时,习惯先打开编辑器,洋洋洒洒写几千字 Prompt,然后去界面上跟它聊两句。聊着聊着发现效果不好,又在 Prompt 里加一段补丁,越改越混乱。
而成熟的做法是:在写第一行 Prompt 之前,先把测试用例定义清楚。
比如我们要开发一个“订单退款审核 Agent”,第一步是先把 30 个真实的测试样本写在 JSON 文件里:
[
{
"id": "case-01-normal",
"input": "我刚拍错了颜色,能帮我把 20260901 的订单退了吗?",
"expected_tools": ["getOrderInfo", "cancelOrder"],
"forbidden_tools": ["triggerDispute"]
},
{
"id": "case-02-expired",
"input": "这件衣服我穿了三个月破了,必须给我全额退款!",
"expected_tools": ["getOrderInfo", "checkWarranty"],
"expected_decision": "REJECT_EXPIRED"
}
]
用例写完,直接运行 Eval。此时由于还没有任何 Prompt 和工具逻辑,测试结果全部飘红(Red)。
接着,你开始编写 Agent 的系统提示词、配置工具描述、编排状态机分支。每一次调整后,都在终端跑一次评测,看着通过率从 20%、60% 稳步爬升到 95% 以上(Green)。
未来无论是要换掉底层模型,还是要给 Agent 增加新功能,只要底下的 Eval 全绿,你心里就有底气。
四、 自动化的 Bad Case 回流飞轮(Eval Flywheel)
在本地通过 30~50 条黄金测试集跑通 EDD 之后,很多团队很快会遇到下一个棘手的工程问题:
测试集不是一成不变的。线上每天都在产生新的异常 Case,我们总不能靠工程师肉眼去翻几千条聊天记录、手动一条条编写测试用例吧?
这时候,只要回顾一下前端最熟悉的监控工具——Sentry,整个思路就豁然开朗了。
在前端工程中,我们通过 Sentry 自动采集运行时的未捕获异常,并记录下当时的操作流水(Breadcrumbs)与报错堆栈(Stack Trace)。
在 AI Agent 体系中,Eval Flywheel(评测飞轮) 扮演的正是这样一个“生成式 AI 时代的 Sentry”角色。它负责把线上不可控的异常偏航,自动捕获、脱敏清洗,并转化为可量化的回归测试用例回流进测试集:
线上异常流量 (用户点踩 / 报错 / 转人工)
│
▼
[ 1. 捕获与过滤 ] ──> 剔除无意义噪点与空请求
│
▼
[ 2. PII 脱敏 ] ──> 正则 + 实体识别脱敏 (抹去手机号、真实地址、订单隐私)
│
▼
[ 3. 语义去重 ] ──> 向量聚类 (Cosine > 0.90 自动合并,避免测试集无限膨胀)
│
▼
[ 4. 结构化合成 ] ──> 大模型基于业务上下文,自动逆向生成用例 (Input + Expected Tool + Rubric)
│
▼
[ 5. Staging 审核 ] ──> 自动提交 PR 或待审队列,人工花 30 秒确认 Ground Truth
│
▼
合并入主干 Golden Dataset ──> 触发 CI/CD 回归门禁,防止质量滑坡
如果我们把 Sentry 的各个核心模块与 AI 的 Eval 回流飞轮放在一起对标,两者的设计哲学几乎完全一致:
| 阶段 / 模块 | 传统 Sentry 体系 | AI 评测飞轮(Eval Flywheel) |
|---|---|---|
| 异常触发(Trigger) | 500 Crash、未捕获的 JS 异常、Promise Rejection | 用户前台点踩(Thumbs Down)、Tool 参数解析失败、死循环超时、转人工 |
| 现场追踪(Breadcrumbs / Trace) | 用户点击轨迹、网络请求日志、代码调用堆栈 | 当前 Prompt 版本、多轮对话上下文、模型超参数、Agent 工具调用链(Trajectory) |
| 聚合与去重(Grouping) | 根据 Stack Trace Hash 聚合为一个 Issue | 对用户 Query 提取向量(Embedding),余弦相似度 Cosine > 0.90 自动聚类合并 |
| 问题转化(Triage & Ticket) | 报错转化为 Jira / GitHub Issue 分配给工程师 | 大模型自动将报错会话提炼为结构化用例(input + expected_tools + rubric) |
| 防回归资产(Regression Testing) | 工程师根据堆栈手动补写 Unit Test | 审核后合并入主干 Golden Dataset,通过 CI/CD 自动评测门禁阻止质量滑坡 |
在传统开发中,“看 Sentry 报错 → 手动复现 → 手写单测”往往耗时耗力,很多边缘 Bug 因为复现成本高而被无限期搁置。
而在 AI 项目中,通过打通这套 Eval 飞轮,线上每一次真实的“偏航与翻车”,都会被全自动提炼成一条永远守护系统的回归测试用例。测试资产随着真实业务的运行而自主成长,大模型应用才能真正具备工业级的稳健性。
我们前端开发天生对用户体验、接口契约与异常排查非常敏感。面对大模型的不确定输出确实难搞,但只要把它当成一个需要被严密测试的黑盒组件,用写单测和看 Sentry 的平常心去对待它,其实并没有那么遥不可及。