提示词上线前测试:评估发现了 16% 的回归

缩短后的提示词漏掉了一条规则:在110条新消息的测试中,新版有18次未写出订单号,遗漏率为16%,旧版则没有一次遗漏。然而,此前一次仅含40行的测试并未发现任何漏写,表明单次小规模测试的零遗漏结果,不能代表缩短后的提示词在新消息中仍能完整遵守规则。

我们的推荐

实时价格
模型 结论 价格
GPT-6 Luna support-answer 的 v1 在 275 次测试调用中有 275 次给出了订单号。采用更短系统提示词的 v2 在预先确定的测试运行中,110 次调用中有 92 次给出了订单号。 两个受测版本使用的模型 低至 $0.1/百万
Claude Sonnet 5.5 以 PASS 或 FAIL 判定每个回答是否回应了客户的问题。六次测试运行的总成本为 $0.14,其中该模型的调用成本为 $0.12。 演示中的评判模型 低至 $2/百万
目录
  1. 提示词测试需要哪些内容?
  2. 如何通过代码或 CI 运行提示词测试?
  3. 测试检出了回归吗?
  4. 为什么一次测试通过也可能漏掉回归?
  5. 缩短提示词还破坏了其他要求吗?
  6. 提示词测试要花多少钱?
  7. 测试如何辅助上线决策?
  8. 本文未涵盖哪些内容?
  9. 常见问题
  10. 部署前如何测试 LLM 提示词?
  11. 提示词回归测试需要多少用例?
  12. LLM 评判能替代模式检查吗?
  13. 相关阅读

提示词上线前测试,就是让新版和当前版本处理同一批预先固定的输入,再按写好的规则逐条检查回答。我们用这个方法测试了客服提示词的 v2。这个版本缩短了系统提示词,也删掉了“始终写出订单号”这条规则。测试就是要找出这种回归,也就是让输出质量变差的改动。在此前从未用于测试的 110 条消息上,v2 有 18 条回答(16%)漏写了订单号,v1 则没有遗漏。这个差异远超随机波动所能解释的范围(Fisher 精确检验,p = 3.6 × 10⁻⁶)。此前有一次 40 条消息的测试完全没有遗漏,所以单次测试通过并不代表这个版本没有问题。

TL;DR

  • 在测试内容事先固定的 110 条回答中,support-answer 的 v2 有 18 条漏写订单号,v1 则为 0 条(p = 3.6 × 10⁻⁶)。
  • 此前的三次 40 行测试中,有一次 v2 的 40 行全部通过;最终的三个测试集中,也有一个单独使用时无法检出问题。
  • 演示脚本对 v1 发起的 275 次测试调用,回答全部写出了订单号。
  • 六次测试每次包含 15 至 20 行,总费用为 $0.14,其中评判模型的费用为 $0.12。

本文接着提示词版本管理中的示例展开,沿用其中创建的 support-answer 及其版本。v1 和 v2 都使用 GPT-6 Luna,区别只有系统提示词。在 github.com/synthorai-io/use-cases 的同一目录中运行:

cd use-cases/prompt-library
python evals.py                        # v2 against v1 on the orders and general sets, three runs each
python evals.py --set fresh --runs 1   # the test fixed before the run
python evals.py --read RUN_ID          # read back a run started in the console

本文中的测试运行于 2026 年 10 月 19 日。评判模型使用 Claude Sonnet 5.5,与两个被测版本使用的模型不同。

提示词测试需要哪些内容?

测试集由一张输入表和一组检查项组成。每行提供提示词变量的值,也可以包含用户消息和预期回答。测试运行时,会针对每行分别调用候选版本和基线版本,再用所有检查项评估每个回答。支持以下检查类型:

检查项回答满足以下条件时通过
contains / not_contains包含或不包含指定字符串
regex / not_regex匹配或不匹配指定模式(Go 的 RE2 语法)
equals_expected与该行的预期回答相同
json / schema是有效 JSON/符合该版本的输出 schema
max_latency_ms / max_cost_usd响应延迟和费用未超过限制
judge模型按你编写的标准评为 PASS

每个测试集最多包含 50 行,每个检查项都会应用于所在测试集的所有行。这决定了测试集该怎么拆分。“回答包含订单号”只适用于输入中提供了订单号的消息,所以演示将这类消息单独放在几个测试集中:

测试集测试行内容检查项
orders(先是 15 行,后为 40 行)每条消息都带有订单号,涉及配送、退货、发错商品、商品损坏和账单包含订单号(regex \bA-\d{5}\b);模型评判:回答了客户的问题
fresh orders 1 至 3另取 110 条同类消息,未出现在其他测试集中,拆成 40、35 和 35 行同上两项
general(20 行)不含订单号的消息,其中几条询问退款到账时间not_contains “refund by” 和 “business days”;延迟低于 20 秒;模型评判:回答了问题;模型评判:未承诺退款日期

