AI Pulse

vibe-code 能拼出应用,vibe-test 却测不出智能体能不能用

vibe-code 能拼出应用,vibe-test 却测不出智能体能不能用

你可以靠 vibe-code(凭感觉写代码)拼出一个功能强大的应用,但你不能靠 vibe-test(凭感觉测试)就宣告完工。手动调几个 prompt、试几个输入,能走的路也就这么远;真正能让智能体为部署做好准备、并在上线后持续保持正常工作的,是 evals(评估)。

团队常常问该用哪个 eval 工具,好用的确实不少:Langfuse、Braintrust、LangSmith 等等。但没有任何工具能告诉你你的智能体应该做什么;这需要你自己去定义。

这就是 eval 驱动开发(eval-driven development):先定义“良好表现”是什么样的,然后用测试来检验智能体,再迭代改进直到它通过。每一个重要的失败都应该变成一个回归测试:每次改动后都重跑这个用例,这样如果失败再次出现,你就能立刻发现。

下面,我们先介绍每一个 eval 工具都共有的四个基本构件,然后用一个五步流程把它们串起来,供你在开发新行为或修复生产环境中发现的失败时使用。

什么是 eval?

eval 是应用于 trace(追踪记录)上的评分器(grader)。

评分器(也叫 evaluator 或 scorer)是对智能体表现进行打分的任何东西——从简单的代码检查到由 LLM 扮演的裁判。trace 是一次智能体运行的完整记录:输入、它采取的每一步、以及输出。

例如,顾客问“我的退款到哪了?”智能体调用了 search_orders,然后调用 issue_refund,并回复“退款已发送,3–5 天内到账。”下面是这次运行作为 trace 的样子:

trace 的解剖:顾客的输入、智能体的 search_orders 和 issue_refund 工具调用、以及最终回复

如果没有 trace,就很难运行 evals。所以如果你还没有建立可观测性,先从那里开始。

其他所有东西都是为了让 trace 送到评分器面前、并对分数做点有用处理的“管道”:

- Case(也叫数据集条目、示例或测试用例):包含你想测试的输入,以及对该输入“良好”的定义。Case 存放在 dataset(数据集)中。有些 case 带有参考答案,另一些则只指定在 case 运行后应该应用哪些评分器。
示例:输入“我的退款到哪了?”来自一个订单 #4821 有待处理退款的顾客,期望回复是“您订单 #4821 的退款已发出,预计 3–5 个工作日到账。”
- Run(也叫实验):数据集中的每个 case,针对你的 prompt 或智能体的某个具体版本执行一次。每个 case 会产生一条 trace,每条 trace 都会被打分。一次 run 就是一个快照:“版本 X 在数据集 Z 上得分为 Y。”通过对比不同 run 的结果,你可以了解对智能体的改动是否真的有帮助。
示例:你的退款数据集里共 40 个 case,对当前智能体跑一遍。如果 v3 通过了 36/40,而一次 prompt 改动使 v4 通过了 39 个,那么这个改动有帮助,并且你能精确看到是哪个 case 仍然失败。

给智能体打分的四种方式

评分器分为几个家族,大多数真实环境会在同一条 trace 上跑多种评分器。下面看看每种评分器如何给前面那个退款 trace 打分。

确定性检查(Deterministic checks)

纯代码,不涉及模型。智能体是否恰好调用了一次 issue_refund,并且用的是它查到的同一个订单 ID?重复退款或退错了订单会立刻失败。它们廉价、快速、且无歧义,所以能用就用。

参考答案评分器(Reference-answer graders)

将输出与 case 上存储的参考答案进行比较,可以是精确匹配,也可以让 LLM 裁判检查语义是否一致。case 中存着“您订单 #4821 的退款已发出,预计 3–5 个工作日到账。”,然后裁判检查“退款已发送,3–5 天内到账。”是否表达相同含义。它们需要预先写好参考答案,而真实流量不会自带参考答案,所以它们大多运行在你编写的测试 case 上。

评分标准评分器(Rubric graders,风格、语气、诊断)

LLM 裁判依照一组标准对输出打分,不需要参考答案。回复是否简洁?是否确认了发生了什么?是否告诉用户如果退款没到应该怎么做?最后一个问题其实会标记出我们的示例——这正是关键:评分标准能捕获参考答案会漏掉的问题。因为它们不需要参考答案,所以可以用于任何 trace,包括生产环境的 trace。

