提示词的第一个版本只是草稿。它往往能得到接近你想要的结果,而从“接近”到“正确”的差距,要靠迭代来弥补:有目的地修改提示词,在同一个输入上再运行一次,检查这次修改有没有帮助。如果做得随意,迭代就会变成胡乱改写措辞,直到某次碰巧得到一个看起来不错的输出。如果当成一个小实验来做,你得到的提示词就能在接下来的一百个输入上都好用,而不只是在你测过的那一个上。
这个方法有五步:定义好答案的样子,准备固定的测试输入,一次只改一处,比较输出,以及知道何时停下。
定义好答案是什么样子
在动手修改之前,先写下输出必须做到什么,写成能用“是”或“否”回答的检查项。“一条好的提交信息”是没法检查的。下面这些可以:
- 主题行少于 50 个字符。
- 遵循 Conventional Commits 格式(
fix:、feat:等)。 - 正文说明为什么需要这次修改,而不是复述 diff 已经展示的内容。
- 有对应 issue 时,提到 issue 编号。
标准有两个作用。它们告诉你要往提示词里加什么,因为每一条往往就是一条缺失的指令。它们还能防止你凭输出读起来是否顺耳来评判它,而那正是接受一个自信却错误的答案最容易的方式。
准备一小组测试输入
挑三到十个输入,在整个过程中保持不变。其中要包括典型情况、几个棘手的情况(空字段、很长的输入、提示词没预料到的语言),以及所有以前失败过的输入。只用一个例子调出来的提示词,往往只适合那个例子,换一个就失败。
把提示词写成带输入位置的模板,这样每个版本处理的都是完全相同的材料。
一次只改一处
如果你同时修改了角色、格式和示例,输出也变好了,你就不知道是哪一处修改起了作用,还可能保留了两处没用的修改。改一处,运行,比较。下面的标签页是同一个提示词的三个版本,每个版本只比上一个多改了一处。
- if len(password) > 8:
+ if len(password) >= 8:
return True更新密码长度校验
此提交更新了密码校验逻辑,把比较运算符从 > 改成了 >=。因此,长度恰好为 8 个字符的密码现在会被视为有效。这项修改提高了校验的准确性,并为创建账号的用户带来了更好的体验。
版本 1 没通过第 2 条和第 3 条标准:主题没有类型前缀,正文用更多的字复述了 diff。版本 2 加上了格式要求,通过了第 1 条和第 2 条,但正文仍然在描述 diff。显而易见的下一步修改是再加一条指令(“解释原因”),可模型没法解释一个从没有人告诉过它的原因。版本 3 改为补充了缺失的背景,于是正文和 issue 引用不用要求就自然出现了。这是迭代中最常见的教训:当一条指令修不好输出时,提示词通常缺的是信息,而不是强调。
把输出并排比较
在每个测试输入上运行每个版本,并且每个都运行不止一次,因为聊天模型是采样选词的,同一个提示词运行两次结果也会不同。把输出放在一起,对照你的标准检查,而不是对照你对上一次运行的印象。
对于精确的标准,可以先让模型再调用一次来完成第一轮评分。把输出粘贴进一个写明了标准的评分提示词:
| 标准 | 输出 A | 输出 B |
|---|---|---|
| 1. 主题少于 50 个字符 | 通过:主题为 18 个字符 | 通过:主题相同,18 个字符 |
| 2. Conventional Commits | 通过:以 “fix:” 开头 | 通过:以 “fix:” 开头 |
| 3. 正文说明原因 | 不通过:“把长度检查从 > 改为 >=”只是复述了 diff | 通过:“导致按照说明操作的用户无法注册” |
| 4. 提到 issue | 不通过:没有 issue 编号 | 通过:“Fixes #412” |
把模型评分者当作助手,而不是裁判。Zheng 等人 2023 年的论文“Judging LLM-as-a-Judge with MT-Bench and Chatbot Arena”记录了模型评审中的偏差,包括偏爱更长的回答、偏爱某个特定位置上的回答。让标准保持可检查,要求它引用证据,比较两个输出时调换 A 和 B 的顺序,并自己读一部分输出。像字符数这样的检查,写成一行代码比交给模型判断更可靠。
修改提示词,而不是修补对话
在聊天中,人们很容易用追问来纠正答案:“短一点”“不对,要提到 issue”“换另一种格式”。这样你得到了一个好的输出,提示词却和原来一样差。当某个纠正有效时,把它移进提示词里,然后重新运行提示词。下次需要这个结果时,你只需粘贴一条消息,而不用再重复五次。
保留旧版本,并用一行说明改了什么、修好了什么。一个纯文本文件就够了。当后来的某次修改让结果变差时,你可以退回去,而不用努力回想提示词原来是怎么写的。
用代码运行比较
当提示词通过 API 运行时,一个简短的脚本就能为每个版本和每个测试输入生成并排对比。下面这个脚本使用 OpenAI 的 Python SDK,写出一个可以从头读到尾的 markdown 文件。
from openai import OpenAI
client = OpenAI()
MODEL = "your-model-id" # e.g. from your provider's model list
# each prompt file contains {input} where the test diff goes
PROMPTS = {
"v2": open("prompts/commit_v2.txt").read(),
"v3": open("prompts/commit_v3.txt").read(),
}
TESTS = [open(f"tests/diff_{i}.txt").read() for i in range(1, 6)]
with open("results.md", "w") as out:
for i, test in enumerate(TESTS, 1):
out.write(f"## Test {i}\n\n")
for name, template in PROMPTS.items():
for run in (1, 2):
response = client.chat.completions.create(
model=MODEL,
messages=[{"role": "user", "content": template.replace("{input}", test)}],
)
out.write(f"### {name}, run {run}\n\n{response.choices[0].message.content}\n\n")
何时停下
当每个测试输入在几次运行中都能通过每一条标准时,就停下。在那之后再加内容,大多只是增加长度,而每多一条指令,就多一处可能和其他指令冲突的地方。
当修改开始“拆东墙补西墙”时也要停下:一处修改修好了测试 2,却弄坏了测试 4,下一处修改又反了过来。这种模式说明,你在要求提示词做一件单靠一条指令无法确定的事。通常的出路有三种:用几个示例展示格式(少样本提示),用提示词链把任务拆成几个步骤,或者把确定性的部分(比如数字符、检查格式)移到检查输出的代码里。如果你想不出办法,元提示可以帮忙:把提示词、输入和不好的输出交给模型,问它提示词的哪一部分最可能导致了这个问题。
常见问题
提示词给出的回答不好,怎么改进?
看看那个不好的回答,说出它错在哪里:格式不对、漏了事实、受众不对、太长。然后找出提示词漏说了什么才导致这个问题,把这一点补上,再用同一个输入运行新版本。回答不好,更多是因为缺少背景或缺少格式要求,而不是措辞问题。
测试一个提示词需要多少个测试输入?
对于要反复使用的提示词,三到十个通常就够了:几个典型情况,一两个边界情况(很短、很长、不寻常的),再加一个以前出过错的输入。迭代时保持它们不变,这样输出的变化才是来自提示词,而不是来自不同的输入。
为什么同一个提示词再运行一次,得到的回答不一样?
聊天模型是从概率分布中采样来选择每个词的,所以每次运行的输出都会有差异。比较两个版本的提示词时,要在相同的输入上把每个版本都运行不止一次。每次运行都出现的差异大概是真的;只在某一次出现的差异可能只是偶然。通过 API 调用时,你还可以调低 temperature 来减少差异。
可以用 AI 给提示词的输出打分吗?
可以,前提是标准能说得很精确,比如“正文说明了为什么需要这次修改”或“提到了 issue 编号”。把你的确切标准交给评分模型,要求它对每条标准给出通过或不通过,并引用原文作为证据。模型评分有已知的偏差,包括偏爱更长的回答、偏爱某个位置上的回答,所以要自己读一部分输出,并在比较两个输出时调换它们的顺序。
什么时候应该停止改进提示词?
当每个测试输入在几次运行中都能通过每一条标准时就停下;或者当每次新的修改修好一个用例、却又弄坏另一个时也停下。到了这一步,问题通常已经不在提示词本身:这个任务可能需要示例、需要拆成几个步骤,或者需要用代码来检查。