调试提示语的过程:调整第一版,然后用一些案例测试一下,然后再调试,再测试。。 如果把这个过程自动化,就可以构建一个自动提示语工程了。
有了这个想法,我做了个项目已经放到github了[5]
本文从这个思路,将这些调整流程通过LLM自动化,实现了一个自动化提示语工程项目。 涉及的内容包括:
- LLM评估提示语设计、改良提示语设计
- 评估中的定量和定性
- 自动提示语工程化的一些改进
一、什么是贝叶斯过程
整个这个"调整->测试->再调整->再测试.." 的过程叫做贝叶斯过程。
贝叶斯过程[1]:通过新的信息来更新我们对某件事情的看法。
假设你有一个朋友,他有两种行为:
- 按时还信用卡(认为50%概率还钱)
- 不按时还信用卡(认为50%概率不还钱)
你还有一个信息:
- 他最近买了一件很贵的玩具
因为你知道他最近买了一件很贵的玩具,所以我们认为它可能没钱了,所以不按时还信用卡的概率上升了,肯定大于50%了。
这就是贝叶斯过程:通过新的信息来更新我们对某件事情的看法。
二、思考流程
假设我们有一个初版的提示语工程,然后对这个的提示语做一个评价,我们基于评价信息来产生新的提示语,这样一轮一轮的迭代过程可以把提示语工程进行自动化提升。这也就是一个贝叶斯优化的过程:
流程是这样: 1.提供一个初始化提示词 2.使用提示词批量跑标准测试集(比如历史选择题试卷) 3.把提示词与试卷的得分(比如80分,100道题答对了80道题)给到一个分析改善的大模型,让他帮我们分析改良提示词 4.让模型对分析改良后的提示词重新进行批量标准测试机的评估 。。。。 以此类推。让程序自己改良提示词,这样,自动提示语工程的程序就做好了。 (注意:让模型进行提示语分析改善提示词的时候,应该将之前所有跑过测评的提示语和对应的得分都传过去。让他综合进行分析:哪些提示词好了,为什么好,可以借鉴吗。哪些提示词部分不好,为什么不好,可以避免吗)
为了实现思考的流程,图中应该重点关注这两个模块:
- 对提示语的测评
- 改善提示语
这两个模块的构建对提示语工程的改进都至关重要。
2.1、对提示语的测评
无法评估的系统,你也无法改进。
对提示语工程的改进也是一样的,你必须有个正确的路线作为指导,要让提示语往更好的方向改进,你需要能对提示语的效果进行评估。 (传统的方法,是通过人工来评估,然后手动进行改善。 我们希望这个步骤可以自动化)
评估其实是有两种方式的:定量测评和定性测评。
定量测评: 定量测评会简单很多,它是一组有label的标签就可以,比如你有一个客观的标准答案[2],如下:
这里边的label标签,也就是客观答案就是answer这列。 如果是这样的数据,我们就可以通过提示语将试卷全部运行一遍,算出最终的得分,这个准确率得分可以认为是此提示语的最终得分。
举一个具体的例子,比如你拥有了一个高考历史的选择题试卷[2],我们提示语改进的方向流程是这样的: 1.比如初始化提示语的得分只有70分 2.通过分析提示词语得分情况,就可以产出第二个提示词了 3.第二个提示词变成了80分 3.然后通过分析第一次、第二次的提示一与对应的得分,就可以产出第三个提示词了 。。。 以此类推,就可以产出与来越好的提示词。
定性测评: 比如一些开放问题,比如:你如何理解中国特色社会主义道路的? 它的标准答案是:中国特色社会主义是由道路、理论体系、制度构成的,是党和人民90多年奋斗、创造、积累的根本成就。中国特色社会主义理论体系作为其重要组成部分,是马克思主义中国化的最新成果,是坚持和发展中国特色社会主义的行动指南。
这些问题虽然没有标准答案,但这也正是模型擅长的,它擅长分析文字,然后再用文字分析出评判的结果。 虽然定型测评看上去有点难评估,但是其实定性测评有个好处:它不会造成信息的过度失真,会保留更多的有效信息。 举个例子,你有个苹果,现在让你给这个苹果按照定量去做一下评估:苹果属于大苹果还是小苹果? 对于这个问题你很难给出答案,比如你给个分界点,直径大于等于10厘米的就是大苹果,但是9厘米的苹果真的是小苹果吗? 10厘米的苹果真的是大苹果吗? 所以定量测评带来了信息的过度概括和失真。直到大模型的出现,才给了我们更多进行定性测评尝试的机会。 如果使用定性的测量方式,我们就可以将每一条答错的信息让模型分析,汇总所有的分析之后,全部发给改良模型让它帮忙改善。
再回到测评上来,为了简单的最小可行性产品(MVP),我们采用定量测评的方式,然后把所有的题做完,获得一个分数。 我们将这个提示语和总得分都给到改良模型,让它们作为一个信息反馈,一起传给优化改进模型,帮我们重新生成提示词。
2.2、改善提示语
通过一个LLM对之前做过评估的提示语进行改善,当然这个过程我们希望LLM能吸取之前的提示语的不足和优点,改善出更好的提示语。
所以我们要精挑细琢一下,告诉LLM改善的规则,甚至可以总结一下openai中提示语手册中的内容,也作为它的一个参考,让它有了之前提示语和对应的得分以及有改善提示语的规则,它会结合这些所有的信息生成一个更优的提示语。
有了这些思考和铺垫,我们开始实战一个真正的项目了。
三、实战
一些说明: 之前使用过DSpy,但是Dspy内部封装过分严格,而且输出的prompt都是英文的,很难深入的改造和体验。所以一直想做一个简单一些的程序,但一直没有没有开始,直到看到了Heiko Hotz的文章[3],对我启发很大,所以后边的实战基于此作者的github项目[4]的基础上,我fork之后进行了修改,主要修改了以下几点:
- 将vertai的工具去除掉(因为不支持国内模型)
- 将国内模型DeepSeek整合进了项目
- 将英文几何徒刑SVG测试集换成了中国历史高考题[2]
- 将所有提示词更改调成了中文提示词
更改后的代码已经放到了github[5]。
3.1 测试数据
第一步,我们下载中国历史高考题,并且将数据分为train.csv和test.csv部分。各100道题:
ini
# 加载数据集
dataset = load_dataset("dmayhem93/agieval-gaokao-history")
# 假设数据集中有一个 'question' 和 'answer' 字段
# 这里我们将数据集分为训练集和测试集
train_data = dataset['test'].select(range(100))
test_data = dataset['test'].select(range(100, 200)
。。省略了部分代码
# 保存为 CSV 文件
df_train.to_csv("train.csv", index=False)
df_test.to_csv("test.csv", index=False)
数据中只包含question和answer:
3.1 测试的基准
第二步,我们需要有个测试基准。
就是最初的提示词:
根据问题,请给出正确答案。
我们直接使用测试机test.cvs先进行测试:
准确率是:0.79(100道题,答对了79个)
3.3 自动化提示语
第三步,我们要开始执行我们的自动提示语提升了 我们应该在train.csv上进行提升训练,然后在test.csv上进行测试,这样可以避免过拟合(过拟合的意思就是过度适应了当前数据集的效果,而忽略了其他数据集。避免过度适应数据集的这个过程,也叫做提高范化能力。)
我们的初始化提示语是:
根据问题,请给出正确答案。一步步思考,最终得出正确答案。
后边会基于这个提示语陆续进行提升和改善,并迭代生成新的提示语。
首先,我们让LLM根据这个提示语给我生成历史试题的答案:
ini
response = client.chat.completions.create(
model="deepseek-chat",
messages=[
{"role": "system", "content": prompt},
{"role": "user", "content": question},
],
stream=False,
temperature=0
)
system就是我们的初始化提示语,question就是我们的答案,具体的写法是这样的:
ini
messages=[
{"role": "system", "content": "根据问题,请给出正确答案。一步步思考,最终得出正确答案。"},
{"role": "user", "content": "一万年农业,五千年文明,两千年大一统"指的是 选项:(A)中华文明 (B)埃及文明 (C)印度文明 (D)希腊文明
答案:从A到D, 我们应选择"},
],
模型的回答是这样的:
diff
答案:(A)中华文明
解析:
- "一万年农业"指的是中国农业的起源,中国是世界上最早开始农业生产的国家之一,距今约一万年。
- "五千年文明"指的是中华文明的历史,中国有文字记载的历史可以追溯到约五千年前的夏朝。
- "两千年大一统"指的是中国历史上多次出现的中央集权的大一统国家,如秦朝、汉朝等,这些朝代的历史大约有两千年。
因此,这些描述最符合中华文明的历史特征。
除了让模型回答问题,我们还需要一个LLM来判断我们的结果是否正确,提示词如下:
python
你是一个评估模型,负责检查导航问题回答的正确性。
回答中可能包含详细的步骤和解释,但最终答案是关键。
请根据给定的正确答案,判断回答中的最终答案是否正确。
如果最终答案正确,请回复"True";如果不正确,请回复"False"。
只需回复"正确"或"错误",无需其他内容。
模型回答:
{model_response}
正确答案:
{ground_truth}
根据这个提示词,模型将给出是否答案正确的回复,正确是"True",不正确是"False"。 这个提示词帮助我们评价此提示语的得分情况(也就是100道题能答对多少题)
100道题运行完成之后,我们就有了这个提示词对应的得分是多少了:
xml
<PROMPT>
<PROMPT_TEXT>
根据问题,请给出正确答案。一步步思考,最终得出正确答案。
</PROMPT_TEXT>
<ACCURACY>
Accuracy: 0.87
</ACCURACY>
</PROMPT>
这时候,需要让一个LLM帮我们根据这个提示词和得分对提示词来分析和改良,从而产生新的提示语,提示语如下:
xml
<EXPLANATION>
我会提供一些提示词及其对应的准确率。
这些提示词按照准确率从低到高排列,准确率越高表示质量越好。
</EXPLANATION>
<PROMPTS>
{prompt_scores}
</PROMPTS>
每个提示词都是关于如何更好的回答历史问题的,并给出正确答案的。
<EXAMPLE>
<QUESTION>
问题:中国古代有一个统一王朝,它最先在全国范围内推行了郡县制,建立起中央集权制的政治模式。据此判断,这个王朝是 选项:(A)西周 (B)秦朝 (C)东汉 (D)明朝
答案:从A到D, 我们应选择
</QUESTION>
<ANSWER>
(B)
</ANSWER>
</EXAMPLE>
<TASK>
编写一个新的提示词,以尽可能高的准确率完成任务,并且与之前的提示词不同。
</TASK>
<RULES>
- 新提示词必须与所有旧提示词明显不同,这一点非常重要!
- 确保你分析高准确率的提示词,并重用过去有效的那部分模式
- 确保你分析低准确率的提示词,并避免过去无效的那部分模式
- 在创建提示词之前,先大声思考。描述过去哪些方法有效,哪些无效。只有在分析之后再创建新的提示词。
- 使用所有可用的信息,如提示词的长度、正式/非正式语言的使用等进行分析。
- 要有创意,尝试不同的提示方式。你甚至可以设想一些可能提高准确率的假设场景。
- 你正在生成系统提示词(system prompt)。这意味着提示词中不应有占位符,因为它们无法在运行时填充。相反,应专注于提供有助于模型完成任务的通用指令。
- 将你的新提示词写在双中括号中。提示词文本仅使用纯文本,不要添加任何Markdown格式(即不要使用井号、反引号、引号等)。
</RULES>
其中{prompt_scores}部分就是我们的历史提示词与对应的得分。
这是模型反思后的回复:
markdown
在分析过去的提示词时,我发现高准确率的提示词通常包含以下几个特点:
1. **明确性**:提示词明确要求模型一步步思考,并最终得出正确答案。
2. **引导性**:提示词引导模型从问题出发,逐步推理,而不是直接给出答案。
3. **简洁性**:提示词简洁明了,没有冗余信息,直接指向任务的核心。
低准确率的提示词则可能存在以下问题:
1. **模糊性**:提示词可能不够明确,没有明确指出模型应该如何处理问题。
2. **冗余性**:提示词可能包含不必要的细节或重复信息,分散了模型的注意力。
3. **缺乏引导**:提示词可能没有提供足够的引导,导致模型无法有效地推理。
基于这些分析,我设计了一个新的提示词,旨在结合高准确率提示词的优点,同时避免低准确率提示词的缺点:
[[请逐步分析问题,从选项中排除不符合条件的选项,最终确定唯一正确的答案。确保每一步推理都清晰明确,逻辑严谨。]]
双括号部分就是我们新的提示词:
请逐步分析问题,从选项中排除不符合条件的选项,最终确定唯一正确的答案。确保每一步推理都清晰明确,逻辑严谨
然后使用新的提示词进行下一轮的评分和改进。。 进行了5轮改进之后,效果是这样的:
yaml
Prompt number: 0
Prompt: 根据问题,请给出正确答案。一步步思考,最终得出正确答案。
Accuracy: 0.87
Prompt number: 1
Prompt: 请逐步分析问题,确保每一步推理都清晰且逻辑严密。最终,提供一个准确且明确的答案。
Accuracy: 0.83
Prompt number: 2
Prompt: 请逐步推理问题,确保每一步都逻辑严密且清晰。在推理过程中,详细解释每个步骤的理由,并最终提供一个准确且明确的答案。
Accuracy: 0.86
Prompt number: 3
Prompt: 在回答问题时,首先回顾相关历史背景和事件,确保对每个选项有充分的理解。然后,逐步推理每个选项的可能性,排除不符合条件的选项。最后,基于逻辑推理和历史事实,确定最符合条件的答案。
Accuracy: 0.90
Prompt number: 4
Prompt: 在回答历史问题时,首先全面回顾相关历史背景和事件,确保对每个选项有深入的理解。接着,逐步推理每个选项的可能性,排除不符合条件的选项。最后,基于逻辑推理和历史事实,确定最符合条件的答案,并详细解释选择该答案的理由。
Accuracy: 0.90
能看到我们的提示词是在改进的,在train.csv中准确率由0.87提升到了0.90. 但这个数据是训练数据,我们必须要在测试数据集中(test.csv)再次进行验证.
需要验证的提示词:
,首先全面回顾相关历史背景和事件,确保对每个选项有深入的理解。接着,逐步推理每个选项的可能性,排除不符合条件的选项。最后,基于逻辑推理和历史事实,确定最符合条件的答案,并详细解释选择该答案的理由。
测试集(test.csv)得分:0.80
测试机分数从基准的0.79提升到了0.80. 自动提示语工程的逻辑就是这些了。
四、反思与思考
4.1多关注业务场景
这里边是要使用了DeepSeek作的所有的大预言模型,将上边的所有流程都跑完,才花了不到1元钱。 所以随着模型的发展,越来越应该多关注业务场景,而不是费用了。[6]
4.2定量评估的缺陷
一个提示语给一个定量的评分是由严重缺陷的,它缺失了很多信息,比如:这个问题你没答对,原因是什么,只有知道了原因才可以更好的改进。孤零零的分数导致了提示语改进模块只能根据提示语对应的总分来猜到底应该怎么改进。 所以,我认为后边更多的模型反思工作,应该是通过定性来评估的,而不是定量:就是说要根据自然语言描述来评估哪里做的好哪里不好,不能只根据数据的范围或指标来改进。
4.3改进方向的优化
除了收集更多新的信息以外,还需要更多的指导方向信息来提升提示语。比如我们的改进提示词的规则是怎么写的:
markdown
<RULES>
- 新提示词必须与所有旧提示词明显不同,这一点非常重要!
- 确保你分析高准确率的提示词,并重用过去有效的那部分模式
- 确保你分析低准确率的提示词,并避免过去无效的那部分模式
- 在创建提示词之前,先大声思考。描述过去哪些方法有效,哪些无效。只有在分析之后再创建新的提示词。
- 使用所有可用的信息,如提示词的长度、正式/非正式语言的使用等进行分析。
- 要有创意,尝试不同的提示方式。你甚至可以设想一些可能提高准确率的假设场景。
- 你正在生成系统提示词(system prompt)。这意味着提示词中不应有占位符,因为它们无法在运行时填充。相反,应专注于提供有助于模型完成任务的通用指令。
- 将你的新提示词写在双中括号中。提示词文本仅使用纯文本,不要添加任何Markdown格式(即不要使用井号、反引号、引号等)。
</RULES>
这个最大的问题是:只考虑到了基于原提示词和评分进行改进,并没有一些通用规则的指引。 像openai提示语手册中提到的指导原则[7],如果将这些通用的指导原则加进来改善效果会更好,比如:
markdown
1. 提示词中是否指明了角色身份(eg 你是名医生)
2. 是否补充增加了一些案例(few-shot)
等。。
参考资料:
- 贝叶斯定理:zh.wikipedia.org/wiki/%E8%B4...
- 高考历史题:huggingface.co/datasets/dm...
- Automated Prompt Engineering: The Definitive Hands-On Guide:towardsdatascience.com/automated-p...
- Heiko Hotz源代码:github.com/marshmellow...
- 从0构建自动提示语工程github:github.com/astordu/aut...
- DeepSeek用量展示:platform.deepseek.com/usage
- openai提示语工程手册:platform.openai.com/docs/guides...