探索编写提示词的乐趣:蒙特卡洛方法、木偶剧和笑声的融合 [译]

Ben Bernard

文章标题插图,“如何制作:探索编写提示的乐趣:蒙特卡洛方法、木偶剧和笑声的融合”。图中左下角显示 Instacart 的标志,右侧有一个代表编程的空 html 结束标签。
文章标题插图,“如何制作:探索编写提示的乐趣:蒙特卡洛方法、木偶剧和笑声的融合”。图中左下角显示 Instacart 的标志,右侧有一个代表编程的空 html 结束标签。

现今,大语言模型(LLMs)领域的发展令人振奋,自去年 11 月 ChatGPT 面世以来,整个行业就像被点燃了一样,迅速变化。我们见证了 AI 工具在整个行业的崛起,这为产品开发带来了前所未有的可能性。

Instacart 正在迅速采用大语言模型和生成式 AI:不妨看看我们的内部助手 Ava,超强 AI 搜索工具 Ask Instacart,以及我们在 ML 平台上的创新。我们正探索各种应用场景和能力,最重要的是,我们致力于为员工、客户、商家和购物者创造价值。

任何有过使用这些模型经验的人都会知道,这种新兴技术拥有无限的潜力。不过,这些潜力目前还受到一些挑战的限制,比如上下文大小的问题、模型产生的幻觉,或是模型无法完成我们设定的任务。

幸运的是,有很多技巧可以帮助我们利用大语言模型开发产品。这篇文章将探讨这些技巧,并希望能够激发你对更多可能性的思考!

先插播一条简短的信息:所有这些提示技术都已在 GPT-4 上得到实施和应用,有些甚至在 GPT-3.5 上也有所使用。目前来说,GPT-4 是最先进的对话模型,它在性能上远超过 GPT-3.5 及其他所有对话模型。如果经济上可行,我强烈建议在你的应用场景中采用 GPT-4。

本文将深入探讨我们在 Instacart 内部生产力工具中所采用的各种提示技术。这些技术结合了行业与学术研究以及我们自身的小规模内部开发成果。建议你在自己的评估环境下对这些技术进行测试,并根据你的具体需求进行调整。

提示(Prompt)的魅力

在与大语言模型的合作中,有一个引人入胜的环节——提示。这是我们与模型沟通的桥梁,是我们与这个 AI 实体互动的途径。就像你最爱的菜肴中不可或缺的调料一样,恰当的提示技巧能够显著改善交流成果。接下来,我们将介绍一些广为人知的技巧,并展示我们开发的一些更具创新性的技巧。

