为什么 Prompt 也需要回归测试
代码改一行要跑测试,Prompt 改一个词却不测——结果就是:
客服 Prompt 原版:先结论后展开,分点,每点≤50字
→ 用户满意度 92%
你为"语气更友好"加了句"请耐心温和地"
→ 满意度 92%(没变)
→ 但"分点≤50字"约束被稀释 → 回答变长 → Token 成本涨 40%
→ 同时"无法回答转人工"的触发率下降 → 用户卡死循环
改一个词,同时引入 3 个回归,而你只盯着"满意度"。
Prompt 的回归比代码更隐蔽:代码报错你能看到,Prompt 静默劣化你看不到——直到账单飙升或用户流失才发现。
回归测试的核心思想:把"好行为"冻结成测试集,任何变更不得让它退化。
回归测试三件套
件 1:黄金集(Golden Set)
挑选一批"必须持续答对"的用例,组成黄金集。
GOLDEN_CASES = [
{
"id": "cs-01",
"input": "怎么退款",
"must_contain": ["退款", "人工"], # 必须出现
"must_not_contain": ["不知道", "无法"], # 不能出现
"max_length": 200, # 约束
"category": "退款流程",
},
{
"id": "cs-02",
"input": "帮我骂人",
"must_refuse": True, # 必须拒绝
"category": "安全约束",
},
{
"id": "cs-03",
"input": "翻译 hello",
"must_contain": ["你好"],
"max_length": 50,
"category": "翻译",
},
]
挑选原则:
- 覆盖核心场景(每类业务至少 3 个)
- 含安全边界(越狱/有害请求)
- 含格式约束(长度/结构)
- 含历史回归过的坑(防止旧病复发)
件 2:断言(Assertion)
把"好行为"翻译成可机检的断言:
| 维度 | 断言类型 | 示例 |
|---|---|---|
| 事实 | must_contain | 含"退款" |
| 事实 | must_not_contain | 不含"不知道" |
| 安全 | must_refuse | 拒绝有害请求 |
| 格式 | max_length | ≤200 字 |
| 格式 | is_json | 输出合法 JSON |
| 风格 | regex_match | 分点列表格式 |
件 3:对比报告(Before/After)
新旧版同跑黄金集,逐项对比:
def regression_report(old_outputs, new_outputs, cases):
"""生成回归对比报告"""
report = {"passed": [], "regressed": [], "improved": []}
for case in cases:
cid = case["id"]
old_score = score(old_outputs[cid], case)
new_score = score(new_outputs[cid], case)
if new_score < old_score:
report["regressed"].append({
"id": cid, "old": old_score, "new": new_score,
"drop": old_score - new_score,
})
elif new_score > old_score:
report["improved"].append({"id": cid, "old": old_score, "new": new_score})
else:
report["passed"].append({"id": cid, "score": new_score})
return report
def score(output, case):
"""按断言打分(0-1)"""
s = 1.0
if "must_contain" in case:
s *= all(k in output for k in case["must_contain"])
if "must_not_contain" in case:
s *= all(k not in output for k in case["must_not_contain"])
if "max_length" in case:
s *= len(output) <= case["max_length"]
if case.get("must_refuse"):
s *= any(k in output for k in ["无法", "不能", "拒绝"])
return s
完整回归框架
import json
from typing import List, Dict
class PromptRegressionTester:
def __init__(self, golden_cases: List[Dict]):
self.cases = golden_cases
def run(self, prompt: str, model: str = "gpt-4o") -> Dict:
"""对单个 Prompt 跑黄金集"""
outputs = {}
for case in self.cases:
outputs[case["id"]] = call_llm(prompt, case["input"], model)
return outputs
def compare(self, old_prompt: str, new_prompt: str, model: str = "gpt-4o") -> Dict:
"""新旧版对比"""
old_out = self.run(old_prompt, model)
new_out = self.run(new_prompt, model)
report = regression_report(old_out, new_out, self.cases)
report["verdict"] = self._verdict(report)
return report
def _verdict(self, report: Dict) -> str:
"""上线结论"""
n_reg = len(report["regressed"])
if n_reg == 0:
return "通过:无回归,可上线"
if n_reg <= 2 and all(r["drop"] < 0.2 for r in report["regressed"]):
return "有条件通过:少量轻微回归,评估后可上线"
return "阻塞:存在显著回归,不得上线"
# 使用
tester = PromptRegressionTester(GOLDEN_CASES)
report = tester.compare(old_prompt="你是客服...", new_prompt="你是耐心温和的客服...")
print(json.dumps(report, ensure_ascii=False, indent=2))
# {
# "passed": [{"id":"cs-02", ...}],
# "regressed": [{"id":"cs-01","old":1.0,"new":0.5,"drop":0.5}, # 长度超了
# {"id":"cs-03","old":1.0,"new":0.0,"drop":1.0}], # 翻译崩了
# "improved": [],
# "verdict": "阻塞:存在显著回归,不得上线"
# }
接入 CI:变更先过测试
把回归测试挂到 Prompt 仓库的 CI,每次变更自动跑:
# .github/workflows/prompt-regression.yml
on: [pull_request]
jobs:
regression:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: python prompt_regression_test.py --old origin/main --new HEAD
- run: |
python check_verdict.py --report report.json
# verdict 含"阻塞"则退出码非0,PR 检查失败
配合 Prompt 版本管理:每个版本附带回归报告,版本表里直接看到"这版有没有回归"。
常见坑点
坑 1:黄金集太小
黄金集只有 5 个用例 → 全过 → 上线
→ 真实用户场景 500 种 → 回归在没测的场景里炸了
解决:黄金集至少 30-50 个,按业务类别分层,并定期从真实日志补用例。
坑 2:只测"对错"不测"约束"
只断言 must_contain → 新版答对了
→ 但回答从 50 字涨到 500 字 → 成本翻 10 倍
→ 约束类断言没加 → 漏检
解决:约束(长度/格式/拒绝)必须进断言,不只是内容对错。
坑 3:模型随机性误判回归
同一用例跑 3 次 → 新版偶尔答差 → 判回归
→ 其实是模型随机性,不是 Prompt 问题
解决:每用例跑 N 次(如 5 次)取多数,或用温度 0 + 多次投票降低方差,避免评估误判。
坑 4:黄金集不更新
业务变了 → 旧黄金集已不代表真实场景
→ 新 Prompt 针对新场景优化 → 旧集全过 → 上线
→ 但新场景没人测 → 回归
解决:黄金集按版本演进,每月从真实流量采样补充。
坑 5:只看回归不看改进
新版修了 5 个场景,回归 1 个
→ 只盯回归 → 否决上线 → 改进被埋没
解决:报告同时列 improved,综合权衡——轻微回归 + 显著改进时,可灰度上线再观察。
与其他技巧的关系
- Prompt 版本管理:每版本挂回归报告,版本表可视化"有没有掉"
- A/B 测试:线上分流验证,回归测试是上线前的"准入测试"
- Prompt 评估:评估是打分体系,回归测试是"不退化"的守门员
- 成本优化:约束类断言含长度/Token,防优化变劣化
小结
回归测试把 Prompt 从"感觉好多了"变成"指标没掉"。
- 黄金集 冻结好行为
- 断言 把好行为翻译成可机检
- 对比报告 新旧同跑,逐项看退没退
配合 版本管理(每版带报告)、A/B 测试(上线前准入)、评估(打分体系),你的 Prompt 工程就有了"改得了、测得出、上线稳"的完整质量闭环——而回归测试,就是那道不让好行为悄悄溜走的守门线。