prompt.png

随着大模型在2023年横空出世,“Prompt 工程” 应运而生,作为用好大模型最重要的武器,Prompt 的好坏对模型效果有着决定性的影响。

本文希望结合我们在 “Prompt 工程” 中的实践经验,更加体系化地对 “Prompt 工程” 进行梳理,希望可以一步步地帮助大家用好大模型,人人都是 Prompt 高手。

前言

Prompt 与 GPT 的前世今生

如今我们所讨论的大语言模型,大多专指2023年 “ChatGPT” 爆发以来出现的众多模型,而非广义的 Transformer 架构下的所有模型。而 Prompt 的概念也正是伴随 “GPT” 模型的发展应运而生的。我们要明白 Prompt 是什么,就要知道 Prompt 的前世今生,这就要从 GPT 的发展开始谈起。

发展史

如图,自 2017 年 Transformer 诞生以来,在这个架构的基础上,以 BERT 为代表的模型和以 GPT 为代表的模型便以极快的速度向前发展。在 2018 年 BERT 诞生后,语言模型便开始重塑 NLP 领域,快速在市场中得到广泛应用,时至今日这些语言模型依然是 NLP 领域中最被广泛应用的模型,我们今天看到以 GPT 为代表的各类大模型也是其中之一。

从 GPT 的发展来看,我们可以大致划分为4个阶段," GPT1 - GPT2 - GPT3 - ChatGPT ",我们可以从这个发展过程中了解到 Prompt 是如何诞生的,以此更好的了解 Prompt。

阶段1: GPT-1 诞生在 Transformer 初期,是最早期基于 Transformer 架构打造的模型之一,其采用了和 BERT 相同的范式,通过 "pretrain + finetune" 的方式,首先让模型在大量未标注的数据上自监督的进行学习,完成预训练,随后在应用时再使用有监督数据进行微调,以此让模型可以适用于各种任务。在这种范式下 BERT 作为双向模型,可以充分的获取上下文信息,这让他在各类任务中都展现出了更精准更稳定的效果,而 GPT 作为单向模型,更擅长生成任务,而由于在这个阶段还处于大模型发展的早期,模型规模和效果还没有成长起来,因此生成的不稳定性使得 GPT 并没有被大规模应用。时至今日,即便 GPT 已经展现出了令人惊艳的效果,但目前 BERT 类的模型依然是各个业务使用更多的模型。


阶段2: 相比 GPT-1,GPT-2 第一次提出了全新的范式,当我们扩大模型规模增加训练数据,让模型在一个称为 WebText 的由数百万个网页组成的数据集上完成预训练后,模型不再需要任何监督数据,就可以完成各类任务。在 OpenAI 的 Blog 中我们可以看到,团队在研究过程中发现,提升模型规模及训练数据的体量,可以让模型在 zero-shot 任务中的效果明显提升,这也在今天被认为是对 scaling law 的第一次发现,虽然当时还没有诞生智能涌现的现象。也有人解读,由于 BERT 在各个领域展现出的优异效果,GPT 被迫找到了新的发展方向,也为如今的智能涌现奠定了基础。由此,GPT 开启了与 BERT 截然不同的范式,并在新的范式下进行研发,专注模型在 zero-shot 中的效果。


阶段3: 沿着 GPT-2 增大模型体量和训练数据规模的思路,GPT-3 使用了 570G 的训练数据,达到了 GPT-2 的15倍,参数量更是达到了惊人的 1750B,是 GPT-2 的 116 倍。参数量的提升让 scaling law 大显神威,让模型在各个领域中都展现出了令人惊艳的效果,尤其是在 zero-shot 方面,发布会上通过手绘 UI 图生成前端代码的例子至今让人印象深刻。GPT-3 在 2020 年底发布,当时 Transformer 已经经过了4年的发展,BERT 类模型已经在各类应用中被广泛使用,成为了绝对的主流。然而在这种情况下,GPT-3 的发布也依然成为了领域中最瞩目的事件,即便还是有很多问题,但其远超预期的效果依然令人震撼。


阶段4: 之后的故事大家都非常熟悉了,OpenAI 在 GPT-3 的基础上针对不同场景进行了优化,在“多轮对话”的优化中诞生了“ChatGPT”,随后成为了世界上最火热的话题,也被认为是 AI 市场化的起点。GPT-3 后的模型不再开源,也没有论文公开发表,我们只能在 Blog 中获取一些信息,此处不再进行展开。

