likes
comments
collection
share

Prompt工程和LLM的开发人员手册

作者站长头像
站长
· 阅读数 14

Prompt工程是与生成的人工智能模型进行沟通的艺术。在本文中,我们将介绍如何在GitHub中接入Prompt工程,以及如何使用它来构建自己的基于LLM的应用程序。

LLM(Large Language Models),这种创新的人工智能源于十年的开创性研究,能够在某些任务中超越人类。你不必拥有机器学习博士学位就可以使用LLM构建软件——开发人员已经在使用LLM通过基本的HTTP请求和自然语言提示构建软件了。

在本文中,我们将阐述Github结合LLM的形式,进而帮助其他开发者学习如何更好的运用这项技术。本文包含两个部分:

  • 第一个部分:LLM如何工作以及如何构建基于LLM的应用程序
  • 第二个部分:深入了解一个基于LLM的应用例子——Github Copilot代码提示

现在开始👇🏻👇🏻👇🏻

1、LLM的1600个tokens都在做什么?

众所周知,当我们在手机上面输入文字的时候,在输入框下面一般会有一些建议性文字出现,你可以评估并选择来完善你的输入内容,进而帮助你更快地完成内容的输入。这就非常像LLM干的事儿,但是是一种受限的LLM。

Prompt工程和LLM的开发人员手册

LLM不是在你的手机上发短信,而是预测下一组最好的字母,这些字母被称为“token”。就像你可以不断点击中间按钮来完成短信一样,LLM通过预测下一个单词来完成文档。它将一次又一次地继续这样做,只有当它达到token的最大阈值时,或者当它遇到一个发出“停止!这是文档的结束”信号的特殊token时,它才会停止。

不过,有一个重要的区别。你手机中的语言模型非常简单——它基本上是说,“仅根据输入的最后两个单词,下一个最有可能的单词是什么?”相比之下,LLM产生的输出更类似于“基于公共领域中已知存在的每个文档的全部内容,文档中最有可能的下一个token是什么?”通过在巨大的数据集上训练这样一个大型、架构良好的模型,LLM几乎可以表现为具有常识性:例如理解放在桌子上的玻璃球可能会滚落和破碎。

Prompt工程和LLM的开发人员手册

但请注意:LLM有时也会自信地产生不真实或不真实的信息,这些信息通常被称为“幻觉”或“虚构”。LLM似乎也会学习如何做最初没有被训练过的事情。从历史上看,自然语言模型是为一次性任务创建的,比如对推特的情绪进行分类,从电子邮件中提取业务实体,或识别类似的文档,但现在你可以要求像ChatGPT这样的人工智能工具执行一项从未受过训练的任务。

Prompt工程和LLM的开发人员手册

2、通过LLM构建应用

完善的文档引擎与每天涌现的LLM应用程序的惊人激增相去甚远,这些应用程序涵盖了对话式搜索、写作助手、自动化IT支持和代码完成工具,如GitHub Copilot。但是,所有这些工具怎么可能都来自一个有效的文档引擎工具呢?秘密是,任何使用LLM的应用程序实际上都是两个域之间的映射:用户域和文档域。

Prompt工程和LLM的开发人员手册

与LLM通信时的用户流图,在本例中为Dave的用户流。

看图读故事:上图左边是用户,他的名字叫戴夫,他有个问题:今天是他观看世界杯大型派对的日子,无线网络断了。如果他们不尽快把它修好,他将成为朋友们多年来的笑柄。Dave打电话给他的网络供应商,得到了一个自动助理。啊!但是,假设我们将自动助理实现为LLM应用程序。我们能帮助他吗?

问题的关键是甄别如何将用户域转化到文档域,一方面我们需要将用户的表达转化为文字,当自动支持代理说“请说明与有线电视相关的紧急情况的性质”时,Dave脱口而出:

哦,太可怕了!今天是世界杯决赛。我的电视连接到了无线网络,但我撞到了柜台,无线网络盒子掉了下来,坏了!现在,我们不能看比赛了。

我们现在有了文本,但它没有多大用处。也许你会想象这是一个故事的一部分,然后继续下去,“我想,我会打电话给我哥哥,看看我们是否可以和他一起看比赛。”一个没有背景的LLM同样会创造Dave故事的延续。因此,让我们给LLM一些上下文,并确定这是什么类型的文档:

