Prompt 版本管理 & A/B 测试
昨天(Day 18)你学了「上下文工程」——怎么把有限的窗口塞得刚刚好。今天更进一步:怎么把你写的 Prompt 当"正经代码"一样管起来,改了能回退、能对比新旧哪个更好。这也为后面所有阶段(RAG、Agent、评测)打底——因为 Prompt 会被你改无数次。
为什么 Prompt 也要"版本管理"
为什么这事重要到要单独讲一天?因为 Prompt 有三个"要命"的特点:
- 它对结果影响巨大:同一个模型,Prompt 差一句,成功率可能从 60% 掉到 30%。
- 它很脆:你以为"顺手优化"的一句话,可能悄悄破坏了另一类问题的表现。
- 它会被改无数次:一个真实项目里,一条核心 Prompt 迭代几十版很正常。
所以专业做法是:把 Prompt 当代码一样,纳入版本控制。改动有记录、能 diff(看两版差在哪)、能回退。这正是面试时"你怎么迭代 Prompt 的?"这类问题想听到的答案。
第一步:把 Prompt 从代码里"抽"出来
最简单可行的做法:给每一版 Prompt 存一个文件,文件名带版本号。比如建一个 prompts/ 文件夹:
prompts/
├── summarize_v1.md # 第 1 版:最初的总结提示词
├── summarize_v2.md # 第 2 版:加了"分点输出"
└── summarize_v3.md # 第 3 版:又加了"控制在 100 字内"
代码里不再写死 Prompt,而是读文件:
# 读取指定版本的提示词文件,返回里面的文字
def load_prompt(name: str) -> str:
# open 打开文件;encoding="utf-8" 保证中文不乱码
with open(f"prompts/{name}.md", encoding="utf-8") as f:
return f.read() # read() 读出全部内容为字符串
# 想用哪一版,改这一行就行,代码逻辑一个字不用动
system_prompt = load_prompt("summarize_v2")
print(system_prompt) # 打印出来确认读对了
summarize_v2 切到 summarize_v3,结果用户投诉"总结太短、丢信息"。你不用手忙脚乱回忆改了啥——直接把代码那行改回 load_prompt("summarize_v2"),一分钟恢复。旧菜谱一直在,随时贴回去。
模板 + 变量占位:一张菜谱套无数道菜
{article},运行时再把真实内容填进去。文件里这样写({...} 是占位符,先空着):
# prompts/summarize_v2.md 文件的内容大概长这样:
# 你是一名编辑,请把下面的文章总结成 3 个要点,每点一句话。
# 文章:
# {article}
代码里用字符串的 .format() 把真实文章填进去:
template = load_prompt("summarize_v2") # 读出带 {article} 的模板
real_article = "今天 A 公司发布了新款手机,售价 3999 元……"
# .format 会把模板里的 {article} 替换成 real_article 的内容
prompt = template.format(article=real_article)
print(prompt) # 现在 {article} 已经被真实文章填满了
👶 小白:那我把 Prompt 拼成一个大字符串直接用不行吗?为什么非要"模板+占位符"?
👨🏫 老师:能跑,但会乱。分开之后,"固定的话术"(版本要管的部分)和"每次变化的数据"(文章内容)彻底分家。你调 Prompt 时只动模板文件,不碰业务数据;而且这也是明天(Day 20)"指令与数据分离"防注入的地基。一举两得。
A/B 测试是什么:让两版菜谱同台竞争
A/B 测试的三个要素,缺一不可:
- 同一批题目:拿完全一样的一组输入分别喂给 A、B(不能 A 用简单题、B 用难题)。
- 同一把尺子:用同一个评判标准给两版打分(下一讲讲怎么打)。
- 只变一处:A 和 B 最好只差你要验证的那一点,否则分出高低也不知道是哪句话的功劳。
动手把 A/B 跑起来(小样本对比)
下面这段代码演示"骨架"(模型调用先用假函数占位,你把 ask_llm 换成 Day 11 学的真实调用即可):
# ---- 1. 准备一个小测试集:每道题 = 输入 + 我们期望里应出现的关键词 ----
test_set = [
{"article": "A 公司发布新手机,售价 3999 元", "must": ["手机", "3999"]},
{"article": "B 队 3:0 战胜 C 队夺冠", "must": ["夺冠", "3:0"]},
]
# ---- 2. 读两个版本的模板 ----
prompt_A = load_prompt("summarize_v2")
prompt_B = load_prompt("summarize_v3")
# ---- 3. 一个简单的"打分尺子":期望关键词命中越多分越高 ----
def score(answer: str, must: list) -> int:
# sum(...) 对列表里每个 True 计 1 分;kw in answer 判断关键词是否出现
return sum(kw in answer for kw in must)
# ---- 4. 让某一版 Prompt 跑完整个测试集,返回总分 ----
def run(prompt_template) -> int:
total = 0
for case in test_set:
prompt = prompt_template.format(article=case["article"])
answer = ask_llm(prompt) # 调模型(Day11 的真实调用换到这里)
total += score(answer, case["must"])
return total
print("A 版总分:", run(prompt_A))
print("B 版总分:", run(prompt_B)) # 分高的那版胜出
A 版总分: 3、B 版总分: 4。满分是 2 题 × 2 个关键词 = 4 分。说明 B 版在这批题上把关键信息保留得更全 → 你更有底气把线上切到 B。这就是"用数据说话",而不是"我觉得 B 好像好点"。
怎么判断"更好"?以及记好实验账
没有万能尺子,但常用的"尺子"就这么几把,按任务挑:
| 尺子 | 怎么量 | 适合的任务 |
|---|---|---|
| 格式合规率 | 输出能不能被程序解析(如是否是合法 JSON) | 结构化输出、function 参数 |
| 关键词/要点命中 | 该出现的信息出现了几个(本课 L05 用的) | 总结、抽取 |
| 人工抽查打分 | 人按 1-5 分打,或"A 好/B 好/平" | 写作、对话质量 |
| 模型当裁判 | 让另一个模型按标准打分(阶段9 D50 细讲) | 大批量、难量化的主观任务 |
更关键的一步:把每次实验记成账。别跑完就忘,攒成一张表,你会越改越有方向:
import csv, datetime
# 把一次 A/B 结果追加进 experiments.csv("a" = append 追加不覆盖)
def log_result(version, total, note):
with open("experiments.csv", "a", newline="", encoding="utf-8") as f:
w = csv.writer(f)
# 记:时间、版本号、得分、这版改了啥
w.writerow([datetime.date.today(), version, total, note])
log_result("summarize_v3", 4, "加了'控制100字内'") # 一行账
👶 小白:才 2 道测试题,B 赢了就能上线吗?会不会是运气?
👨🏫 老师:好问题!2 道太少,容易被偶然带偏——就像只请 2 位客人试吃说明不了什么。原则是题目越多、越接近真实场景,结论越可信。入门先攒 10-20 道覆盖常见情况的题;分数差得很小(比如 14 vs 15)时别急着换,可能只是噪声。这套"攒测试集"的思路,正是阶段9「评测」要放大的核心,今天先埋下种子。
今日小结 + 动手 10 分钟
🧠 今天你应该能回答
- 为什么 Prompt 要版本管理?(影响大、很脆、改得频繁,得能追溯和回退)
- 怎么把 Prompt 抽成文件、用 git 管起来?读文件而不是写死在代码里。
- 模板 + 占位符
{变量}解决什么?固定话术与变化数据分家。 - A/B 测试三要素是什么?同一批题、同一把尺子、只变一处。
- "更好"能用哪些尺子量?为什么测试题不能太少?
✋ 动手(约 10 分钟)
把你 Day 16-17 写过的任意一个 Prompt,做成"可对比两版"的迷你实验:
# 1. 建目录,存两版提示词(v1 原版,v2 你改一句话)
mkdir prompts
echo "把下面文章总结成3点:{article}" > prompts/sum_v1.md
echo "把下面文章总结成3个要点,每点不超过15字:{article}" > prompts/sum_v2.md
# 2. 用 git 把它管起来(Day06 学过),以后每次改都 commit 一下
git init && git add prompts && git commit -m "prompt v1 v2"
然后照 L05 的代码,准备 3-5 道你熟悉的测试题,两版各跑一遍、打分、比总分,把结果 log_result 记进 csv。体会一次"用数据决定用哪版"的感觉。