提示词上线前,用黄金集替代几次试跑
文章指出,少量样例的一次成功不足以证明提示词可靠,建议重复运行并统计通过率。评测集应覆盖不同失败模式,同时保留不参与提示词调优的独立样本,并设置离线检查关卡。
AI 深度解读
改一行 system prompt,三个例子效果更好,两天后退款流程却坏了。作者用这次经历提醒团队:冒烟测试跑通几条样例,只能说明某几次输出没出错,不能证明一次提示词修改值得上线。
作者给出的办法很轻量:按故障类型挑选黄金集,对每条输入重复运行,尽量用确定规则检查,再让 CI 拦住样本不足或出现失败的条目。它适合防回归,但不能提供统计意义上的可靠性保证。对正在把 AI 功能接进业务的程序员,这个区分比追求一个漂亮的总分更实用。
同一个问题答对一次,为什么还不够
原文先拆掉两个容易混淆的概念:一次成功不等于稳定成功,样本多也不等于覆盖充分。
对同一输入,作者建议先运行五次,观察结果是否变化。这里关心的不是措辞有没有变化,而是业务要求能否反复满足。一次回答正确,只得到一个样本;重复运行,才开始看见这个条目的失败情况。
评测应该追问每条用例多容易失败,而不只是它有没有成功过。
黄金集(golden set)也应围绕故障类型组织。作者认为,40 条用例各跑五次,比 200 条各跑一次更有用;每种失败模式覆盖一条,比给同一条正常流程写十种问法更有用。这是作者提出的工程取舍,原文没有提供对照实验,不能当成普遍成立的数量关系。
例如,退款失败和取消失败是两种行为,同一个退款问题的十种改写却可能始终只覆盖一个行为。整理用例时,应先问它能发现哪类回归,再决定是否增加数量。
还要留一部分从不用于调提示词的样本。否则,团队可能只是越来越擅长通过自己反复查看的题目,却误以为系统整体变好了。
总通过率同样会遮住问题。作者用 91% 举例,指出它可能对应许多偶发失败,也可能对应某个持续失败的条目。原文没有交代具体样本数,这个数字用于说明聚合会丢失信息,不是完整的统计案例。
作者的上线规则因此落在条目上:先检查每条用例的多次结果,再判断整个修改是否通过。所有纳入门禁的条目,都必须每次通过。
让另一个模型打分,也要先检查裁判
当输出没有简单的标准答案时,团队常用 LLM judge,也就是让另一个模型判断回答是否合格。作者提醒,裁判也会受答案位置、篇幅和自我偏好影响,遭到越狱攻击后还可能更不可靠。
原文称这些偏差已有文献记录,但没有给出具体研究。能直接拿走的是它提出的校准流程:人工标注 30 条输出,让裁判对同一批输出评两次,先比较两次裁判结果,再比较裁判与人工的结果。
如果裁判两次判断有 20% 不一致,就很难把它当成稳定的尺子。这个 20% 是作者举出的判断场景,并非报告的实验结果,也不是经过验证的通用阈值。
对程序员更实用的原则是:能精确检查的,就精确检查。例如输出是否包含必要字段、某个结果是否满足明确条件,不必全部交给模型凭语言理解打分。否则,业务输出的不稳定和裁判的不稳定会混在一起,很难定位失败来自哪里。
把回归检查放进 CI,脚本究竟拦什么
作者把评测拆成两种工作。回归评测使用小集合、多次运行,尽量采用精确检查,目标是几分钟内完成;能力评测使用大集合、较少重复,更依赖模型裁判,建议每周运行,而非每次提交都跑。
下面的脚本解决的是回归门禁:读取已经生成的 JSONL 结果,拦住运行次数不足或出现失败的条目。它自身不调用模型。
#!/usr/bin/env python3
"""gate.py - block a prompt change unless every item passes every run.
Reads JSONL rows: {"item": "refund-1", "run": 0, "ok": true}
Exit 0 = ship, 1 = block.
"""
import json
import sys
from collections import defaultdict
MIN_RUNS = 3
def load(path):
seen = defaultdict(list)
with open(path) as fh:
for n, line in enumerate(fh, 1):
line = line.strip()
if not line:
continue
try:
row = json.loads(line)
except json.JSONDecodeError:
sys.exit(f"bad json on line {n}")
seen[row["item"]].append((row["run"], bool(row["ok"])))
return seen
def verdict(seen):
blocked, flaky = [], []
for item, pairs in sorted(seen.items()):
runs = {r for r, _ in pairs}
oks = [ok for _, ok in pairs]
if len(runs) < MIN_RUNS:
blocked.append((item, f"under-sampled {len(runs)}/{MIN_RUNS}"))
elif not all(oks):
flaky.append((item, f"{sum(oks)}/{len(oks)} pass"))
return blocked, flaky
def main():
path = sys.argv[1] if len(sys.argv) > 1 else "results.jsonl"
blocked, flaky = verdict(load(path))
for item, why in blocked:
print(f"BLOCK {item}: {why}")
for item, why in flaky:
print(f"FLAKY {item}: {why}")
if blocked:
print(f"\n{len(blocked)} item(s) under-sampled. Run more.")
return 1
if flaky:
print(f"\n{len(flaky)} item(s) flaky. Treat as failures.")
return 1
print("gate: pass")
return 0
if __name__ == "__main__":
raise SystemExit(main())
重点看 MIN_RUNS = 3 和 all(oks):每条用例至少需要三个不同的 run 值,而且所有记录都要通过。退出码 0 表示放行,1 表示阻断;默认文件名是 results.jsonl。
下面的固定样例用来验证门禁确实会拒绝失败结果,避免接入 CI 后只验证成功分支。
printf '%s\n' \
'{"item":"refund-1","run":0,"ok":true}' \
'{"item":"refund-1","run":1,"ok":false}' \
'{"item":"refund-1","run":2,"ok":true}' \
'{"item":"cancel-2","run":0,"ok":true}' \
'{"item":"cancel-2","run":1,"ok":true}' \
'{"item":"cancel-2","run":2,"ok":true}' > results.jsonl
python3 gate.py results.jsonl; echo "exit=$?"
注意 refund-1 的第二条记录:三次中只有两次成功,预期会出现一条 FLAKY、一个失败条目的计数,以及 exit=1。把其中的 false 改为 true,门禁就会通过。
实际调用模型、执行检查和写日志,需要团队自己接上。原文提供的生产端只是模板,按 range(3) 遍历黄金集,通过 transport(item.prompt) 获取输出,再调用 item.check(out) 或 judge(item, out),每次尝试记录一行。
调用成本也要分开看。60 条用例运行三次就是 180 次调用,门禁脚本执行很快,不代表前面的结果生成也快。作者建议限制门禁集合规模,把耗时矩阵移到较慢的执行通道。
我的判断
我赞同把小规模回归检查放到日常修改前面。相比只看几条演示结果,逐条重复运行能提供更明确的拒绝理由;相比每次提交都跑庞大的能力评测,它也更容易形成持续执行的习惯。
但三次全过只是一个严格的工程规则,不能证明失败概率足够低。作者也明确排除了合规签核所需的统计保证、缺乏可检查属性的长篇文本,以及单纯的能力测量。
脚本还有接入前必须理解的边界:它只检查文件里出现过的条目,没有核对完整用例清单。按代码逻辑,空文件也会放行。它用不同 run 值计算采样次数,却没有对重复记录去重;bool(row["ok"]) 也没有严格验证字段必须是布尔值。这些都需要由结果生产端或额外校验补上。
原文还披露文章属于 MonkeyCode 的产品推广,并提到其免费模型访问和免费服务器选项。免费额度可以降低试跑门槛,但原文没有给出额度、服务器规格或延迟数据。是否足够承担合并门禁,需要另行核实,不能从「免费」推出「稳定可用」。
能拿来做什么
- 从最近三次事故中挑十条用例,按失败行为分类,优先为可以明确判定的结果编写精确检查,每条先运行三次。
- 给提示词修改接入一组小型回归检查,逐条保留运行结果;把范围更大的能力评测安排到独立任务中,作者建议每周执行。
- 如果通过 Node 服务调用模型,就让调用层为每次尝试输出包含
item、run、ok的 JSONL,并核对用例是否齐全、运行标识是否重复、字段类型是否正确。 - 必须使用模型裁判时,先人工标注 30 条输出并重复评判,确认裁判的一致性,再决定哪些判断可以用于阻断合并。