###ISP IT支持记录:
以下是ISP客户Dave Anderson和IT支持专家Julia Jones之间的对话录音。这份成绩单是Comcrash为其客户提供卓越支持的一个例子。

*戴夫:哦,太可怕了!今天是重要的比赛日。我的电视连接到了无线网络,但我撞到了柜台,无线网络盒子掉了下来,坏了!现在我们不能看比赛了。

*Julia:

现在,如果你发现了这个伪文档,你将如何完成它?根据额外的背景,你会发现Julia是一位IT支持专家,而且显然是一位非常优秀的专家。你会认为接下来的话是明智的建议,可以帮助Dave解决他的问题。朱莉娅不存在并不重要,这也不是一段录音对话——重要的是,这些额外的单词为完成提供了更多的背景。LLM也做同样的事情。在阅读了这份部分文件后,它将尽最大努力以有益的方式完成朱莉娅的对话。

但是,我们还可以做更多的工作来为LLM制作最佳文档。LLM对有线电视故障排除了解不多。(好吧,它已经阅读了所有在线发布的手册和it文档,但请留在这里)。让我们假设它在这个特定领域缺乏知识。我们可以做的一件事是搜索可能对Dave有帮助的额外内容,并将其放入文档中。让我们假设我们有一个投诉搜索引擎,它可以让我们找到过去在类似情况下有用的文档。现在,我们所要做的就是在一个自然的地方将这些信息编织到我们的伪文档中。

*朱莉娅:(在她的公文包里转来转去,为戴夫的要求拿出了完美的文件)
常见的互联网连接问题。。。
<。。。在这里,我们插入了来自我们的客户支持历史数据库的搜索结果的1页文本…>
(看完文件后,Julia提出以下建议)
*Julia:

现在,鉴于这一全文,LLM的条件是使用植入的文档,在“有用的IT专家”的背景下,该模型将产生响应。此回复考虑了文件以及Dave的具体要求。

最后一步是从文档域移动到用户的问题域。对于本例,这意味着只需将文本转换为语音。由于这实际上是一个聊天应用程序,我们会在用户和文档域之间来回切换几次,每次都会使文字记录更长。

这个例子的核心是Prompt工程。在这个例子中,我们制作了一个具有足够上下文的提示,让人工智能产生尽可能好的输出,在这种情况下,这为Dave提供了有用的信息,让他的Wi-Fi重新启动并运行。在下一节中,我们将了解GitHub如何改进GitHub Copilot的Prompt工程技术。

3、Prompt工程的科学与艺术

在用户域和文档域之间进行转换是Prompt工程的领域——由于我们已经在GitHub Copilot上工作了两年多,我们已经开始在这个过程中识别一些模式。

这些模式帮助我们将管道形式化,我们认为这是一个适用的模板,可以帮助其他人更好地为自己的应用程序进行Prompt工程。现在,我们将通过在我们的人工智能配对程序员GitHub Copilot的上下文中对其进行检查来展示这个管道是如何工作的。

3.1、Github Copilot的Prompt管道工程

从一开始,GitHub Copilot的LLM就建立在OpenAI的人工智能模型上,这些模型一直在变得越来越好。但没有改变的是Prompt工程的核心问题的答案:模型试图完成什么样的文档?

我们使用的OpenAI模型经过培训,可以在GitHub上完成代码文件。忽略了一些过滤和分层步骤,这些步骤并没有真正改变Prompt工程游戏,这种分布几乎是根据数据收集时最近提交给main的单个文件内容。

LLM解决的文档完成问题是关于代码的,而GitHub Copilot的任务就是完成代码。但两者非常不同。

以下是一些示例:

  • 提交到main的大多数文件都已完成。首先,它们通常是编译的。大多数情况下,用户在键入代码时,代码不会编译,因为在推送提交之前会修复不完整的代码。
  • 用户甚至可以按层次顺序编写代码,先编写方法签名,然后编写正文,而不是逐行或混合样式。
  • 编写代码意味着四处跳跃。特别是,人们的编辑通常需要他们跳到文档中并在那里进行更改,例如,向函数添加参数。严格来说,如果Codex建议使用一个尚未导入的函数,无论它有多大意义,那都是一个错误。但作为GitHub Copilot的一个建议,它将是有用的。