judge 检查会为每个回答调用一次模型,返回 PASS 或 FAIL。它有费用,也可能判断错误,所以演示只用它检查模式匹配无法表达的要求,并将其结果与模式检查分开报告。

如何通过代码或 CI 运行提示词测试?

CI(持续集成,即每次改动合并前自动执行的检查)可以通过 API 运行提示词测试,测试过程由你的代码执行:先创建一次测试运行,再针对每行通过 prompt_version 调用两个版本,评估回答,然后将结果写回。控制台会像展示其他测试运行一样展示这次结果。演示中的循环简化后如下:

EVAL = {"X-Synthorai-Metadata": '{"source":"eval"}'}   # keeps test calls out of per-version stats

run = admin.post("/prompts/support-answer/eval-runs",
                 json={"set_id": set_id, "candidate": 2, "baseline": 1}).json()["data"]
results = []
for i, row in enumerate(rows, 1):
    entry = {"row": i}
    for side, version in (("candidate", 2), ("baseline", 1)):
        answer = call_by_version("support-answer", row, version, headers=EVAL)   # a Chat Completions call
        entry[side] = {"version": version, "answer": answer.text,
                       "checks": {"order number": bool(ORDER.search(answer.text)),
                                  "answers": judge(ANSWERS, row, answer.text)}}
    results.append(entry)
    admin.put(f"/prompts/support-answer/eval-runs/{run['id']}",
              json={"status": "finished" if i == len(rows) else "running", "done": i, "checked": i,
                    "passed": sum(all(e["candidate"]["checks"].values()) for e in results),
                    "results": {"rows": results}})

call_by_version 按 ID 调用提示词,并设置 prompt_version,用法见提示词版本管理;judge 则调用评判模型,要求返回 PASS 或 FAIL。发布期间,source: eval 标签很重要:Prompts 页面的各版本汇总统计会排除带有该标签的调用,避免测试干扰线上流量的对比。

演示用 Python 执行模式检查。要与控制台的判断完全一致,可以将检查项和回答发送到 POST /api/v1/prompt-checks/grade。服务端会使用 RE2 执行 contains、not_contains、regex、not_regex、equals_expected 和 json 检查,并返回每个回答在各检查项上的通过或失败结果。简单验证的结果符合预期:用 (?i)refund 匹配 “REFUND by Friday” 时通过,用 not_regex (?i)refund.{0,40}\bby\b 检查同一回答时失败。

控制台的 Run 按钮也采用相同方式,只是从浏览器发起:逐行调用两个版本并保存结果。有一次控制台对 40 行 orders 测试集的运行(下文的运行 10)只评估了候选版本,旁边同时展示 v1 的回答,但没有对 v1 执行检查。

控制台测试的第 37 行:v1 的回答写出了订单号 A-50037,v2 没有,因此 v2 未通过模式检查,但通过了模型评判

测试检出了回归吗?

检出了。我们在运行前就固定了测试方案:使用此前从未测试过的 110 条新消息,每行对每个版本调用一次,对全部 110 行的订单号检查结果做一次 Fisher 精确检验,显著性阈值设为 0.05。Fisher 精确检验计算的是:假设两个版本的实际表现相同,出现至少这么大差异的概率。双侧检验会同时考虑两个方向的差异。首次调用前,我们已将测试行和这条规则提交到代码仓库。结果如下:

测试集v2 写出订单号v1 写出订单号仅对该测试集计算的 Fisher p 值
fresh orders 1(40 行)33/4040/400.012
fresh orders 2(35 行)27/3535/350.005
fresh orders 3(35 行)32/3535/350.239
全部 110 行92/110110/1103.6 × 10⁻⁶

v2 在 16% 的回答中漏写了订单号。遗漏分布在各类问题中,并不集中于某一种消息:配送、退货、发错商品和商品损坏各有 22 条消息,每类都遗漏了 4 条;账单类 22 条中遗漏了 2 条。一个典型例子是:“订单 A-50918 在我的银行 App 中显示有两笔待处理扣款。”回答解释说,这两条记录可能是待处理的预授权,而不是两笔实际扣款。评判模型给出了通过,但回答始终没有提到订单号。

表中的各组 p 值只是用来描述三个测试集,并不是预先设定的那次检验。三个测试集中,有两个单独使用就能发现 v2 的问题,第三个则不能。

为什么一次测试通过也可能漏掉回归?

如果一个版本只是偶尔违反规则,整次测试也可能一次都碰不到。在上面的测试之前,演示曾在一个 40 行测试集上对 v2 和 v1 做了三次对比:

运行v2 写出订单号v1
735/4040/40
836/4040/40
940/4040/40
10(控制台)39/40演示脚本对已保存回答的评估结果为 40/40

