Press "Enter" to skip to content

我在推动提示工程的极限时学到了什么

一个讽刺的提示工程描绘。具有讽刺意味的是,这张DALL-E2生成的图片是作者使用提示工程生成的,提示是“一个疯狂的科学家正在把一卷卷轴递给一个以复古风格生成的人工智能机器人”,加上一个变化,再加上外部绘画。

我花了过去两个月的时间构建了一个大语言模型(LLM)驱动的应用程序。这是一个令人兴奋、智力刺激的经历,有时也会令人沮丧。我的提示工程的整个概念,以及关于LLMs的可能性,在项目的过程中都发生了变化。

我很想与您分享一些我最大的收获,以期揭示提示工程中经常不言而喻的一些方面。我希望在阅读了我的经历之后,您能够做出更明智的提示工程决策。如果您已经涉足提示工程,我希望这可以帮助您推进自己的旅程!

为了提供背景信息,以下是我们将要学习的项目的TL;DR:

  • 我和我的团队构建了VoxelGPT,这个应用程序结合了LLMs和FiftyOne计算机视觉查询语言,可以通过自然语言搜索图像和视频数据集。VoxelGPT还回答关于FiftyOne本身的问题。
  • VoxelGPT是开源的( FiftyOne也是!)。所有的代码都可以在GitHub上找到。
  • 您可以在gpt.fiftyone.ai上免费尝试VoxelGPT。
  • 如果您想了解我们是如何构建VoxelGPT的,您可以在此处阅读更多信息。

现在,我将提示工程的教训分为四个类别:

  1. 通用教训
  2. 提示技术
  3. 实例
  4. 工具

通用教训

科学?工程?黑魔法?

提示工程既是实验,也是工程。有无限种编写提示的方式,从问题的具体措辞,到您提供的上下文的内容和格式。这可能会让人不知所措。我发现,最简单的方法是从简单的开始,建立一种直觉,然后测试假设。

在计算机视觉中,每个数据集都有自己的模式、标签类型和类名。VoxelGPT的目标是能够处理任何计算机视觉数据集,但我们只从单一数据集MS COCO开始。将所有的额外自由度固定下来,可以让我们更好地了解LLM在第一时间编写语法正确的查询的能力。

一旦您确定了在有限上下文中成功的公式,然后找出如何概括和建立。

使用哪些模型?

人们说,大语言模型最重要的特征之一是它们相对可互换。理论上,您应该能够将一个LLM换成另一个,而不会在很大程度上改变连结组织。

虽然改变您使用的LLM通常只需要简单地交换API调用,但在实践中,确实会出现一些困难。

  • 某些模型的上下文长度比其他模型短得多。转换到上下文更短的模型可能需要重大的重构。
  • 开源很棒,但开源LLM不如GPT模型表现得好(至少目前是这样)。另外,如果您部署一个使用开源LLM的应用程序,您需要确保运行模型的容器具有足够的内存和存储空间。这可能比只使用API端点更麻烦(也更昂贵)。
  • 如果您开始使用GPT-4,然后因成本问题转而使用GPT-3.5,您可能会震惊于性能的下降。对于复杂的代码生成和推断任务,GPT-4要好得多。

LLM 在哪里使用?

大型语言模型非常强大。但仅仅因为它们能够完成某些任务并不意味着你需要或者甚至应该使用它们来完成那些任务。最好的方法是把 LLM 视为一种启用器。LLM 不是整个解决方案:它们只是其中的一部分。不要期望大型语言模型能够胜任所有工作。

举个例子,你正在使用的 LLM 可能可以(在理想情况下)生成格式正确的 API 调用。但是,如果你知道 API 调用的结构应该是什么样子的,而且你实际上有兴趣填写 API 调用的某些部分(变量名、条件等),那么只需使用 LLM 执行这些任务,并使用(经过适当后处理的)LLM 输出自己生成结构化的 API 调用。这样更便宜、更高效、更可靠。

一个完整的 LLM 系统肯定有很多连接组织和经典逻辑,以及大量的传统软件工程和 ML 工程组件。找到最适合你的应用程序的方法。

LLM 有偏见