最后我们来做个总结,从领域发展的角度我们可以看到 3 种不同的研发范式:

  1. Transformer 之前:有监督训练(train)
  2. GPT1 & BERT:无监督预训练(pre-train) + 有监督微调(finetune)
  3. GPT2 & GPT3:无监督预训练(pre-train) + 提示词(Prompt)

Prompt 到底是什么

Prompt 译为 “提示”,与 "zero-shot" 的概念相辅相成,“zero-shot” 就是不通过训练直接向模型提问的应用方式,而 Prompt 就是指提问的方式。从这个概念上看 “模版” 可能是更合适的称呼,为什么要用 “提示” 这个单词呢?

实际上,在刚刚出现这个概念时并没有 “Prompt” 这样的称呼,在早期的论文中其往往会被称为 “输入形式(format)” 或 “输入模版(template)”,但随着领域的发展,尤其是在 GPT-3 之后,Prompt 的叫法才被逐步确定,大家认同 “提示” 的概念与其作用更加贴切,尤其是在大语言模型的语境下尤为合适,因此逐渐成为了公认的称呼。

那 Prompt 到底在提示什么呢?从前文中对范式的解读来看,模型能力的应用越来越向 “预训练” 的部分倾斜,绝大多数能力应当是在 “预训练” 阶段就构成的,而非通过进一步的训练构建。而在这种思想的基础上,Prompt 则像一把解锁模型能力的钥匙,让这些 “预训练” 阶段构成的能力唯我所用。因此,Prompt 就是在提示模型回忆起自己在预训练时学习到的能力。

自大模型横空出世,“Prompt 工程” 一直是其中最为火热的方向之一。在此背景下,Prompt 相关的研究已经积累的十分充足,无论是公司内外都积累了众多的文章和课程,其中最火的 《Prompt Engineering Guide》 ,和吴恩达老师的 《ChatGPT Prompt Engineering for Developers》 ,都对 “Prompt 工程” 作出了详细的介绍。

Prompt 万能框架

万能框架

在编写 Prompt 时,从0到1的编写出第一版是最难的,而基于已有 Prompt 利用各种技巧进行优化则相对简单。我们可以参考 “万能模版”,把 Prompt 拆分成了 立角色 + 述问题 + 定目标 + 补要求 ** 这四个部分,通过结构化的拆分完成0到1。无论面对什么任务,利用这个模版都可以得到一个“及格”的 Prompt。下面和大家阐述一下模版是如何得到的,为什么他是有效的。

基础三要素

基础三要素

  1. 问题是什么: 首先你要告诉模型你的问题是什么,你的任务是什么,要尽量描述清楚你的需求。
  2. 你要做什么: 下面你需要告诉大模型具体要做什么,比如做一份攻略,写一段代码,对文章进行优化,等等。
  3. 有什么要求: 最后我们往往还需求对任务补充一些要求,比如按特定格式输出,规定长度限制,只输出某些内容,等等。

通这 3 部分的描述我们就把 “要大模型做什么” 描述清楚了!

这仅仅是第一版 Prompt,你不需要描述的过于详细,也不需要使用技巧,只需要用简练的语言把这几部分描述清晰即可。

1
2
3
4
5
6
7
例如:

问题是什么:你的任务是帮我生成一个产品的简短摘要。

你要做什么:我会给你产品的需求文档,及用户对产品的评价,请结合这些信息概括产品的功能及现状,为我生成一份产品摘要。

有什么要求:请尽量概括产品的特点,并用轻松幽默的语言风格进行编写,摘要的字数不要超过50个字。

任务拆分

在描述清楚任务后,我们就需要调度模型的能力去完成我们的任务,不同的任务需要用到不同的能力,这往往依赖任务的拆分。 我们可以想像,当我们让一个小白帮我们完成一项任务时,我们需要对任务进行分解,并告诉他每一步需要怎么做,以此来让他完成一项复杂的任务。对于大模型而言,这也是适用的。

你当然可以人为的完成这种拆分,再一条条的解释给大模型,但这种做法并不通用,每个领域都有自己独特的专项能力,每个任务都有自己的工作流程,因此这种方案并不适合放到一个通用的框架当中。

好在大模型能力的调用还存在一条捷径,那就是“角色”,他就像大模型里自带一个“能力包”,可以很容易的对模型能力进行调用。 每一个角色,都对应着该角色包含的若干能力,我们可以通过设定角色,“提示”大模型使用该角色对应的能力,这与前文Prompt 到底是什么 中介绍的想法极其匹配,充分说明是“Prompt” 提示的作用,通过简单的“提示”调用出大模型预先训练的能力,这也就是“角色”如此有用的原因。