轨迹评分器(Trajectory graders)

它们打分会看智能体采取过的步骤,而不只是最终回答。它在退款前是否查过订单?一个不查订单就调用 issue_refund、或者调用了十二次 search_orders 的运行,即使最终回复看起来完美,也会失败。它们运行起来更慢、更贵,而且评分器本身也更容易出错。

也需要检查 LLM 裁判

LLM 裁判本身需要验证。裁判也不过是另一个 prompt,它也可能犯错。在信任上面那个评分标准裁判之前,让一位同事人工标记几十条退款回复为通过或不通过,然后测量裁判与人工的一致率。如果一致率低,先修正裁判的评分标准,再让它的分数去驱动任何决策;并且每次你改动裁判后都要重新检查。

在线 eval 和离线 eval 有什么区别?

主要区别在于你要打分的 trace 来自哪里。

- 在线(Online):一个真实用户或系统已经在生产环境中使用了你的智能体,所以 trace 已存在,你只需打分别。
- 离线(Offline):还没有人运行过这个场景,你需要一个测试 case 来触发一次运行以产生 trace,然后打分。

两种情况下 trace 看起来是一样的。其他大多数差异,比如能用哪些评分器、能控制多少,都源于它的来源。

在线 eval 监视生产。评分器自动运行在真实 trace 上,通常取样本以控制 LLM 裁判的成本,它们的任务是暴露不良行为。生产环境通常没有参考答案,所以在线评分依赖确定性检查、评分标准、轨迹评分器和用户信号(如点踩或重试)。

离线 eval 在你的实验室运行:按需运行或跑在 CI 中,在任何东西发布之前。它们的任务是测试预期行为,让你安全地迭代修复。你是编写 case 的领域专家,所以你可以附加参考答案。

两者都需要。只有在线而没有离线,你能看到问题但无法安全修复;只有离线而没有在线,你只是在测试你想象出来的场景,而不是用户真正产生的场景。

连接两者的是提升(promotion):把一条不好的生产 trace 转变为一个离线 case。

什么是 eval 驱动开发?

eval 驱动开发是针对智能体行为的测试驱动开发。在你触碰 prompt、指令或技能定义之前,先写下“正确”是什么意思,然后运行智能体,迭代直到智能体表现得正确。

case 和“正确”从下面两个来源之一进入循环:

- 主动式(Proactive):在构建某行为之前先定义它。这也是在你还没有任何生产流量时入门的方式:为你想要的行为写 case,也为你相信自己已经拥有的行为写 case。
示例:当顾客询问待处理退款时,智能体应该查询订单、发出退款,并告诉顾客何时能到账。
- 反应式(Reactive):在线评分器或用户在生产环境标记出一条糟糕的 trace,你把它提升为一个 case。
示例:一个真实顾客问他们的退款在哪,智能体说它帮不了。你把这条 trace 提升为一个 case,保留相同输入,并定义正确行为:查询订单、发出退款、告诉顾客何时能到账。

无论哪种方式,你最终都需要定义一个行为。从那里开始,循环是相同的:

1. 定义 case 及其通过标准。 写下输入并挂上评分器。参考答案评分器需要参考答案;评分标准评分器只需要标准。
2. 运行它,看着它失败。 如果你还没做任何改动它就通过了,要么这个行为已经生效,要么这个 case 并不能按你以为的那样测试目标。对于被提升的 trace,这一步还能确认你能离线复现失败。
3. 改动智能体。 prompt、型号、指令、工具描述、技能。
4. 再次运行。 重复第 3 和第 4 步,直到它通过。
5. 跑完整数据集,而不仅仅是这一个 case,以捕获回归。

事先决定什么算通过。同一个输入可能这次通过、下次不通过(没错,多亏了非确定性的 LLM!),所以一次绿色勾号说明不了太多。每个 case 跑若干次(k),并明确设定标准:高风险行为用全通过(all-of-k),比如对别人的订单发起退款,只要一次失败就足以证明它可能发生(k 次全部通过并不能证明它不会发生,但失败一次能证明它会);或者用 n-of-k,比如 2/3,适用于允许一定方差的情况。

把生产失败变成 case

提升(promotion)是在线和离线之间的桥梁。找到被标记的 trace(如果没有 tracing 和在线 eval,你是很难抓到这些的!)并把它的输入复制到一个新 case 中。然后写下好的表现是什么样的。显然,糟糕的输出不能作为你的参考答案,所以必须有人提供正确的。

