AI 工作流测试如何应对输出波动
文章指出,温度、上下文压力和模型版本变化都可能影响同一输入的输出。单次执行与精确字符串断言容易造成偶发失败,也可能漏掉真实问题,需要调整测试思路。
文章指出,温度、上下文压力和模型版本变化都可能影响同一输入的输出。单次执行与精确字符串断言容易造成偶发失败,也可能漏掉真实问题,需要调整测试思路。
你为自己的 AI 工作流写了一个测试。周二通过了,周三却失败了。输入相同,测试用例相同,prompt 也相同,输出却不一样。
于是你又跑了一遍。通过了。
欢迎来到非确定性系统的测试世界。大多数开发者熟悉的测试策略,都是为确定性代码设计的:相同的输入进去,相同的输出出来,测试亮起绿灯。AI 工作流并不是这样运行的。模型每次都会生成不同的响应,因为它是从概率分布中采样,而不是查一张对照表。即使输入完全相同,Temperature 设置、上下文窗口压力和模型版本更新,也都会让输出发生变化。
如果你的测试策略是“跑一次,检查输出是否匹配”,你就会把大量时间花在排查那些重跑后便消失的幽灵故障上。更糟糕的是,你还会漏掉真正的问题,因为在你测试的那一次运行中,输出碰巧看起来没问题。
当被测系统本来就会产生变化时,你可以这样开展测试。
团队最先犯的错误,就是断言 AI 输出与某个预期字符串完全一致。这种做法适用于传统代码,却不适用于 AI 工作流,因为输出是生成出来的,而不是计算出来的。
让模型总结一份文档,它每次都会使用不同的措辞。每次生成的摘要可能都同样出色,但具体用词、句子结构和长度都会变化。如果你的测试断言输出必须等于“这份文档讨论了季度营收趋势,并指出三个关键增长领域”,那么即使摘要是正确的,测试也会有十次中的九次失败。
把精确匹配断言换成可以检查的属性:
这些是基于属性的测试,而不是精确匹配测试。它们检查的是输出是否满足约束,而不是是否等于某个特定字符串。
测试一个 API endpoint 时,你不会断言整个 JSON 响应与测试样本逐字节一致。你会检查响应是否具有预期结构:字段是否正确、类型是否正确、状态码是否正确。
把同样的逻辑应用到 AI 工作流中。如果你的工作流应该返回一个包含 category、confidence 和 reasoning 字段的 JSON 对象,就这样测试:
def test_classification_output_structure():
result = run_workflow(sample_input)
assert isinstance(result, dict)
assert "category" in result
assert "confidence" in result
assert "reasoning" in result
assert isinstance(result["confidence"], (int, float))
assert 0 <= result["confidence"] <= 1
assert len(result["reasoning"]) > 10
这个测试不关心 category 是 "sales" 还是 "support"。它关心的是,工作流是否返回了格式正确、包含预期字段和类型的响应。这能告诉你工作流是否正常运转。至于具体分类是否正确,则是另一个问题。
一个测试通过一次,并不能让你对 AI 工作流了解多少。连续通过 20 次,才能说明一些问题。20 次中通过 19 次,又说明了另一种情况。
把核心测试用例运行 10–20 次,并记录通过率。如果通过率是 100%,说明工作流对该输入的表现稳定。如果通过率是 85%,说明存在可靠性问题,而且会在生产环境中暴露出来。如果通过率是 50%,这个工作流基本就是在抛硬币。
def test_workflow_reliability():
results = []
for _ in range(20):
result = run_workflow(sample_input)
results.append(is_valid_output(result))
pass_rate = sum(results) / len(results)
assert pass_rate >= 0.95, f"Pass rate too low: {pass_rate}"
这种测试能捕捉一类特定的 bug:工作流大多数时候能正常运行,但偶尔会失败。这类 bug 在生产环境中最难诊断,因为你无法让它按需复现。可靠性测试能在上线前把它们暴露出来。
测试数据通常很干净,真实数据却不是。干净的测试输入与杂乱的生产输入之间的差距,正是大多数 AI 工作流故障藏身的地方。
一个用于补全销售线索信息的工作流,用 {"company": "Acme Corp", "domain": "acme.com"} 测试时,可能每次都能通过。同一个工作流收到 {"company": "Acme Corp.", "domain": ""} 时,却可能悄无声息地产生垃圾结果。不是报错,而是看起来像有效响应的垃圾结果。
从生产数据中构建一套真实输入语料。必要时移除 PII,但要保留数据的杂乱特征:空字段、意外的字符编码、异常长的字符串、包含特殊字符的字段,以及数据格式错误的字段。用这套语料运行工作流,并检查结果。
如果你还没有生产数据,就手动构造边界用例:
, ; " ' { } [ ] < >。这些并不是凭空设想的问题。在我审查过的一个 CRM 销售线索分配工作流中,最常见的故障触发因素,就是公司名称中包含逗号。信息补全 API 会按逗号拆分,把 "Acme, Inc." 当成两家不同的公司。测试套件里从来没有包含过带逗号的公司名称。
正常路径测试回答的是:“一切顺利时,工作流能正常运行吗?”失败路径测试回答的是:“出了问题时,工作流会怎么处理?”
对于 AI 工作流,每一步都可能出问题。模型调用超时,模型返回空响应,模型返回的响应无法按预期格式解析,工具调用返回错误,数据源不可用,或者上下文窗口被填满,模型开始遗漏信息。
针对工作流中的每一步,都写一个让这一步失败的测试,并检查会发生什么:
def test_model_timeout_handling():
with patch("workflow.model_call", side_effect=TimeoutError):
result = run_workflow(sample_input)
# Does the workflow fail loudly or silently?
assert result["status"] == "error"
assert "timeout" in result["message"].lower()
# Does the next step receive something usable?
assert result.get("fallback") is not None
如果你的工作流没有为模型调用超时提供 fallback,这个测试就会失败。很好。你肯定更愿意在测试中发现这个问题,而不是在凌晨 2 点的线上事故中发现。
AI 工作流可能生成格式正确、语气笃定,却完全错误的输出。只检查结构的测试无法捕捉这种问题。你需要检查特定幻觉模式的测试。
编写测试,向工作流提供已知存在信息缺口的输入,再检查输出是否用虚构信息填补了这些缺口:
def test_no_hallucinated_contact_info():
# Input has no contact information
input_data = {"company": "Acme Corp", "domain": "acme.com"}
result = run_workflow(input_data)
# If the workflow adds contact info, it should be None or empty,
# not a fabricated email or phone number
if "contact_email" in result:
assert result["contact_email"] is None or result["contact_email"] == ""
这个测试捕捉的是一种特定故障:模型“好心”地用编造的数据补全缺失信息。这是生产环境中 AI 工作流最危险的故障模式,因为输出看起来正确,语气也很自信。只有当有人核实信息是否真实时,问题才会被发现。
模型提供商会更新模型。有时会通知你,有时不会。更新可能改变模型行为,即使你没有修改任何代码,也可能让工作流出问题。
每次更新模型之前,都要重新运行测试套件。如果以前通过的测试现在失败了,说明模型更新改变了某些东西。这不是你代码中的 bug,但仍然是你需要处理的问题。
记录测试最近一次通过时使用的模型版本。测试失败时,第一个问题应该是:“模型版本变了吗?”如果变了,你就需要调整 prompt、解析逻辑或测试预期,以适应新的行为。
如果不跟踪模型版本,你可能会花几个小时排查某次“代码变更”,而真正的变化其实发生在提供商那边。我见过一些团队花了 3 天排查一个失效的工作流,最后发现原因只是模型更新把默认 JSON 输出格式从单行改成了带缩进和换行的格式。代码没有错,模型变了。
每次在生产环境中发现 bug,都要为它写一个测试。这是传统软件开发中的常规建议。对于 AI 工作流,它更加重要,因为模型变化时,同一个 bug 可能再次出现。
AI 工作流的回归测试可以这样做:
随着时间推移,这套测试会成为你遇到过的所有故障模式的记录。当你更新 prompt、更换模型或修改工作流逻辑时,它能捕捉回归问题。它也记录了工作流已知能够处理的边界情况。
测试能在大多数问题进入生产环境之前捕捉它们。但测试也有局限:你只能测试自己想得到的输入。真实的生产流量中,总会出现没人预料到的输入。
工作流上线并开始处理真实数据后,你需要另一种保障:对工作流整体进行诊断审查,找出尚未覆盖的故障边界。这不是一份通用检查清单,而是针对你的具体工作流、具体故障模式进行的专项评估,并给出具体修复方案。
如果你正在准备上线一个 AI 工作流,希望在上线前做一次诊断,TryPromptFlow 提供顾问式审查,梳理故障边界并给出修正方案。可免费进行一次诊断,无需信用卡。
如需采取进一步行动,你可以考虑屏蔽此人和/或举报滥用行为。