我提前获得了ChatGPT API的访问权限,但差点把它给玩坏了
今天和大家分享一篇文章,作者是 Alistair [email protected] ,原文标题:I got early access to ChatGPT API and then pushed it to it’s limits. Here’s what you need to know. (译:f.chen@真格基金)欢迎大家在评论区和我们交流~
前言
过去的两个月里,我们有幸通过 YC 获得了 ChatGPT Alpha API 的访问权限。起初,我对将聊天功能添加到 Buildt 中犹豫不决——我一直担心,随着该功能的加入,我们的产品会被简单地归类为 VSCode 中的 ChatGPT,而实际上,我们正在构建的技术,例如语义代码库搜索引擎,要比这强大且细致得多。然而,随着时间的推移,我意识到,将这两种技术关联起来实际上会带来真正的价值 ——从本质上讲,给聊天机器人(它在代码编写和解释技能方面的所有已知能力和优势)提供整个代码库的背景信息是目前通过其他方式无法实现的,能在代码库中做到类似「更新我的初始组件,使其有一个跳过按钮直接带用户到 Dashboard」这样的事是极其厉害的,这意味着它能够定位上下文本身,并以极快的速度轻松执行更改。
速览
你可能已经在其他地方看到过这个信息:ChatGPT API 与标准 playground 模型的工作方式略有不同。在 Alpha 版本中,我们必须使用一些特殊的 token 来包装我们的信息,以使它们发挥作用,其中最明显的 token 是<|im_start|>,<|im_start|>,这两个 token 将用户和聊天机器人之间传输的信息包装起来,并划定了消息开始和结束的位置。值得庆幸的是,在公开版本的 API 中,无需使用这些 token,因为 API 已为用户抽象出了类似内容:
对话消息分为三类:系统(system)、助手(assistant)和用户(user)——这些信息说明谁在特定时刻「说话」,换句话说,这是谁产生的行为。系统信息是特殊的,只能在对话开始时发送一次。我在本文后面会更深入地讨论相关 Prompt。当涉及发送/接收信息时,API 的工作方式是相当原始的,当前的上下文基本上被存储为单个长字符串,需要用户操作以便对话正常进行——另外值得注意的是,每条消息都包括之前的所有上下文,再叠加新的消息,因此每发送一条消息,用户所使用的 token 数量就会增加,目前还没有仅针对每条新消息使用的额外 token 计费的方法。
ChatGPT vs davinci-003
到目前为止,许多人只玩过 davinci 系列的 LLM,而 davinci 和 ChatGPT 的 Prompt 风格之间存在一些微妙差异。例如,k-shot 示例的做法在 ChatGPT 中不太普遍,原因在我看来有几个:首先,这些示例可能占据查询中的大量上下文,而作为聊天机器人,这个查询将会被经常运行,因此用户在 Prompt 中加入的任何 k-shots 都将在每个 API 请求中运行,这可能很快变得非常浪费。ChatGPT 的 Prompt 也包括一些机器人的个性化信息,例如「你的名字是 BuildtBot,是一名专门从事代码搜索和理解的 AI 软件工程师」,这在 davinci 的 Prompt 中也可以做到,但我认为这种情况不太普遍。ChatGPT 的 Prompt 本质上更加零样本(zero-shot)化,用户的确能感受到模型在遵从 Prompt,但有时候如果反复出现反常行为,又需要在系统 Prompt 中提供一两个 k-shot 示例。通常,davinci 的 Prompt 是一些特定「功能」,执行给定的任务,但是对于聊天机器人来说,input 通常比常规的 GPT-3 Prompt 更加日常和不可预测。最终结果是,在某些情况下,用户会看到 ChatGPT 不服从 Prompt 的意外行为,仅让它不要执行某个行为往往是不够的。
OpenAI 还提倡,在许多情况下,应该用 ChatGPT API 替换 davinci-003,因为它便宜 10 倍——仅需 0.002 美元/000 token!在这些情况下,用户可以像标准的 davinci-003 Prompt 格式一样使用 ChatGPT API,其中系统 Prompt 是用户的 Prompt 正文,用户信息是用户的 input,而助手消息是 output。
系统 Prompt
根据我目前的经验,系统 Prompt 对于聊天机器人来说是最重要的。正如我提到的,系统 Prompt 是机器人的「简报(Brief)」,它定义了机器人的性格、目的和可执行操作。不同的应用程序需要截然不同的 Prompt,有些 Prompt 会比其他 Prompt 长得多——例如,Buildt 的系统 Prompt 超过1000 token 长度,这很长,但在我们的用例中,由于可以在代码库中执行许多不同的操作,我们需要非常严格地定义机器人的可以做什么和不可以做什么。在许多情况下,仅定义模型的语气、名称和一般能力就足够了,但在机器人可以实际执行其沙盒之外的动作(我称之为子程序)时,最终将使用更多的 token 来编码这种行为。一个非常基本的系统 Prompt 示例如下:
子程序和内存编辑
让用户的聊天机器人拥有「做」事的能力是将其与原始 ChatGPT 区分开来的核心要素之一。在其沙盒之外执行操作是非常有吸引力并且令人兴奋的,但这其中也有挑战和潜在风险。当把这些 LLM 的 output(不受信任)作为 input 传递给另一个系统时,用户应该保持警惕。例如,我在 Twitter 上看到过许多人在执行 ChatGPT 编写的代码——这让我感到恐惧,请不要这样做,除非用户正确地对 LLM 的 output 进行了无害化处理。
接下来,一旦确定并解析了请求(我用了子字符串操作和正则表达式的组合),用户就可以执行所需的任何子程序,在我们的用例中,我们将查询传递给我们的语义代码库搜索引擎。一旦该子程序完成,我们就需要将结果传递到上下文窗口中,这是内存插入——让聊天机器人相信它得出了这些结果,因为一旦用户提出下一个请求,它就没有办法回溯并执行了。插入结果非常简单,只需将结果附加到当前的上下文字符串即可,但是需要注意的是,在添加结果前,用户必须在子程序中加入结束标签:
</|search_query|>\\\\\\\\\\\\\\\\n[your results here]
这很重要,首先停止序列意味着结束标签永远不会被自然地写入上下文窗口,其次这些模型是根据它们所看到的东西执行操作的,如果用户不在这里加入它,下一次有人想要在对话中执行此操作时,它可能不会响应其中包含的停止序列,因为它看到在这种情况下停止序列被省略了。一旦插入结果,用户就可以结束该回合,插入 <|im_end|> 消息,或者让机器人继续输出结果,就好像它自己想出了结果一样——这取决于用户的实际使用情况。
此时,很明显,有很多字符串操作需要执行,我将在本文的实现技巧部分深入讨论这个问题,但我要说,值得一试的是以下两点:
这是我在 ChatGPT 世界中找到的最接近 Let’s think step-by-step 等效功能的方法。它强制模型表达其推理并预先考虑其可能性答案,然后再提供实际答案。关于这一点,我给出的最后一个提示:为了确保模型始终产生这些前置消息标签,我始终在助手完成中以 <|user_intonation> 开始,并让它从那里继续执行,这样它就不会忽略或选择省略该步骤。
上下文窗口管理
这是一个棘手的问题,特别是 ChatGPT API 的 token 限制只有 4000 个,实际上根本不长,用户还必须自己管理上下文,如果用户的请求超过 4000 个 token ,则像任何其他 OpenAI 请求一样,用户将收到一个 400 错误回复。有传言称,将来会有一个 8000 token 版本,但还没有关于那个版本何时推出的消息。在此之前,我们必须想出一些方法来管理上下文窗口,以尽量确保用户的核心逻辑得以保留。如果用户有一个能够执行子程序的聊天机器人,那么这将是特别困难的,因为至少在我们的用例中,这些程序的结果可能非常冗长,并且占用了上下文窗口的很大一部分。有几件事情需要记住:每个请求中必须始终包含系统 Prompt,同时,仅仅删除上下文窗口中最早的消息并不一定总是最佳解决方案。这可能是整个过程中最大的「因人而异」因素,因为其实施将严重依赖于聊天机器人的使用方式。如果机器人是在用户不太想重新开始的情况下运行的,那么修剪早期的信息应该没问题,但并非总是如此。
实施技巧
我要重申,有一个强大的功能来剥离用户不应该从聊天信息中看到的内容是非常重要的。我们可以用标签将内容包装起来,以确保这些内容不会在最终的聊天信息中呈现,例如,如果用户的查询涉及搜索以使其实现,但本身并不是一个搜索查询,则不应在返回的信息中显示搜索结果。
(如果你希望阅读英文原文,请戳下方原文链接🔗)
沐曦 | 驭势科技 | 芯耀辉 | 森亿智能 | AutoX
微信扫码关注该文公众号作者