第 9 次运行中,v2 的 40 行全部通过。如果团队只跑一次测试,恰好得到这次结果,就会把 v2 上线。这几次运行单独来看也都没有达到统计显著性:与从不遗漏的基线相比,40 行或更多行的测试至少需要出现 6 次遗漏,p 值才能低于 0.05(40 行中通过 34 行时,p = 0.026;通过 35 行时,p = 0.055)。较小的测试集所需遗漏次数更少:20 行中遗漏 5 次,p 值就已达到 0.047。

因此,确定测试规模时,应该让“出现 6 次遗漏”有较高概率,而不只是让期望次数达到 6。在遗漏率为 16% 时,48 行能让至少出现 6 次遗漏的概率达到 80%;38 行虽然预期会遗漏 6 次,但达到至少 6 次的概率只有 58%。如果遗漏率为 6.25%,也就是上述 40 行测试集的结果(160 条回答中遗漏 10 次),则需要 126 行才能达到 80% 的概率。对于重要规则,测试行数应超过单个测试集的容量,拆到多个测试集中,并提前决定如何合并结果。重复相同的测试行,不能像新增测试行那样增加证据:这些运行反复测试的是同一批消息,并非独立样本。演示中,v2 有三行在一次运行中遗漏,另一次运行又遗漏了。

缩短提示词还破坏了其他要求吗?

其他检查没有发现别的问题。general 测试集上,两个版本各运行三次:

  • 不承诺退款日期:两个版本都没有写出 “refund by” 或 “business days”。模型评判检查中,每个版本每次运行的 20 条回答最多有 2 条失败,两个版本之间没有差异。
  • 延迟:v1 在一次运行中有 2 行超过 20 秒;v2 从未超过。
  • 回答了问题(模型评判):v2 在每个测试集上的评分都更高,fresh 测试集上为 88/110,v1 为 75/110。这个比较并未提前设定,因此只能作为趋势参考:按评判模型的判断,更短的提示词回答得更直接。

控制台和演示脚本使用的评判提示词不同,控制台会要求写出理由。针对相同的 40 条消息、但来自不同运行的回答,控制台判定 v2 通过 40/40,演示脚本则判定通过 32 至 35 条。评判结果只能在同一套评判配置内比较。

提示词测试要花多少钱?

使用本演示中的模型,两个版本加一项模型评判检查,每行约花费 0.1 美分:

运行每次运行行数调用费用
对 15 行和 20 行测试集共运行 6 次15 或 20回答调用加评判调用$0.1400,其中评判调用为 $0.1198
对 40 行测试集运行 3 次40240 次回答调用 + 240 次评判调用$0.115
方案提前固定的测试110220 次回答调用 + 220 次评判调用$0.1076

评判占了大部分费用,因为每个回答都要单独调用一次更大的模型来评估。按顺序逐次调用时,一次 40 行测试耗时 10 分钟;并行调用可以缩短时间,但会占用更多限流配额。

测试如何辅助上线决策?

在控制台移动 production 标签时,对话框会在变更旁展示该版本最近一次测试运行的结果。它为操作提供参考,不会阻止操作。测试集和测试运行中的回答会加密存储,回答保留 30 天。

将 production 移至 v2 的对话框:模型和参数不变,消息发生变化;v2 最近一次测试使用 fresh orders 3,共 35 行,通过 28 行

只有一行通过测试集中的所有检查,对话框才会将它计为通过。因此,这里的 28/35 综合了模式检查和模型评判的结果。

本文未涵盖哪些内容?

  • 逐行检查配置:检查项会应用于所在测试集的每一行,因此演示将带订单号和不带订单号的消息放在不同测试集中。
  • 服务端执行测试:无论使用 API 还是控制台的 Run 按钮,模型调用都从调用方发起;目前没有在服务端执行整套测试并等待完成的端点。
  • 评判准确性:我们没有用人工评分校验评判模型,因此它的数值只能反映趋势。本文只对模式检查的结果给出明确结论。

常见问题

部署前如何测试 LLM 提示词?

让新版和当前版本处理同一批固定输入,按书面规则检查每个回答,再用事先选定的显著性检验比较通过数量。演示使用 110 条新消息和一次 Fisher 精确检验,区分出了 16% 和 0% 的遗漏率。

提示词回归测试需要多少用例?

行数要足够多,确保回归确实存在时,有较高概率至少出现 6 次规则检查失败。若要达到 80% 的概率,失败率为 16% 时需要 48 行,失败率为 6.25% 时需要 126 行。演示中,一个在主测试中有 16% 回答违反规则的版本,曾通过一次 40 行测试。

LLM 评判能替代模式检查吗?

能用模式表达的检查,不应改用模型评判。演示中的控制台和脚本使用了不同的评判提示词,对相同的 40 条消息,分别判定 v2 通过 40 条和 32 至 35 条。而模式检查对同一段文本每次都会给出相同结果。

相关阅读

← 使用场景