问题是,仅仅根据光标前的文本预测最有可能的延续来提出GitHub Copilot建议将是浪费机会。这是因为它忽略了令人难以置信的丰富背景。我们可以使用该上下文来指导建议,如元数据、光标下的代码、导入的内容、存储库的其余部分或问题,并为AI助手创建强有力的提示。

软件开发是一个相互关联的、多模式的挑战,我们能够驯服并呈现给模型的复杂性越多,你的完成就会越好。

4、GitHub Copilot的实现流程

Step1: 生成上下文

GitHub Copilot存活在一个IDE的上下文中,比如Visual Studio Code(VS Code),它可以使用它能让IDE告诉它的任何东西——但前提是IDE对此很快。像GitHub Copilot这样的交互式环境中,每一毫秒都很重要。GitHub Copilot承诺会处理常见的编码任务,如果它想做到这一点,它需要在开发人员开始在IDE中编写更多代码之前向他们展示自己的解决方案。我们的粗略启发式算法表明,我们每花10毫秒提出一个建议,它及时到达的机会就会减少1%。

那么,我们能快速说些什么呢?这是一个例子。将此建议视为一个简单的Python:

Prompt工程和LLM的开发人员手册

错误的事实证明用户实际上想要编写Ruby,如下所示:

Prompt工程和LLM的开发人员手册

这两种语言有足够相似的语法,因此只有几行可能是不明确的,尤其是当它接近文件的开头时,我们遇到的大部分都是样板注释。但像VS Code这样的现代IDE通常知道用户在用什么语言写作。这使得语言混淆对用户来说尤其令人讨厌,因为它们打破了“计算机应该知道”的隐含期望(毕竟,大多数IDE都强调语言语法)。

因此,让我们将语言元数据放入我们可能想要包含的上下文堆中。事实上,让我们也添加整个文件名。如果它可用,它通常会通过扩展名暗示语言,并为文件中的预期内容设定基调——这些小而简单的信息不会扭转局势,但有助于包括在内。

在光谱的另一端,还有存储库的其余部分。假设您有一个定义抽象类DataReader的文件。还有一个定义了CsvReader子类。现在您正在编写一个新文件,定义另一个子类SqlReader。在编写新文件时,您可能还想检查两个现有文件,因为它们传达了您需要实现什么以及如何实现的有用背景。通常,开发人员会在不同的选项卡中打开这些文件,并切换以提醒自己定义、示例、类似模式或测试。

如果这两个文件的内容对你有用,那么它很可能对人工智能也有用。所以,让我们将其添加为上下文!毕竟,IDE知道存储库中的其他哪些文件在同一窗口中作为选项卡打开。存储库可能有数百甚至数千个文件,但只有一些文件是打开的,这强烈暗示了它们可能对他们现在正在做的事情有用。当然,“一些”可能意味着很多东西,所以我们不考虑超过20个最新的标签。

Step2: 片段

LLM上下文中的无关信息会降低其准确性。此外,源代码往往很长,因此即使是一个文件也不能保证完全适合LLM的上下文窗口(大约五分之一的时间会出现这种问题)。因此,除非用户对选项卡的使用非常节俭,否则我们根本无法包含所有选项卡。

从其他文件中选择要包含的代码是很重要的,所以我们将文件剪切成(希望)不超过60行的自然、重叠的片段。当然,我们不想真正包括所有重叠的片段——这就是为什么我们对它们进行评分,只取最好的。在这种情况下,“分数”是为了反映相关性。为了确定片段的分数,我们使用Jaccard相似度,这是一个可以用来衡量样本集相似性或多样性的统计数据。(它的计算速度也非常快,这对减少延迟非常有用。)

Step3:装饰一下

现在我们有了一些上下文,我们想传递给模型。但是怎么做呢?Codex和其他模型不提供API,您可以在其中添加其他文件,也可以在其中指定文档的语言和文件名。他们完成了一份文件。如上所述,您需要以自然的方式将上下文注入到该文档中。

路径和名称可能是最简单的。许多文件以序言开头,序言中提供了一些元数据,如作者、项目名称或文件名。因此,我们将假设这里也发生了这种情况,并在最顶部添加一行,内容类似于#filepath:foo/bar.py或//filepath:foop.bar.js,具体取决于文件语言中的注释语法。