框架细化

通过我们的框架,在任何任务中我们都可以完成 Prompt 从 0 到 1 的编写,而这个初版的 Prompt 还只是一个雏形,由于各部分信息还不完整,往往不能达到我们的预期,框架提供了 Prompt 的骨架,还需要我们进一步补充血肉,下面我会对框架的每一部分进行更详细的分解,通过内容的补全一步步的提升模型的效果。

立角色

角色 可以被当作大模型的“能力包”或“语法糖”,我们不再需要对每一项能力进行详细的描述,对任务进行更细节的分解,而是可以通过 import “角色” 的方式,使用这个 “角色” 背后对应的各项能力。那我们该如何设立角色,才是这个“能力包”的正确使用方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
角色模版:

现在你是一位优秀的{{你想要的身份}},拥有{{你想要的教育水平}},并且具备{{你想要的工作年份及工作经历}},你的工作内容是{{与问题相关的工作内容}},同时你具备以下能力{{你需要的能力}}

例:心理咨询师

现在你是一位优秀的 {{心理咨询师}},拥有 {{心理咨询、临床心理学等专业的硕士或博士学位}},并且具备 {{多年的心理咨询工作经验,在不同类型的心理咨询机构、诊所或医院积累了丰富的临床实践经验}},你的工作内容是 {{为咨询者处理各种心理问题,并帮助你的咨询者找到合适的解决方案}},同时你具备以下能力:

{{

专业知识:你应该拥有心理学领域的扎实知识,包括理论体系、治疗方法、心理测量等,可以为你的咨询者提供专业、有针对性的建议。
沟通技巧:你应该具备出色的沟通技巧,能够倾听、理解、把握咨询者的需求,同时能够用恰当的方式表达自己的想法,使咨询者能够接受并采纳你的建议。
同理心:你应该具备强烈的同理心,能够站在咨询者的角度去理解他们的痛苦和困惑,从而给予他们真诚的关怀和支持。
持续学习:你应该有持续学习的意愿,跟进心理学领域的最新研究和发展,不断更新自己的知识和技能,以便更好地服务于你的咨询者。
良好的职业道德:你应该具备良好的职业道德,尊重咨询者的隐私,遵循专业规范,确保咨询过程的安全和有效性。
}}

以上是一个简单的示例,角色的设置往往需要编写者对角色有一定的了解,这可以更好的帮助你补全你的模版

如果你不了解你要设置的角色,不知道这些信息该如何填写,我们如何可以获取到这部分信息呢?
很简单,继续套用 prompt 形成一个新的工程(以获取当前角色信息为目标的prompt)

述问题 & 定目标

对问题的描述由 “述问题” 和 “定目标” 两部分组成,是 Prompt 中信息含量最大的部分,也是和任务最相关的部分,我们要明确的描述我们希望大模型做的工作,才能让大模型输出最符合预期的结果。

除了要描述的清晰明确外,此部分值得强调的就是对任务的分解,这在复杂任务上尤为重要。如果我们需要大模型完成的任务过于复杂,我们则需要先人工对任务进行拆分,并尽量详细的描述任务中包含的各个部分。

我们也可以把这种拆分当作一个任务维度的对齐,当我们用概括的语言描述一项任务时,隐含了大量的背景知识和预期。例如,当我们希望大模型帮我们 “制作一份旅游攻略” 时,我们希望他能帮我们 “规划行程”,“收集信息”,“预定酒店” 等等,而这些信息往往都被包含在 “旅游攻略” 当中。如果我们不明确的对任务进行拆分,大模型就不知道我们的任务具体需要包含哪些部分,因此这个任务维度的对齐十分重要。下面我举几个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
例1:请帮我制作一份深圳的旅游攻略

请帮我制作一份深圳的旅游攻略,以下是一些基本步骤和思考方式:

研究和收集信息:查找关于深圳的旅游信息,包括主要的旅游景点,当地的文化和历史,美食,交通,住宿等。
规划行程:根据你收集的信息,规划你的行程。考虑你想要去的地方,你有多少时间,你的预算等。确保你的行程是实际和可行的。
预订交通和住宿:一旦你确定了你的行程,你可以开始预订你的交通和住宿。比较不同的选项,找到最适合你的。
准备必要的物品:根据你的行程和新疆的天气,准备你需要的物品,比如衣服,鞋子,相机,地图等。


例2:请根据需求帮我设计测试用例