被提升的 trace 是真实用户数据,所以在进入整个团队都能读到的数据集之前,要去掉任何敏感信息。

让 case 更易编写

每个 eval 系统或提供商都有不同的创建 case 和数据集的方式:JSON、CSV、通过 UI 等。帮自己和团队一个忙:创建一个能根据自然语言轻松为你创建 case 的技能(skill)。你应该能够对 Claude 或 Codex 说下面任意一句,然后得到提供商规定格式的数据集:

- “为顾客对超过 90 天的订单要求退款的情况创建一个 case。智能体应该解释退款窗口,不应调用 issue_refund。”(主动式 case)
- “从生产 trace 创建 case:{flagged_trace_id}。智能体当时说它帮不了;它应该查询订单、发出退款、并告诉顾客何时能到账。”(反应式 case)

构建回归测试套件

eval 驱动开发循环的第 5 步说要跑完整数据集,因为一个 prompt 改动修复了一个 case 却可能悄悄破坏其他 case。如果你只重跑正在修的那个 case,你就看不到这一点。

在每次有意义的改动上运行整个数据集,理想情况下放进 CI,并与上一次运行比较。随着时间推移,你提升的每个生产事故都会变成永久测试。你的数据集会变成你的智能体曾以哪些方式失败的记录,以及如果这些失败卷土重来能抓住它们的检查。

结合起来,在线 eval 发现问题,提升把它变成 case,循环在离线修复它,而不断增长的数据集确保它一直保持修复状态。

起步团队需要知道什么

定义好的表现是真正的工作。 在构建之前先写下预期行为,包括你相信自己已经有的行为。当你这样做时,你经常会发现其中一些并不成立。

让创建 case 几乎毫不费力。 如果把一个生产失败变成 case 需要好几个界面和手写 JSON,人们就会跳过它,你的数据集也不会再增长。给团队一个点击即可提升 trace 的按钮、一个用来写预期行为的输入框,以及一个像上面那样的 case 编写技能。

尽早给路径打分。 一个需要 30 轮才能用一次得当的工具调用完成任务的智能体可能仍会给出完美的答案,而只看输出的评分器永远不会标记它。从一开始至少加入一个轨迹检查。

四大主流平台速览

有了这个模型,主流平台看起来相似得多。它们构建的构件相同,只是叫法不同:

- LangSmith:case 叫 examples,评分器叫 evaluators,run 叫 experiments。在线 evaluator 通过规则运行在生产 trace 上,失败的 trace 可以直接加入数据集。
- Langfuse:case 叫 dataset items,评分器结果叫 scores,run 叫 experiments(以前叫 dataset runs)。LLM-as-a-judge 评估器可以对实时 trace 打分,生产 trace 可以直接加入数据集。
- Braintrust:case 叫 records,评分器叫 scorers,run 叫 experiments,生产 trace 叫 logs。在线评分规则通过采样和筛选对日志打分,记录的 trace 可以直接提升到数据集中。
- Arize(AX 和开源 Phoenix):case 叫 examples,评分器叫 evaluators,run 叫 experiments。生产 spans 可以直接添加到数据集。

它们都支持某种形式的提升,因为从生产回到数据集的路径是任何 eval 设置的核心。

这个领域名字变化很快,所以在你投入之前,先查看每个工具当前的文档。

下一篇:当你无法复现一条 trace 时

这套模型假设你可以在离线状态下复现一条 trace。对于简单 prompt,你能做到:把输入复制到 case 里并运行。但对于从真实系统读取或写入真实系统的智能体,你往往做不到。这正是面向真实世界智能体的 eval 设置变难的地方,也是本系列接下来的两部分要讲的内容:

- 第 2 部分:回放世界状态。你的智能体的行为取决于当时其工具返回了什么。当退款 trace 运行时,search_orders 显示订单 #4821 的退款仍待处理。一周后重新运行同一输入,退款已经完成了,所以智能体会走不同路径,你提升的 trace 就无法复现。
- 第 3 部分:在离线运行中拦截写入。issue_refund 会移动真实资金。你想离线评估智能体处理退款的好坏,而不是每次 CI 运行都真实退款。

阅读原文
📚 相关主题 AI开发教程

订阅 AI Pulse

每天 08:00 · 12:30 · 18:30 · 23:50 更新