有时路径未知,例如尚未保存的新文件。即便如此,只要IDE知道,我们也可以尝试至少指定语言。对于许多语言,我们有机会包括#/usr/bin/python或#/usr/bin/node。这是一个巧妙的技巧,可以很好地防止错误的语言识别。但这也有点危险,因为带有shebang行的文件是所有代码中有偏见的子群体。因此,让我们对语言标识错误的危险性很高的短文件执行此操作,而对较大或命名的文件则避免执行此操作。

如果评论可以作为路径或语言等微小信息的传递系统,我们也可以让它们作为60行相关代码的大块深度挖掘的传递系统。

注释是通用的,注释掉的代码遍布GitHub。让我们来看看一些最常见的例子:

  • 不再适用的旧代码
  • 已删除的功能
  • 当前代码的早期版本
  • 专门为文档目的而留下的示例代码
  • 从代码库的其他部分提取的代码

让我们从最后一组例子中汲取灵感。熟悉组(1)-(3)会使模型变得更容易,但我们的片段旨在模仿组(4)和(5):

# compare this snippet from utils/concatenate.py: 
# def crazy_concat(a, b): 
# return str(a) + str(b)[::-1]

请注意,包含代码段源的文件名和路径可能很有用。结合当前文件的路径,这可能会指导引用导入的完成。

Step4:确定优先级

到目前为止,我们已经从许多来源获取了许多上下文:光标正上方的文本、光标下方的文本、其他文件中的文本,以及语言和文件路径等元数据。

在绝大多数情况下(约95%),我们必须做出艰难的选择,我们可以或不能包括什么。

我们通过思考我们可能包含的项目作为“愿望”来做出选择。每次我们发现一段上下文,比如打开的选项卡上的注释片段,我们都会许下愿望。愿望是有优先权的,例如,舍邦线的优先权很低。相似性得分低的代码段几乎不高。相反,光标正上方的行具有最大优先级。愿望在文件中也有一个想要的位置。shebang行需要是第一项,而光标正上方的文本排在最后——它应该直接在LLM完成之前。

选择要填充哪些和放弃哪些的最快方法是按优先级对愿望列表进行排序。然后,我们可以继续删除优先级最低的愿望,直到剩下的符合上下文窗口。然后,我们再次按照文档中的预期顺序进行排序,并将所有内容粘贴在一起。

Step5:人工智能做自己的事

现在,我们已经收集了一个信息提示,是时候让人工智能想出一个有用的完成了。我们一直面临着一个非常微妙的权衡——GitHub Copilot需要使用一个功能强大的模型,因为质量决定了有用建议和分心之间的所有区别。但同时,它需要是一个能够加快速度的模型,因为延迟决定了有用建议和根本无法提供建议之间的区别。

那么,我们应该选择哪种人工智能来完成任务:最快还是最准确?很难提前知道,所以OpenAI与GitHub合作开发了一组模型。我们向开发人员展示了两种不同的模型,但发现人们从更快的模型中获得了最大的收益(就接受和保留的完成量而言)。从那时起,进一步的优化大大提高了模型的速度,因此当前版本的GitHub Copilot有了一个更强大的模型支持。

Step6:现在,交给你!

生成型人工智能生成一个字符串,如果它没有停止,它就会继续生成,并将继续进行,直到它预测到文件的结尾。这将浪费时间和计算资源,因此您需要设置“停止”标准。

最常见的停止标准实际上是寻找第一个换行符。在许多情况下,软件开发人员似乎希望完成当前行,但不是更多。但GitHub Copilot最神奇的贡献是它同时建议多行代码。

当涉及单个语义单元时,例如函数体、if分支或类,多行补全感觉很自然。GitHub Copilot寻找这样一个块正在启动的情况,可能是因为开发人员刚刚编写了开始,例如标头、if guard或类声明,也可能是当前正在编写开始。如果块体看起来是空的,它将尝试为其提出建议,并且只有当块看起来完成时才停止。

这就是当建议出现在程序员面前的时候。剩下的,正如他们所说,是历史10倍的发展。

如果你有兴趣了解更多关于即时工程的知识,以及如何完善自己的技术,请查看我们的GitHub Copilot入门指南

原文翻译自:github.blog/2023-07-17-…

转载自:https://juejin.cn/post/7261885354528899129
评论
请登录