语言模型既是推理引擎,也是知识库。往往,LLM 的知识库方面对用户非常有吸引力,许多人使用 LLM 作为搜索引擎替代品!到目前为止,任何使用过 LLM 的人都知道它们容易制造虚假“事实”——这种现象被称为幻觉。

然而,有时候 LLM 会遇到相反的问题:它们过于坚定地固定在训练数据中的事实上。

在我们的例子中,我们试图提示 GPT-3.5 确定将用户的自然语言查询转换为有效的 FiftyOne Python 查询所需的适当 ViewStages(逻辑操作的流水线)。问题是,GPT-3.5 知道 `Match` 和 `FilterLabels` ViewStages,这些功能在 FiftyOne 中已经存在一段时间了,但它的训练数据没有包括最近添加的功能,其中可以使用 `SortBySimilarity` ViewStage 找到与文本提示相似的图像。

我们尝试传入 `SortBySimilarity` 的定义、使用细节和示例。我们甚至试图指示 GPT-3.5 不能使用 `Match` 或 `FilterLabels` ViewStages,否则将受到惩罚。无论我们尝试什么,LLM 仍然倾向于它所知道的,无论是否是正确的选择。我们在对抗 LLM 的本能!

最终,我们不得不在后处理中处理这个问题。

痛苦的后处理是不可避免的

无论你的示例有多好;无论你的提示有多严格——大型语言模型无论如何都会产生幻觉,给你不正确的响应格式,并在不理解输入信息时发脾气。LLM 最可预测的属性是其输出的不可预测性。

我花费了大量的时间编写用于模式匹配和纠正幻觉语法的例程。后处理文件最终包含了近 1600 行 Python 代码!

其中一些子例程非常简单,比如在逻辑表达式中添加括号,或将“and”和“or”更改为“&”和“|”。有些子例程则更加复杂,比如验证 LLM 响应中实体的名称,如果满足某些条件,则将一个 ViewStage 转换为另一个,确保方法的参数数量和类型有效。

如果你正在一个相对封闭的代码生成环境中使用提示工程,我建议采取以下方法:

  1. 使用抽象语法树(Python 的 ast 模块)编写自己的自定义错误解析器。
  2. 如果结果在语法上无效,请将生成的错误消息输入到 LLM 中并让它再试一次。

这种方法无法解决语法有效但结果不正确的情况。如果有人对此有好的建议(除了 AutoGPT 和“show your work”风格的方法),请告诉我!

提示技术

越多越好

为了构建 VoxelGPT,我使用了似乎是太阳下所有提示技术:

  • “你是专家”
  • “你的任务是”
  • “你必须”
  • “你将受到惩罚”
  • “以下是规则”

没有任何这样的短语组合能够确保一定类型的行为。聪明的提示仅仅是不够的。

话虽如此,你在提示中使用的这些技巧越多,就越能将LLM引导到正确的方向!

例子 > 文档

现在已经是公认常识了(也是常识!),即例子和其他上下文信息,如文档,都可以帮助从大型语言模型中引出更好的响应。我发现这对于VoxelGPT也是如此。

但是,一旦您添加了所有直接相关的例子和文档,如果在上下文窗口中还有多余的空间,该怎么办?在我的经验中,我发现与之相关性较小的例子比与之相关性较小的文档更为重要。

模块化 >> 单体化

你可以将一个宏大的问题分解成更小的子问题,这样做越多越好。与其提供数据集模式和一系列端到端的例子,不如识别出单个选择和推理步骤(选择推理提示),并在每个步骤中仅提供相关信息。

这是出于以下三个原因的考虑:

  1. LLMs在单个任务上的表现比在多个任务上要好。
  2. 步骤越小,输入和输出的消毒就越容易。
  3. 作为工程师,了解应用逻辑是一个重要的练习。LLMs的目的不是使世界成为一个黑匣子,而是使新的工作流程成为可能。

例子

需要几个例子?

提示工程的一个重要部分是确定在给定任务中需要多少个例子。这是高度问题特定的。

对于某些任务(如有效的查询生成和基于FiftyOne文档回答问题),我们可以不使用任何例子。对于其他任务(如标签选择、聊天历史是否相关以及标签类别的命名实体识别),我们只需要一些例子就能完成工作。然而,我们的主要推理任务几乎有400个例子(这仍然是整体性能的限制因素),因此我们只在推理时传递最相关的例子。