请根据需求帮助我设计测试用例,测试用例的设计是一个系统化的过程,以下是一些基本步骤和思考方式:

理解需求:首先,你需要深入理解软件的需求和功能。这包括阅读需求文档,理解用户故事,或者与项目经理和开发人员进行讨论。
确定测试范围:确定你需要测试哪些功能和特性。这可能包括正常操作,边缘情况,错误处理等。
设计测试策略:确定你将如何测试这些功能。这可能包括单元测试,集成测试,系统测试,性能测试等。
编写测试用例:对于每个测试,编写一个详细的测试用例。这应该包括预期的输入,预期的输出,以及如何执行测试。
评审测试用例:对测试用例进行评审,以确保它们完整,准确,并且易于理解。
复杂任务的拆解往往十分有效,不仅可以提升大模型的推理能力,也可以让大模型的输出更加贴合你的要求(对齐),但这需要你对当前任务有一定的理解,才可以完成这样的拆分。但如果你并不熟悉你要处理的任务,如何完成这一步呢。正如前文中表达的观念,我们希望得出一套通用的方法,让每个人在面对每个任务时都可以完成 Prompt 的编写,因此一定需要找到更加通用的方法。

其实,这个步骤你完全可以让大模型帮助你完成, 这类似 Agent 中的 Planning的思想【Agent 详解 】,让大模型帮助你拆分一项复杂任务。你可以使用这样简单的 Prompt 完成这个任务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
任务拆分 Prompt:

{{你要做的任务}},需要哪些步骤,该如何思考?

例:希望大模型帮我写一份基金财务分析报告

输入:

制作一份基金财务分析报告,需要哪些步骤,该如何思考?

输出:

制作一份基金财务分析报告是一个涉及多个步骤的过程,需要对基金的财务状况、投资策略、市场环境等进行深入的研究和理解。以下是一些主要步骤和思考方式:

收集数据:首先,你需要收集基金的财务数据,包括但不限于基金的净值、收益率、投资组合、费用比率等。这些数据通常可以从基金公司的年度报告或季度报告中获取。
数据分析:对收集到的数据进行分析,包括对基金的收益率、风险、投资组合分布等进行计算和比较。你可以使用一些财务分析工具,如 Excel,或者专门的财务分析软件来进行这些计算。
基金比较:将基金的表现与其同类基金或者基准指数进行比较,看看基金的表现是否超过市场平均水平。这可以帮助你理解基金的相对表现。
市场环境分析:考虑基金的投资环境,包括经济状况、行业趋势、政策变化等。这些因素可能会影响基金的表现。
基金经理分析:研究基金经理的投资策略和历史表现,看看他们是否有能力在不同的市场环境下取得良好的回报。
撰写报告:将你的分析结果整理成一份报告,包括你的主要发现、基金的优点和缺点、以及你对基金未来表现的预测。
在进行这些步骤时,你需要保持客观和批判性的思考。不要只看基金的过去表现,而要尝试理解基金的表现背后的原因。此外,你还需要考虑到市场的不确定性,因此在做出预测时要谨慎。

补要求

我们已经描述清楚了我们的任务,下面我们往往还需要对我们的任务提出一些要求,告诉大模型希望他做什么以及不做什么,这往往与模型在应用中的准确率息息相关。我们常用的要求可能有如下几种:

  1. 字数限制。
  2. 输出结构(结构化输出,Json 输出)。
  3. 在输出中排除 xxx。
  4. 在输出中仅包含 xxx。
  5. ......

让大模型遵循我们的要求,尤其是在“格式”层面让大模型的输出符合我们的规定,对大模型的工业应用十分重要。如何让大模型更听话,让其遵循我们的要求呢?

首先, 我们可以把要求放在 Prompt 的最后。大语言模型的本质是在做文本补全,后文的输出更倾向于依据距离更近的语境。

如果利用 "LIME" 这样的模型解释算法分析,距离更近的文本间权重往往更大,这在 Transofrmer 中的 Attention 权重 上也可以清晰的看到。同时,这与大模型在预训练阶段中完成的任务也更加匹配,虽然现在的大模型在 SFT 阶段会进行多种任务的训练,但其本质上还是建立在自监督“文本补全”任务上被训练出来的,因此其天然的更加遵从离得更近的文本。因此,把要求放在 Prompt 的最后可以很有效的帮助大模型变得更“听话”。

其次, 我们还可以利用大模型的“编程”能力巧妙的让他更“听话”。

