Alejandro Rioja.
AI Agents Operations

我用来无所畏惧地发布 AI 智能体的评估框架

Alejandro Rioja
Alejandro Rioja
1 分钟阅读
TL;DR

无所畏惧地发布智能体来自一件事:一套评估框架。一组固定的评分测试用例,自动打分(断言加上一个 LLM 评判),在每次提示词或模型改动前运行。分数守住,就发布。测试集是用真实的生产故障构建的。

免费新闻通讯

每周三。28,400+ 读者。纯干货。

目录

2026 年 6 月更新。

摘要: 我之所以能在一个上线的智能体上改提示词或换模型而不必屏住呼吸,靠的是一件事:一套评估框架。一组固定的评分测试用例,自动打分——能写硬断言的地方写硬断言,写不了的地方用 LLM 评判——在每次改动前运行。分数守住,我就发布。分数下降,我就不发。测试集不是合成的;它是用真实的生产故障构建的,所以每个 bug 都会变成一项永久的回归测试。

操作者视角: 在 100 多个智能体里,我有信心去碰的和我害怕去碰的,区别就在于有没有评估。没有评估框架,每一次提示词微调都是一场赌博。评估框架把”我觉得这个更好”变成”这个可量化地好了 4 分,而且什么都没弄坏”。这就是全部的解锁所在。

你不会不写测试就发布代码。人们却不停地不写评估就发布智能体,然后纳闷为什么一次”小小的提示词微调”搞坏了生产。评估框架就是为非确定性软件准备的测试套件。这是我实际运行的那一套。

从真实故障构建的测试集开始

框架的好坏取决于它的测试用例,而最好的测试用例来自生产,而非你的想象。每次智能体在真实环境里失败时,我都会捕获确切的输入(我把每次运行都连同一个追踪 ID 记录下来——见如何在生产环境中调试智能体),并把它变成一个评估用例:

typescript
interface EvalCase {
  id: string;
  input: AgentInput;        // 确切的生产输入
  expected?: string;        // 有标准答案时的基准真值
  assertions: Assertion[];  // 必须通过的硬性检查
  rubric?: string;          // 用于 LLM 评判,当输出是开放式时
}

这里有两条实践很重要。从生产中提取,这样你的评估测试的是真正会坏的东西,而不是你猜测可能会坏的东西。以及覆盖整个范围——正常路径、边缘情况、对抗性输入,以及那些会导致静默失败的空/格式错误输入。一套精选的 30 到 50 个用例的测试集,比 500 个敷衍的用例抓到的多得多。我宁愿有 40 个各自代表一种真实失败模式的用例,也不要一千个全都测试同一条简单路径的用例。

先用断言打分,再用 LLM 评判

并非每个输出都需要一个模型来打分。我会去拿能用的最便宜的打分器。

对一切结构化的东西用硬断言。输出能解析为有效的 JSON 吗?包含必需字段吗?提取出的日期在范围内吗?它是否用正确的参数调用了正确的工具?这些是确定性的、免费的、毫无歧义的——能写多少就写多少。

typescript
const assertions: Assertion[] = [
  (out) => isValidJSON(out),
  (out) => parse(out).category in ALLOWED_CATEGORIES,
  (out) => parse(out).confidence >= 0 && parse(out).confidence <= 1,
];

对开放式的其余部分用 LLM 评判——语气、有用性、“它到底有没有回答问题”。在这里你给模型输入、输出和一份评分标准,让它打分。两条规则能让评判保持诚实:把评分标准做得具体(带有描述性锚点的 1 到 5 分量表胜过”给质量打分”),并用一个强模型作为评判——评判是一项推理任务,所以即便智能体本身按照成本计算跑在 Haiku 上,这也是我乐意为 Sonnet 付费的地方。模糊的评分标准或羸弱的评判会给你看起来像信号的噪声。

在每次改动前运行框架

框架的存在是为了回答一个问题:这次改动让智能体变好了还是变差了? 所以我在每次提示词编辑、模型替换或工具改动之前都运行它。

bash
# main 上的基准
npm run eval -- --suite=booking-agent > baseline.json

# 做出改动,然后重新运行
npm run eval -- --suite=booking-agent > candidate.json

