没有基线评测集的提示词修改就是赌博。最小可行的方案只需要三样东西:20–50 条真实测试用例、一个能自动跑的判分器、以及每次改动前的版本快照。 Anthropic 在其企业实践里把话说得很直白——评估是迭代的前提,没有扎实的评测,你既不知道这次改动是变好还是变坏,也没有基础设施去应对下一次模型升级。
为什么提示词必须版本化
代码有 Git,提示词往往只有一个聊天框里的文本。这带来三个具体问题:
- 改动不可回溯。上线后指标掉了,说不清是哪次改动引入的。
- 影响面不可知。改一句系统提示,可能让某个低频场景彻底失效,而你在高频场景里完全看不出来。
- 模型会变。底座模型升级后,上个月还稳定的提示词可能行为漂移,没有回归测试就只能在用户投诉里发现。
Anthropic 在 skill-creator 的更新说明里把这件事讲得更具体:跑 eval 有两个核心用途——捕获质量回归,以及判断模型通用能力是否已经超过你的提示词(如果不开 skill 模型也能通过你的 eval,说明这套提示词的技巧已经被吸收进模型默认行为了)。
最小可行的三层结构
| 层 | 存什么 | 形式 | 更新频率 |
|---|---|---|---|
| 版本层 | 提示词全文快照 + 变更说明 + 关联指标 | Git 仓库里的目录或数据库记录 | 每次改动 |
| 用例层 | 输入样本 + 期望约束(不是标准答案) | JSONL / CSV | 每周补充 |
| 判分层 | 规则校验 + LLM 判分 + 抽样人工 | 脚本 + 评分表 | 随用例演进 |
关键点:用例里存的是约束而不是标准答案。「必须包含订单号」「不得编造退款政策」「分类必须落在给定枚举内」这类可判定的约束,比「输出应该像这段话」稳定得多。
六步落地流程
- 先攒用例,再改提示词。从真实日志里抽 20–50 条样本,覆盖高频场景和三种边界情况。用例先于提示词,否则你会按当前输出去写期望,等于给错误行为背书。
- 给每条用例写可判定约束。格式校验、枚举归属、必含字段、禁用表述,能写成代码判定的一律写成代码判定。
- 跑基线并存档。当前提示词在用例集上跑一遍,把通过率、失败清单、耗时全部存下来,这就是版本号 v1 的基线。
- 改提示词必须带版本号和改动说明。一次只改一个意图,禁止「顺手优化一下措辞」这种无法归因的改动。
- 新旧版本跑同一套用例,逐条对比。重点看「新版本过了哪些老版本没过的」和「新版本新增了哪些失败」——后者才是回归。
- 留一份留出集。用另外 10–20 条从未参与调优的样本做最终确认,防止你把提示词调到只适配评测集。
判分方式怎么选
| 方式 | 适用 | 成本 | 坑 |
|---|---|---|---|
| 代码校验 | JSON 合规、枚举、必含字段 | 极低 | 只能判形式,判不了语义 |
| 规则 + 正则 | 禁用词、长度、格式 | 低 | 容易误杀同义表达 |
| LLM 判分 | 语义相关性、语气、完整性 | 中 | 判分标准要写死,否则自身不稳定 |
| 人工 5 分制 | 主观质量、最终把关 | 高 | 样本量小,只适合抽样 |
Anthropic 控制台的 Evaluate 功能正是把这几种组合起来:自动生成或导入测试用例、一键跑全量、两版提示词并排对比输出、还能让领域专家按 5 分制打分。自己搭时照这个组合就够用。
一个最小回归测试骨架
# cases.jsonl 每行:{"id":1,"input":"...","must_include":["订单号"],"enum":"退款|换货|咨询"}
import json, subprocess, hashlib, pathlib
def load(p): return [json.loads(l) for l in open(p, encoding="utf-8") if l.strip()]
def judge(out, c):
if c.get("must_include"):
if not all(k in out for k in c["must_include"]): return False, "missing"
if c.get("forbid") and any(k in out for k in c["forbid"]): return False, "forbidden"
if c.get("enum") and out.strip() not in c["enum"].split("|"): return False, "enum"
return True, ""
def run(prompt_path, cases):
prompt = pathlib.Path(prompt_path).read_text(encoding="utf-8")
ver = hashlib.sha1(prompt.encode()).hexdigest()[:8] # 内容哈希即版本号
passed, fails = 0, []
for c in cases:
out = subprocess.run(["llm", "--prompt", prompt, "--input", c["input"]],
capture_output=True, text=True).stdout
ok, why = judge(out, c)
if ok: passed += 1
else: fails.append({"id": c["id"], "reason": why})
return {"version": ver, "rate": passed / len(cases), "fails": fails}
old = run("prompts/v1.md", load("cases.jsonl"))
new = run("prompts/v2.md", load("cases.jsonl"))
print("v1", old["rate"], "v2", new["rate"])
print("新增失败:", [f for f in new["fails"] if f not in old["fails"]])
什么时候该删掉这段提示词
评测的另一个价值是告诉你哪些提示词已经没必要存在。按 Anthropic 的说法:如果底座模型在不加载你的提示词时也能通过 eval,说明这些技巧已经被模型吸收,继续保留只会增加 token 和维护成本。做法是每季度把关键提示词做一次「空跑对照」——关掉提示词跑同一套用例,通过率接近就考虑下线。
和智能体其他模块怎么衔接
提示词版本化不是孤立的:判分依赖输出结构稳定,因此需要 智能体结构化输出与校验重试;上线后的指标漂移要靠 智能体可观测性与 trace 才能定位;评测体系的整体搭法见 AI 智能体评测与上线后监控;而提示词本身属于上下文工程的一部分,可对照 上下文工程的六个动作。






评论 (0)