我们可以通过设定角色调用大模型的能力,那有什么能力可以让大模型更“听话”呢?我们都知道“大模型”在“编程”方面也展现出了惊人的能力,而这个能力恰好可以将“模糊的文理问题”变成“准确的数理问题”,以此让大模型更加遵守我们的要求。

具体而言,就是把我们的要求转换为一个 “编码” 任务,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
请列出10个国家,并以列表形式返回

请列出10个国家,并以列表形式返回。我需要将这个列表引入到 python 代码中,请严格遵守 python 列表的格式进行输出。

例2:请输出10个国家,并包含这10个国家的“名称”,“人口”,“位置”。

请列出10个国家,并包含这10个国家的“名称”,“人口”,“位置”。我需要将这份数据引入到 python 代码中,因此请以 Json 格式进行输出,Json 格式如下:

'''

{"name": 名称, "population": 人口, "position": 位置, }

'''

例3:请为我输出一份产品摘要,字数不要超过50个字。

请为我输出一份产品摘要。我需要将这个摘要引入到 python 代码中,该变量的大小为50,因此摘要内容不要超过50个字符通过这样引入大模型“编程”能力的方式,我们可以对模型提出更加精准的要求,并通过将我们的任务转换为更加准确的编程问题的方式,让大模型更 “听话”。

格式很重要

除了输入的内容外,输入的格式也很重要,清晰的结构对大模型的效果有很大的影响。

除了增加合适的 “空行” 让结构变的清晰外,我们还可以增加一些“标识符”来区分各个部分,例如:#,<>,```,[],-。同时大模型也具备 MarkDown 解析的能力,我们也可以借助 MarkDown 语法进行 Prompt 结构的整理。

由于“格式”对模型效果的影响,越来越多研究聚焦在了这个方向上,其中 “LangGPT” 得到了广泛的应用。LangGPT 提出了一种结构化的 Prompt 模式,可以通过一套结构化的模版构造出格式清晰的 Prompt。

例:LangGPT 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# Role: 设置角色名称,一级标题,作用范围为全局

## Profile: 设置角色简介,二级标题,作用范围为段落

- Author: yzfly 设置 Prompt 作者名,保护 Prompt 原作权益

- Version: 1.0 设置 Prompt 版本号,记录迭代版本

- Language: 中文 设置语言,中文还是 English

- Description: 一两句话简要描述角色设定,背景,技能等

### Skill: 设置技能,下面分点仔细描述

1. xxx

2. xxx

## Rules 设置规则,下面分点描述细节

1. xxx

2. xxx

## Workflow 设置工作流程,如何和用户交流,交互

1. 让用户以 "形式:[], 主题:[]" 的方式指定诗歌形式,主题。

2. 针对用户给定的主题,创作诗歌,包括题目和诗句。

## Initialization 设置初始化步骤,强调 prompt 各内容之间的作用和联系,定义初始化行为。

作为角色 <Role>, 严格遵守 <Rules>, 使用默认 <Language> 与用户对话。然后介绍自己,并告诉用户 <Workflow>。

LangGPT 示例中可以看到,他用各种分隔符对 Prompt 内容进行了整理,可以很清晰的看到每部分的作用与区分。我们可以借鉴 LangGPT 对分隔符的使用,通过对格式的整理让同样的 Prompt 展现出更好的效果。

总结(框架)

我们把 “框架细化” 分成了4步,并在每一步中都提出了通用的实践方法:

  1. 角色:通过角色模版和招聘 JD 补全角色。
  2. 问题&目标:在大模型的辅助下拆分任务。
  3. 要求:借助编码能力,让大模型更好的遵守要求。
  4. 格式整理:结合 LangGPT 的思想合理应用分隔符,让 Prompt 结构清晰。

至此,我们已经完成了 Prompt 主体部分的编写,面对任何一个任务都可以通过这套统一的方法完成一个还不错的 Prompt,并且通过我们对 Prompt 结构化的拆分,我们现在也可以更好的管理我们的 Prompt,并为上层应用提供更好的支撑。

结语

大模型的推理,根本上还是基于用户输入的信息进行推理,我们提供的信息越充分,大模型就能越好的完成推进。因此,要想让模型的效果更好,我们就需要提供更多的输入信息。

增加更多信息,让效果变得更好 这个想法十分自然,但我们要增加什么信息?如何增加这些信息呢?

为了能在合适的场景下增加合适的信息,势必要引入知识库检索的工作,来根据需要找到合适的信息,而说到 知识库检索 就不得不提名声大噪的 RAG 了。

要深入了解 RAG,请查看 RAG 工作机制详解 篇。