# 比较
npm run eval:diff baseline.json candidate.json

差异报告会显示汇总分数、逐用例的通过/失败,以及——至关重要的——具体是哪些用例发生了回归。 一个在三个用例静默损坏的同时往上走的汇总分,不是改进;那是一笔我想看到并批准的取舍,而不是悄悄溜过去的那种。盯着逐用例的差异,就是你避免”修好一个、弄坏两个”的办法——正是这种失败模式让人们害怕自己的提示词。

设一道回归闸门,让它拦下来

一旦你信任了框架,就把它作为闸门接入通往生产的路径。我的规则很直白:任何让分数跌破基准阈值的改动都不发布。 不是”我回头看看”——而是被拦下,和一个失败的 CI 测试一样。

typescript
const PASS_THRESHOLD = 0.90; // 90% 的用例必须通过
if (candidate.passRate < PASS_THRESHOLD || candidate.passRate < baseline.passRate) {
  throw new Error(`Eval regression: ${candidate.passRate} < ${baseline.passRate}`);
}

正是这一点把评估从一个锦上添花的东西,变成让你能快速行进的那件东西。闸门让”无所畏惧地发布”字面意义上成真:一次糟糕改动的最坏情况是一次飘红的评估运行,而不是一场生产事故。而且因为测试集每次有东西损坏时都会增长,闸门会随着时间自行变得更严格、更具保护性。

在打分中考虑非确定性

一个让人栽跟头的微妙之处:同一个输入在不同运行中可能得到不同的分数,因为模型的采样方式不同。如果你每个用例只跑一次,你会看到幻影回归——一个”坏了”的用例其实只是采样噪声。

两种缓解办法。在 temperature: 0 下运行评估以缩小方差(它无法完全消除)。而对那些你见过闪烁的用例,跑 N 次并取通过率,而不是单次的通过/失败。一个 10 次通过 9 次的用例,状态比一个 10 次通过 5 次的更好,哪怕两者都能显示一次飘绿的单次运行。这与我调试间歇性失败时用的”用量胜于轶事”原则相同——一次运行是观点,五十次运行是数据。

用生产监控闭合回路

评估框架针对已知用例做测试。生产则抛出新奇的用例。所以回路是这样的:监控线上行为,抓住一种新的失败模式,把它变成一个评估用例,修好它,于是它现在被永久守护了。监控这一侧——在线上流量中追踪成功率、输出有效性和每次运行的成本——是我在我如何衡量一个 AI 智能体是否真的在工作中讲到的。评估和监控是同一系统的两半:监控找出 bug,评估确保它们保持死透。

那个反馈回路才是真正的产品。任何单一的评估集都会过时;而一个把每次生产故障都转化为永久测试的流程,每周都会变得更强。这就是一个智能体如何从”碰都不敢碰”变成我会在某个周五下午眼都不眨地重构的东西。

常见问题

一个 AI 智能体评估集里包含什么?

把真实的生产输入变成评分用例——正常路径、边缘情况、对抗性和格式错误的输入——每个都配硬断言,对开放式输出再配一份 LLM 评判评分标准。从真实故障中提取的 30 到 50 个用例,胜过数百个全都测试简单路径的合成用例。

我该用 LLM 来给智能体输出打分吗?

凡是输出结构化的地方(有效 JSON、正确字段、正确的工具调用)都用硬断言——它们免费且确定。把 LLM 评判留给语气、有用性这类开放式特质,配上具体的评分标准和强评判模型,这样你得到的是信号,而不是噪声。

我如何阻止一次提示词改动悄悄搞坏生产?

在每次改动前运行评估框架并与基准做差异比对,盯着逐用例的回归,而不只是汇总分数。然后用结果为部署设闸,任何跌破基准阈值的改动都像失败的测试一样被拦下。

我如何处理评估中的非确定性?

在温度 0 下运行以降低方差,对会闪烁的用例,多次运行并用通过率打分,而不是单次运行。一个 10 次中通过 9 次的用例,比一个 10 次中通过 5 次的更健康,即便单次运行两者都显示为绿。

继续阅读

将AI实战手册发送到您的邮箱

每周三。28,400+ 读者。纯干货。

↵ 查看全部结果 esc esc 关闭