第 7 章 非确定性测试
同一个用例,这次跑过了,下次跑挂了。代码没变、输入没变、连天气都没变——它就是挂了。这是 Agent 测试最让人崩溃的地方,也是传统测试方法论彻底失效的地方。
开篇:一条记忆合并用例的三天翻转
我有一个 eval 用例,测的是记忆合并——用户先说了一件事,后来补充了细节,Agent 应该把两次信息合并成一条记忆,而不是存两条。
这个用例的测试结果,五月初的那几天是这样的:
- 5 月 8 日下午 5:12 —— FAIL
- 5 月 8 日下午 5:17(五分钟后)—— FAIL
- 5 月 9 日早上 8:59(第二天)—— PASS
同一个用例,同一份代码,什么都没改,结果从 FAIL 变成了 PASS。
这不是个例。我翻 eval 日志,发现这种翻转到处都是:
- "静默存记忆"用例:5 月 13 日上午 9:30 FAIL,当天上午 11:24 PASS——两小时内自己翻过去了。
- "记录开销"用例:同一个时间点,9:30 PASS,11:24 FAIL——反过来了,从过变挂。
最诡异的是 LLM 裁判的判定。同一个用例的同一轮回复,裁判第一次的 reason 是"应判断为通过。但仔细看…所以通过"——纠结了半天判 PASS;第二次同样的回复,裁判直接判 FAIL,理由完全不同。
裁判自己都在摇摆,你怎么信它?
这就是 Agent 测试的核心难题:非确定性。传统软件测试的整个方法论,都是建立在"同样输入得到同样输出"这个假设上的。这个假设在 Agent 时代被彻底打破了。
为什么 Agent 的输出不确定
先说清楚为什么会这样。
Agent 的每一次决策,背后都是一次 LLM 调用。LLM 是概率模型——它不是在"计算"答案,而是在"采样"答案。同一个 prompt,模型内部的概率分布是固定的,但每次采样可能采到不同的位置。大多数时候差异很小,但偶尔会差到影响行为——比如这次它选择调工具,下次它选择直接回复。
再加上温度参数、上下文长度的微小变化、甚至请求到达服务器的时间差,都会让输出产生波动。这不是 bug,是 LLM 的本质特性。
Galileo AI 有个数据说,93% 使用 LLM 裁判的团队都遇到过重大的可靠性问题,最主要的原因就是"一致性失败"。这个"一致性失败"不是偶发的,它是系统性的——你永远无法保证"这次和上次一样"。
第一个策略:跑多次,看通过率
既然单次结果不可靠,那就跑多次。
我在 eval 框架里实现的策略是:每个用例跑 5 次,通过率 ≥ 80%(也就是 5 次里至少过 4 次)才算通过。
为什么是 5 次和 80%?这不是精确计算出来的,是试出来的。跑 3 次太少——2 比 1 和 1 比 2 都可能出现,没法区分"偶尔抖动"和"稳定失败"。跑 10 次太多——跑一轮 eval 的时间会爆炸。5 次是个平衡点:足够平滑掉偶尔的抖动,又不至于太慢。
80% 的阈值也是同理。100% 太严——有些维度(比如回复的自然程度)天生就不可能 100% 一致。50% 太松——跟抛硬币没区别。80% 意味着"绝大多数时候能做对,偶尔会抖",这是一个合理的成功标准。
这个策略的本质是:放弃"每次都对",追求"大概率对"。 这是对非确定性的第一层接纳——你不再追求确定性测试里的"通过/不通过"二元判断,而是用统计方法描述质量。
第二个策略:按维度区分,不是所有地方都需要跑 5 次
跑了一段时间之后,我发现 5 次 × 80% 这个标准对所有用例一刀切,有问题。
有些维度是高度确定性的——比如"记没记住""记了几条""有没有替代关系"。这些是查数据库的代码断言,只要 Agent 确实执行了存储操作,结果就是确定的。这些维度跑 5 次是浪费——跑 3 次甚至 1 次就够了。
真正需要跑 5 次的,是那些 LLM 深度参与的维度——比如"回复质量好不好""记忆内容准不准确""有没有被误触发"。这些维度每次都可能有波动,必须用多次运行来平滑。
所以更精细的策略是按维度区分运行次数:
高度确定的维度(数据库操作、触发判断的代码逻辑)
→ 跑 3 次,100% 通过算过
LLM 深度参与的维度(回复质量、记忆准确性、意图理解)
→ 跑 5 次,80% 通过算过诚实地说一句:这个分层策略我在方法论文档里写了,但代码里还没有完全落地——目前所有用例还是统一跑 5 次。这是"理论和实践的落差",我承认。但方向是对的,只是还需要工程时间来细化。
第三个策略:跟裁判的不确定性共处
LLM 裁判的不确定性,比 Agent 本身的不确定性更隐蔽,也更危险。
Agent 输出不稳定,你能直接看到——回复变了、行为变了。但裁判不稳定,你看不到——它每次都给你一个干净的 {"pass": true/false, "reason": "..."},看起来很权威。你很容易忘了裁判自己也是个概率模型。
我的 eval 日志里有一个特别典型的案例。同一个"记忆修正"用例的同一轮回复,裁判第一次判了 PASS,理由是一大段纠结的推理("应判断为通过。但仔细看……所以通过");第二次判了 FAIL,理由完全不同。同一个裁判,同一份输入,结论相反。
怎么应对?几个实践经验:
第一,裁判和 Agent 用不同的模型。 如果裁判和 Agent 用同一个模型,它们会有相同的偏好和盲区——Agent 容易犯的错,裁判也容易忽略。用不同模型的裁判,至少能交叉覆盖一部分盲区。
第二,裁判的 prompt 要有锚点。 别只说"判断这条回复好不好",要说"好的标准是:1)确认了改期 2)说明了新日期 3)语气自然 4)长度不超过两句"。标准越具体,裁判越稳定。模糊的标准会让裁判每次"理解"不一样。
第三,定期人工校准。 从裁判判过的结果里抽样一批,人工复核。如果发现裁判和人的判断吻合度下降,说明裁判飘了,需要调整 prompt 或者换模型。这一步不能省——它是你信任裁判的基础。
第四,接受"裁判也会错"这个事实。 别把裁判的判断当圣经。它是一个"不太稳定的同事帮你审稿"——大部分时候有用,偶尔会犯傻。你的 eval 体系要能容忍裁判偶尔判错,而不是被它绑架。
一个对照:确定性系统怎么测
讲了这么多"概率性测试"的方法,容易让人以为 Agent 项目里所有东西都是不确定的。其实不是。
我另一个项目里有一块纯计算的功能——大量数值运算和数据处理。这些东西是确定性的,跟 LLM 没有关系。同样的输入永远得到同样的输出。
这个项目的测试方式和 Agent 项目完全不同。它的测试目录里全是传统单元测试:
def test_rsi_calculation():
# 固定随机种子,消除不确定性
np.random.seed(42)
data = np.random.randn(100)
rsi = calculate_rsi(data, period=14)
assert rsi[-1] == pytest.approx(58.3, abs=0.1)你看,np.random.seed(42) 一行就把随机性钉死了。测试跑一次就够了,通过就是通过,不需要跑 5 次看通过率。这是确定性系统的测试方式——传统方法论在这里完全适用。
两套测试哲学,对应两类系统:
| 确定性系统(算法/计算) | 概率性系统(Agent/LLM) | |
|---|---|---|
| 输出是否可复现 | 是,固定种子后完全可复现 | 否,每次可能不同 |
| 测试方式 | 单元测试,跑一次断言 | eval,跑多次看通过率 |
| 判定标准 | assertEqual(精确匹配) | 通过率阈值(如 80%) |
| 回归检测 | 输出变了就是退化 | 通过率下降才是退化 |
| 随机性处理 | 固定种子消除 | 跑多次平滑 |
一个产品里往往两种系统都有。一个典型的 Agent 产品,底层可能有确定性的计算模块(用固定种子测),上层有 LLM 驱动的交互模块(用 eval 测)。两套测试哲学并存,各管各的部分。
这一章的工具:非确定性测试检查清单
🔧 非确定性测试检查清单
如果你的 Agent 测试结果不稳定,按这个清单排查:
是不是真的不稳定?
- [ ] 同一用例、同一代码版本,连跑 3 次,结果一致吗?
- [ ] 如果不一致——是 Agent 行为变了,还是裁判判错了?(先排查裁判)
- [ ] eval 环境是干净的吗?上一个用例的残留有没有污染?(队列、缓存、文件)
应对策略
- [ ] 你的 eval 是跑一次就判生死,还是跑多次看通过率?
- [ ] 通过率阈值设了多少?合理吗?(太严会假阳性,太松会假阴性)
- [ ] 不同维度有没有区分运行次数?(确定性维度少跑,概率性维度多跑)
裁判可靠性
- [ ] 裁判和 Agent 是不是用了不同的模型?
- [ ] 裁判的 prompt 有没有明确的、具体的评判标准(锚点)?
- [ ] 最近有没有人工抽查校准过裁判的判断?
- [ ] 你心里清楚"裁判也会判错"这件事吗?
全局
- [ ] 你的项目里,哪些部分是确定性的,哪些是概率性的?
- [ ] 确定性部分用传统测试,概率性部分用 eval——两边都覆盖了吗?
- [ ] 有没有"确定性部分测得很好,概率性部分完全没测"的盲区?
小结
这一章的核心是一个认知转变:Agent 测试的标准,从"每次都对"变成"大概率对"。
这不是降低标准,而是承认现实。LLM 的输出天生是概率性的,你没法用传统测试的确定性框架去套它。你能做的是——跑多次、看通过率、按维度区分、跟裁判的不确定性共处。
最危险的情况不是"Agent 不稳定",而是"你以为它稳定"。单次跑通就以为没问题,裁判说 PASS 就以为真 PASS——这种虚假的确定感,比不确定本身可怕得多。
到这里,第三部分"怎么知道 Agent 能用"就讲完了。第 6 章讲怎么定义"成功长什么样"(eval-driven),第 7 章讲怎么跟"成功本身不确定"这个现实共处。两章合起来,就是 Agent 时代的测试方法论。
下一部分,我们进入一个更进阶的话题:当一个 Agent 不够用的时候,怎么让多个 Agent 协作。
下一章
第 8 章 · 多 Agent 不是银弹 —— 什么时候该引入多 Agent,什么时候不该。