先来看一个听起来像直接从认知科学教材里跳出来的技巧:[思维链] (https://arxiv.org/abs/2201.11903) (思维链,CoT)。思维链是一种简单却富有启发性的提示方法,它带来的深远影响我们将在下一部分详述。CoT 有多种形式,其中最流行的一种就是在提示语中加入“让我们一步步来”的说法。像许多这样的技巧一样,它简单到几乎让人觉得可笑。另一个较新的说法是“深呼吸,然后制定一个回答计划”。虽然模型无法真正地呼吸或深思,但这些措辞能引导它在确定回答方向前,在可能的答案空间中进行更深入的思考。

这里有一个实例,展示了如何利用思维链为之前讨论过的文章构思标题(我就是用这种方法为这篇文章起的标题):

Now we will generate a title for the article. First take it step
by step and determine what are the most important elements of the
article to include in the title and what makes a good title in general.
After you’ve done that, generate the title.
现在我们来为文章定个标题。
首先,一步步分析,弄清楚文章中哪些核心元素需要包含在标题里,
以及一个好标题通常需要具备什么特质。
完成这些后,再来创作标题。

另一个广为人知的提示技巧是 ReAct。这种技巧赋予模型在其文本生成流程之外采取行动的能力。可能的行动包括搜索网页、进行数学计算,甚至查阅内部文档信息。通常情况下,你需要明确告知模型它所具备的功能。例如:

在回答下述问题时,你还可以执行以下操作来获取更多信息:

In answering the question below, you may also take the following actions
to get more information:
INTERNAL_LOOKUP: <search terms> - perform a search across internal sources
GOOGLE_SEARCH: <search terms> - perform a web search for your terms
CALCULATION: <math terms> - Perform an arithmetic calculation i.e.
CALCULATION: 2 * (8^10)
Actions must come at the end of your output, and you will be given the
result in response. Please restate all information for the user.
在回答下述问题时,你还可以执行以下操作来获取更多信息:
INTERNAL_LOOKUP: <搜索词> - 在内部资源中进行搜索\
GOOGLE_SEARCH: <搜索词> - 进行网络搜索\
CALCULATION: <数学术语> - 进行算术计算,比如\
CALCULATION: 2 \* (8^10)
在输出的最后部分必须包含执行的动作,并且你会在响应中收到结果。请确保为用户重述所有相关信息。

在输出的最后部分必须包含执行的动作,并且你会在响应中收到结果。请确保为用户重述所有相关信息。

如今,当模型进行回应时,它可能会采用内部查找(INTERNAL_LOOKUP)、谷歌搜索(GOOGLE_SEARCH)或计算等方式,我们的软件将执行这些操作,并请求模型利用新的信息来完成任务。

通过这种方式,我们可以为模型构建一系列的功能。关于这方面更高级的实现,可以参考 ChatGPT 的插件系统

大语言模型与人类的相似之处

对比人类的思维方式与大语言模型(LLMs)的工作原理,就像是一次神奇的探险。让人不禁惊叹,LLM 的反应经常与和一个聪明但因缺乏睡眠而反应迟钝的实习生沟通如此相似。这样的实习生需要明确而具体的指令来完成任务,LLM 也是这样。他们都需要适当的引导,以确保保持专注,避免回答变得混乱,或者在 LLM 的情况下,产生错误的信息。

就像对待人类实习生一样,让 LLM 有犯错和自我修正的机会也是有益的。虽然人们可能会对拟人化的 LLM 持怀疑态度,但这样的比喻实际上有助于我们更好地与之互动,提高成功完成任务的可能性。再来看一个像人类一样的特点:给予 LLM 正确的引导和修正后,它们有时会给你一个意想不到的幽默回应,或者提供深刻的见解,让你惊喜。这种体验既充满乐趣又充满不确定性,有时甚至有些沮丧。

这方面的一个例子是在少样本学习中使用“谢谢你”这个短语。在少量样本示例中对模型表现出礼貌,可以帮助模型更准确地理解下一个示例的含义。在少样本学习中,我们会提供 2-5 个涵盖不同情况的输出示例。我们发现,如果仅仅是提出问题和给出答案,模型有时会混淆,误以为下一个问题是对前一个答案的纠正,而非一个新的示例。但如果说“谢谢你,做得很好,下一个问题是:”,结果通常会更好。实际上,“谢谢你”这个短语比其他表达方式在我们的测试中效果更佳!

当我们开始把 LLM 看作一位实习生时,这会帮助我们更实际地看待我们的指令。想象一下,一个没有专门在你的领域学习过的普通教育背景的人可能如何处理你的任务。在反思和改进我自己那些效果不佳的指令后,我发现了一个趋势:把 LLM 当作一个聪明但有时会困惑的参与者,这促使我重新审视和修改我的指令,更加明确地表达任务需求。

这种观点的转变不只是理论层面的;它在学术 研究和日常的提示工程实践中都展现了其实际价值。通过在与大语言模型 (LLMs) 的互动中加入一些人性化的元素 —— 把它们当作有善意但可能稍显迷茫的伙伴 —— 我们能够提升它们的表现和我们的使用体验。

高级提示技术

接下来,我们将介绍在 Instacart 我们所开发的一些高级提示技术。虽然我们并不是这些技术的唯一发明者,也可能并未使用行业通用术语,但我们确实在开发 Ava 系列产品时应用了这些技术,这些产品是我们用于内部工作流的。我已经根据技术的复杂程度进行了排序,所以不要错过分类技术和操纵技术哦!

深思熟虑 —— 先规划再行动

“深思熟虑”是指在大语言模型开始回答问题之前,明确地鼓励它先制定一个策略。这个过程需要细致地挑选合适的词汇,以达到一个精妙的平衡。特别是 ChatGPT,它通过 RLHF 的训练,已经能够直接回答用户问题,而不是停下来等待。很多时候,我们需要明确告诉模型不要立即作答。比如,下面是我们用于生成内部代码审查的 Pull Request (PR) 标题和描述时的一段提示:

First let’s create an outline for the pull request description. Do not
generate a title and description, only write the outline. Be sure to think
about what the categories of the change are (i.e. 1. A change to add the
--foo argument, 2. Add retries for network calls, etc) based on what you
see in the diff.
首先,我们来为Pull Request描述草拟一个大纲。
不要马上生成标题和详细描述,仅需撰写大纲。
重点考虑更改的种类
(比如:1. 添加 --foo 参数的更改,2. 为网络调用增加重试机制等),
这些都是基于你在差异 (diff) 中观察到的内容。

这个特定的提示还省略了所有关于输出格式的指示,或者比如如何挑选一个合适的标题(我们在后续生成 PR 的讨论中会涉及到这些内容)。

这样做的目的是给模型一个空间,让它先思考如何更好地构建 Pull Request。就像对待一个人类思考者一样,我们只是在它真正撰写内容之前,先让它准备一个初稿或大纲。

有时,引导模型去思考一个好的回答中应该包含哪些要素也是不错的方法(例如:“首先列出五个优秀 Pull Request 的关键特点”),虽然很多时候这样的预先思考可以直接融入提示中,以节省时间。比如说:

A good pull request description is clear, concise, and fully lays out
the complex parts of the change. When composing a pull request
description, the best ones cite the changes with the description,but
also don't over describe small changes (especially one line changes).
Given this, create an outline for the pull request description, only
writing the outline
… etc …
一个优秀的Pull Request描述应该清晰、简洁,
并且能详细阐述更改的复杂部分。
在撰写Pull Request描述时,最佳的做法是既引用更改的细节,
又不过度描述那些微小的变动(尤其是只涉及一行代码的更改)。
基于此,为Pull Request描述撰写一个大纲,仅限于大纲
……等等……

这样,我们就将对“优秀 Pull Request 的标准”的思考融入到了提示之中,无需花费额外时间或生成令牌来创建这个固定的列表。我们仍然想为模型留出空间,让它去思考那些依赖于特定查询(例如这个例子中的特定 Pull Request 更改)的问题部分。

蒙特卡洛方法 — 创意选择的头脑风暴

蒙特卡洛技术的精髓在于,我们要求模型产生几个不同的方案,然后综合这些方案的精华,形成一个完整的最佳答案。这个过程中可以看到“思维的空间”(Room for Thought)的影子,因为模型被赋予了出错、尝试和创新的空间,最后才产生成果。

当你需要利用模型进行创意工作时,蒙特卡洛方法尤为有效。想象一下,当你和同事一起解决创意问题时,首先会进行头脑风暴,列出各种想法。蒙特卡洛技术就是用大语言模型(LLM)来实现这一过程。

这里有一个我最近用来为我女儿的生日派对构思创意并最终确定主题的实例:

I am looking for ideas for my 9 year old's birthday party. She is into
Pokemon, corgis, Roblox, and loves playing with her friends.
First list out elements of a good birthday party for a kid that can
be accomplished on a budget, and a list of fun themes/ elements of a
party given her interests.
Then create 5 radically different ideas for parties.
Finally create a final singular title recommendation that combines the
best elements of the options.
我正在寻找适合我 9 岁女儿生日派对的创意。
她喜欢宝可梦、柯基犬、罗布乐思,还喜欢和朋友们玩耍。
首先要列出适合孩子的生日派对的要素,
这些要素要在预算内可行,同时还要考虑她的兴趣,
列出一些有趣的主题和派对元素。
然后,创造五个完全不同的派对构思。
最后,综合这些构思的精华,提出一个终极主题建议。

蒙特卡洛最精彩的部分是,在互动过程中,你可以获得额外的五个选项。我通常会发现其中一个选项特别吸引我,然后选择它。需要注意的是,明确指出这些想法应该尽可能多样化非常重要,否则在某些情况下模型可能会重复五次,只是略微改变措辞。

我发现这种技术在生成带有幽默感的想法时特别有用。GPT-4 在幽默或笑话方面并不十分擅长,因此让它产生多个选项,有助于找到真正有趣的点子。

自我纠错 — 自我反思

自我纠错是指让模型反思自己的回答,并从批判性的角度思考怎样进行改进,然后将这些思考融入最终的答案中。这种方法与之前提到的蒙特卡洛技术结合使用效果最好,因为它可以对每个选项进行分析并提出建议。如果你还给出了关于什么是“好”的回应的指导,可以要求模型在提出建议时考虑这些指导。

我们再来尝试一下之前提到的 PR 标题和描述的生成,这一次加入自我纠错的环节:

Now we will generate the title for the PR. The title should succinctly
indicate what the pull request aims to do. Ideally, it should be a short
and clear description of the purpose of the pull request.
Generate 5 possible radically different titles and then critique them.
Finally generate a refined final title after the critique.
现在我们要为 PR 制定标题。
标题应该简洁明了地指出 Pull Request 的目的。
理想情况下,它应是一个简短清晰的描述,表明 Pull Request 的意图。
生成 5 个截然不同的可能标题,然后对它们进行评价。
之后,在评价的基础上生成一个更精炼的最终标题。

这里的关键是“然后对它们进行评价”。通过让模型进行评价,你让模型在其观察中进行改进。同时,当与模型互动时,你也可以了解到模型在形成这些评价和最终答案时的“思考过程”。

分类技术在大语言模型中非常有趣,它运用了这些模型的一些不常用特性。在使用大语言模型时,你可能会面临这样一个问题:你希望模型回答本质上相当于多项选择题的问题。但如果用传统的提示方式,可能会出现一些问题,比如模型在给出答案前需要先进行思考,或者在回答之前加上一些前缀信息(例如:“你的问题的答案是 A”,而不是直接说“A”)。当你想要以编程方式使用大语言模型的输出时,从中提取正确的回答可能会变得非常困难。

我们在内部 OpenAI / 大语言模型代理上构建了一个 API 端点,能够保证输出的有效性。实现这一 API 的关键在于,大语言模型能够可靠地在答案中重复使用上下文中的标签。有了这个能力,我们可以设计出这样的提示:

Consider the following statement carefully and think through your
reasoning before answering:
The sky is blue.
Possible Answers:
000 True
001 False
002 Uncertain
Answer the question by directly referencing the number of the answer.
请仔细考虑以下陈述,并在回答之前思考你的推理:
天空是蓝色的。
可能的答案:\
000 真\
001 假\
002 不确定
你需要通过直接引用答案的编号来回答这个问题。

这种方法虽然简化了对模型输出的处理,但单纯使用上述提示仍会带来之前讨论过的问题。大语言模型的工作原理是,根据给定的输入生成下一个最可能的 token(这里的 term 指的是字符或词片段)。它通过计算每个可能的下一个 token 的概率,并选择最有可能的那个。我们可以通过在请求 OpenAI 时调整 logit_bias 参数来影响这种概率。如果我们把偏置设置为 100,就可以强制模型选择特定的 token 集合。一旦我们限制了模型的响应为 '000'、'001'、'002' 等,我们就让它生成单个 token(通过将 max_tokens 设置为 1),从而确保我们的答案始终是有效的选择。值得注意的是,所有的三位数数字组合都被认为是单个 token。

但等一下 - 那些像 Room for Thought、CoT 以及其他为模型留出决策空间的技术怎么办?我们的 API 实际上允许一个“深思”模式,它可以执行 CoT 和其他的“大声思考”式的思考。这样做的方式是,首先要求模型进行思考,但不给出答案,然后在后续阶段使用 logit_bias 来强制得出最终答案。通常,在对话式的多轮提示中使用不同的技术,可以使应用更加多样化。

想象一下,如果我们要从一系列自动生成的选项中挑选一个最合适的标题给代码的 Pull Request,该如何操作。我们的目标不是直接生成一个最终的标题,而是让模型从它自己生成的选项中挑选出最好的一个。同时,我们需要限制模型只能选择一个选项,并且还要给予它一定的思考和自我评估的空间。具体操作流程如下:

Message 1:
Consider the following question carefully and think through your reasoning before answering:
Which of the titles below make the best pull request title, given these changes:
CHANGE
Be sure to take a deep breath and think through your answer
Possible Answers:
000 The best PR EVAR!!!
001 Adding CRUD endpoints
002 Adding POST and DELETE handlers for /api/books
You will first carefully consider the question and write down a bulleted list of thoughts that will lead me to an answer.
消息 1:
请你仔细思考下面的问题,并在做出选择前充分考虑你的推理逻辑:\
在下列变更的情况下,哪个标题最适合作为代码的 Pull Request 标题:\
变更
在作出回答前,请深呼吸,仔细考虑你的选择。
可能的答案包括:\
000 最棒的 PR EVAR!!!\
001 添加 CRUD 端点\
002 为 /api/books 添加 POST 和 DELETE 处理程序
你需要首先仔细考虑这个问题,并列出一系列的思考点,帮助我找到答案。

在模型给出其推理逻辑之后,我们继续:

Message 2:
Thank you. Now please identify the answer that best matches the
reasoning above.
Just reference the item number from the answer list above.
消息 2:
感谢你的回答。
现在,请根据上述推理,确定最合适的答案。
只需提及上面答案列表中的编号即可。

第一次回答要求模型给出完整的反应,并能够访问所有的 tokens。而对于第二次消息的回应,则只限于答案 tokens。值得注意的是,要得到正确的引导,需要进行一些微调,因为我们已经发现,即使是很小的词语变化(例如去掉“谢谢”这句话),也可能导致回答的准确度发生很大的变化。

关于这种技巧的一个提示:在进行分类时,我们建议使用较低的温度设置(甚至可以设为 0)。温度值决定了模型在下一步生成过程中选择非“最可能”token 的可能性。在我们的案例中,我们通常希望选择最有可能的 token。另外,根据你的具体问题,有时候在选项中提供一个“逃生口”是很重要的。例如上面的“不确定”,在其他情况下,“无”或“无关紧要”可能也是合适的选择。

提线木偶技巧 - 让模型按你的意愿发言

这是我特别偏爱的一种提示技巧。值得一提的是,我们并不是发明这个技巧的唯一团队【不止我们想到这个】。

在几乎所有的大语言模型 API 中,你都需要在每次生成请求时提交对话状态。你得输入一些文本或 JSON,来展示用户、助理和系统各自说了什么。有个非常有意思的地方是,你实际上可以让助理“误以为”它已经开始回答了,即使实际上它并没有。你可以指示它按照你的意愿说任何话。这种做法在少样本提示中已经相当常见,但你还可以用它来避免模型在需要特定输出格式时的啰嗦或怪异回答,甚至在思考时也能用。

比如,当我们需要模型以特定的 JSON 格式输出 Pull Request 脚本时,我们会这么做:

User: Finally output the title and description according to the
below JSON format, it is very important to follow the format below exactly.
{
"title": "<title>",
"description": "<description">,
}
Assistant: {
"title": "
User:请按照下面的 JSON 格式准确输出标题和描述,确保严格遵循以下格式。
{
"title": "<title>",
"description": "<description>"
}
Assistant: {
"title": "

注意这个提示中加入了最后两行。这样一来,我们实际上是在“欺骗”模型,让它认为自己已经通过输出一个“{”字符开始回答了,因此它应该开始“用 JSON 的方式思考”。我们也避免让它对“title”这个关键字进行猜测,而是直接引导它开始写标题。这样做减轻了模型在回答时需要以特定格式开始的压力。它使得模型能更放松地直接给出我们想要的答案。(在上述示例中,“User:”和“Assistant:”指的是 OpenAI API 中的角色)

我们称这种方法为“提线木偶”,因为它实际上是在强迫模型按照我们的设定说话。模型看到这些指令,就会认为自己已经这么做过了。接着,为了保持一致,它会从那个地方继续思考下去。

这种技巧还可以用来让大语言模型遵循你的提示规则。比如,如果你的提示以这样的方式结束:

Assistant: First, I will think through the options, identifying the good
pieces of each approach.
Assistant:首先,我将审视各种方案,挑选出每个方案中的优点。

这实际上是在提醒模型,在给出回答之前,先进行深思熟虑。

结语

在这篇文章中,我们向您展示了我们创造的一些技术,并期待听到您发现的新技巧!Ada Cohen 和 Kevin Lei 在编写本文和开发这些技术上贡献巨大。

祝您创造提示愉快!

扩展阅读

本文基于众多学术论文、流行文章和资源。如果您希望深入了解,以下是一些我们认为非常值得一读的资料: