3 月 1 日发布的 ChatGPT API 将调用成本降低到了 $0.002/1000 token,是之前 davinci 的 1/10。OpenAI 表示,在 12 月 ChatGPT 发布以来,已经将其成本降低了 90% 。这样的成本降低速度,让我们想起了一个熟悉的概念:摩尔定律是的,Sam Altman 在 2 月 27 日发推说:新时代的摩尔定律可能即将到来——世界上的(人工)智能每隔 18 个月翻倍。
记得在 ChatGPT 刚发布的时候,有人通过计算得出 ChatGPT 每次对话的成本太高,无法与 Google 搜索的成本抗衡的结论。但在历史上,我们看到了一次又一次成本以 Wright‘s Law 下降的例子,使得我们现在可以开到 3 万美金的特斯拉,使用不到 1000 美金的 iPhone,而 4 张 4090 显卡的算力就可以超过刘慈欣写《三体》时世界上最顶级的超算。
The future is faster than you think,jump in.
——真格基金管理合伙人 戴雨森
今天和大家分享一篇文章,作者是 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 示例如下:
You are an intelligent AI assistant. Answer as concisely as possible.
你是一个聪明的 AI 助手。尽可能简洁地回答问题。
正如用户所看到的,这只是文本,但它永远不会被包含在聊天信息中——用户的系统 Prompt 会随着每个请求发送,因此用户在此处使用的 token 是恒定开销。
我很喜欢在编写聊天机器人时使用的一种方法(我们在 BuildtBot 中使用了这种方法),像为销售人员提供呼叫客户说明一样编写系统 Prompt,例如「如果用户看到 <X> 类型的行为,则用 <Y> 回应」,「尽可能有帮助且简洁地回答问题,同时始终确保用户的谈话围绕主题展开」,然后用「好的,用户即将问用户一个问题」来结束 Prompt 。我不能确定原因,但这种方法似乎对机器人理解和响应用户请求的能力有微小的改进。当用例涉及许多不同的场景时,即有些情况下机器人可以做某些事情而在其他情况下则不能,我发现最好不要向 LLM 发出散文式的请求,询问它该做什么和不该做什么,而只是给它一些 K-shot 的示例(有时可能需要与详细说明能力一样多的 token)。
关于系统 Prompt 的最后一点是,我注意到它的影响力和权重会随着上下文窗口的延长而逐渐减弱。这可能只是我的个人问题,但我注意到,当用户越接近最大 4000 tokens 的上下文窗口限制时,它遵守其 Prompt 的可能性就越大(特别是如果它们是复杂的和基于逻辑的)。我尚未找到一个很好的解决方案,也许在整个上下文窗口中定期提醒它的目的可以解决这个问题,但这些将计入总 token 限制,因此根据用例可能弊大于利。
子程序和内存编辑 
让用户的聊天机器人拥有「做」事的能力是将其与原始 ChatGPT 区分开来的核心要素之一。在其沙盒之外执行操作是非常有吸引力并且令人兴奋的,但这其中也有挑战和潜在风险。当把这些 LLM 的 output(不受信任)作为 input 传递给另一个系统时,用户应该保持警惕。例如,我在 Twitter 上看到过许多人在执行 ChatGPT 编写的代码——这让我感到恐惧,请不要这样做,除非用户正确地对 LLM 的 output 进行了无害化处理。
我已经找到了一种相当好的方法让聊天机器人执行操作,即将这些操作插入上下文窗口,再继续执行。这正是我们在 Buildt 搜索中所做的,BuildtBot 能意识到用户正在查找某些东西,无论是明确还是隐晦地说出「找到<X>」,只要用户所要求的任务在某种程度上依赖于搜索完成,例如 「更新登录组件以进行<XYZ>」隐含着执行任务依赖于「找到登录组件」。我稍后会详细阐述我实现识别用户意图的方式。
首先,子程序的运行原则是,聊天机器人通过停止序列打断自己,以示它认为需要运行子程序。我们可以通过使用指定的特殊标签 / token 来表示这些不同序列来实现这一点,我发现使用 ChatGPT 专有格式标签效果很好,例如,我们将 </|search_query|>作为标签,并将在适当的时候为我们将要推出的 Coach 和 Genie 功能添加更多的标签,这些标签及其显示标准会在用户的系统 Prompt 中被清晰定义。标签在实践中的工作方式如下:当涉及语法时,我们将它们像 React/JSX 那样打标,例如,在 Buildt 中创建一个搜索子程序的真实方法如下:
<|search_query query={"Find the login component"}|></|search_query|>
现在,这可能看起来很奇怪,但这其中有些方法,最重要的部分是结束标签 </|search_query|>,因为这个标签是停止序列,所以实际上这个标签永远不会出现在 output 中。API 会告诉我们它的终止原因,因此我们知道它何时因停止序列而停止,并且通过解析,我们就可以轻松地看到它为我们的子程序创建的请求。
接下来,一旦确定并解析了请求(我用了子字符串操作和正则表达式的组合),用户就可以执行所需的任何子程序,在我们的用例中,我们将查询传递给我们的语义代码库搜索引擎。一旦该子程序完成,我们就需要将结果传递到上下文窗口中,这是内存插入——让聊天机器人相信它得出了这些结果,因为一旦用户提出下一个请求,它就没有办法回溯并执行了。插入结果非常简单,只需将结果附加到当前的上下文字符串即可,但是需要注意的是,在添加结果前,用户必须在子程序中加入结束标签:
</|search_query|>\\\\\\\\\\\\\\\\n[your results here]
这很重要,首先停止序列意味着结束标签永远不会被自然地写入上下文窗口,其次这些模型是根据它们所看到的东西执行操作的,如果用户不在这里加入它,下一次有人想要在对话中执行此操作时,它可能不会响应其中包含的停止序列,因为它看到在这种情况下停止序列被省略了。一旦插入结果,用户就可以结束该回合,插入 <|im_end|> 消息,或者让机器人继续输出结果,就好像它自己想出了结果一样——这取决于用户的实际使用情况。
此时,很明显,有很多字符串操作需要执行,我将在本文的实现技巧部分深入讨论这个问题,但我要说,值得一试的是以下两点:
a)获得你打算拥有的所有可能标签类型的枚举;
b)获得某种方法,以便在向用户呈现字符串之前,将所有这些标签从字符串中剥离出来。
最后,我们谈谈识别用户意图的问题。我们发现,最困难的问题之一是确定用户实际想要什么,并基于此确定需要采取的子程序/操作,用户请求中的许多东西是隐含的,并且可能作为其他步骤的依赖项。我在解决这个问题方面取得了成功(并不是说这是解决该问题的唯一方法),是通过让聊天机器人在编写任何面向用户的文本之前再添加一些必须写的标签,如下所示:
这是我在 ChatGPT 世界中找到的最接近 Let’s think step-by-step 等效功能的方法。它强制模型表达其推理并预先考虑其可能性答案,然后再提供实际答案。关于这一点,我给出的最后一个提示:为了确保模型始终产生这些前置消息标签,我始终在助手完成中以<|user_intonation>开始,并让它从那里继续执行,这样它就不会忽略或选择省略该步骤。
上下文窗口管理
这是一个棘手的问题,特别是 ChatGPT API 的 token 限制只有 4000 个,实际上根本不长,用户还必须自己管理上下文,如果用户的请求超过 4000 个 token ,则像任何其他 OpenAI 请求一样,用户将收到一个 400 错误回复。有传言称,将来会有一个 8000 token 版本,但还没有关于那个版本何时推出的消息。在此之前,我们必须想出一些方法来管理上下文窗口,以尽量确保用户的核心逻辑得以保留。如果用户有一个能够执行子程序的聊天机器人,那么这将是特别困难的,因为至少在我们的用例中,这些程序的结果可能非常冗长,并且占用了上下文窗口的很大一部分。有几件事情需要记住:每个请求中必须始终包含系统 Prompt,同时,仅仅删除上下文窗口中最早的消息并不一定总是最佳解决方案。这可能是整个过程中最大的「因人而异」因素,因为其实施将严重依赖于聊天机器人的使用方式。如果机器人是在用户不太想重新开始的情况下运行的,那么修剪早期的信息应该没问题,但并非总是如此。
实施技巧
我要重申,有一个强大的功能来剥离用户不应该从聊天信息中看到的内容是非常重要的。我们可以用标签将内容包装起来,以确保这些内容不会在最终的聊天信息中呈现,例如,如果用户的查询涉及搜索以使其实现,但本身并不是一个搜索查询,则不应在返回的信息中显示搜索结果。
(如果你希望阅读英文原文,请戳下方原文链接🔗)
📢 插播一则消息:真格基金的 AI 英雄帖仍在招募中,欢迎联系我们📮:[email protected] 或扫描下方二维码报名,更多信息请戳👉《来自真格基金的 AI 英雄帖》
更多被投新闻
格灵深瞳 | 曦智科技 | 来也科技 | 星亢原
东方空间 | 循环智能 | 诗云科技 | 赛舵智能
推荐阅读
继续阅读
阅读原文