在生成例子时,请尝试遵循以下两个准则:

  1. 尽可能全面。如果您有有限的可能性空间,那么请尝试为每种情况至少提供LLM一个例子。对于VoxelGPT,我们尝试至少为每个ViewStage使用的每种语法上正确的方式提供一个例子,通常为每种语法提供几个例子,以便LLM可以进行模式匹配。
  2. 尽可能一致。如果您将任务分解为多个子任务,请确保从一个任务到另一个任务的例子是一致的。您可以重用例子!

合成例子

生成例子是一项费力的过程,手工制作的例子只能带你走得这么远。提前想到每种可能的情况是不可能的。在部署应用程序之前,您可以记录用户查询并使用它们来改进您的示例集。

然而,在部署之前,您最好的选择可能是生成合成例子。

以下是两种生成合成例子的方法,您可能会发现有用:

  1. 使用LLM生成例子。您可以要求LLM改变其语言,甚至模仿潜在用户的风格!这对我们没有用,但我相信对许多应用程序而言这是可行的。
  2. 基于输入查询本身中的元素,以编程方式生成例子-可能具有随机性。对于VoxelGPT,这意味着基于用户数据集中的字段生成例子。我们正在将其纳入我们的管道中,迄今为止,我们看到的结果是有希望的。

工具

LangChain

LangChain很受欢迎的原因是:这个库使得将LLM的输入和输出连接在复杂方式中变得很容易,抽象掉了繁琐的细节。尤其是模型和提示模块。

话虽如此,LangChain肯定还在不断发展:他们的Memories、Indexes和Chains模块都存在显著的局限性。以下是我在试图使用LangChain时遇到的一些问题:

  1. 文档加载器和文本分割器:在LangChain中,文档加载器应该将来自不同文件格式的数据转换为文本,而文本分割器应该将文本分割成语义上有意义的块。VoxelGPT通过检索最相关的文档块并将它们导入提示来回答有关FiftyOne文档的问题。为了对FiftyOne文档的问题生成有意义的答案,我不得不有效地构建自定义加载器和分割器,因为LangChain没有提供适当的灵活性。
  2. 向量存储:LangChain提供向量存储集成和基于向量存储的检索器,以帮助查找相关信息并将其纳入LLM提示中。这在理论上是很好的,但具体实现缺乏灵活性。我不得不使用ChromaDB编写自定义实现,以便预先传递嵌入向量,而不是每次运行应用程序时重新计算它们。我还不得不编写自定义检索器来实现我所需的自定义预过滤。
  3. 使用来源进行问题回答:在构建超过FiftyOne文档的问题回答时,我找到了一个合理的解决方案,利用了LangChain的“检索QA”链。当我想要添加来源时,我认为只需将该链替换为LangChain的“RetrievalQAWithSourcesChain”即可。然而,差劲的提示技术意味着这个链展示了一些不幸的行为,例如关于迈克尔·杰克逊的幻觉。再次,我不得不自行解决问题。

这意味着什么?也许更容易自己构建组件!

向量数据库

向量搜索可能非常重要,但这并不意味着您需要在项目中使用它。我最初使用ChromaDB实现了我们的相似示例检索程序,但由于我们只有数百个示例,最终我切换到了精确最近邻搜索。我确实需要自己处理所有元数据过滤,但结果是一个更快的程序,更少的依赖性。

TikToken

将TikToken加入方程非常容易。总共,TikToken为项目添加了不到10行代码,但允许我们在计数令牌和尝试将尽可能多的信息适合上下文长度时更加精确。这是工具方面唯一真正不需要思考的选择。

结论

有很多LLMs可供选择,很多闪亮的新工具和许多“提示工程”技术。所有这些都可能令人兴奋和不知所措。使用提示工程构建应用程序的关键是:

  1. 分解问题;构建解决方案
  2. 将LLMs视为使能器,而不是端到端解决方案
  3. 仅在工具使您的生活更轻松时使用它们
  4. 拥抱实验!

去构建一些酷的东西吧!

Leave a Reply

Your email address will not be published. Required fields are marked *