我处理了 5 亿 GPT tokens 后:langchain、RAG 等都没什么用

作者 | ken kantzer

译者 | 平川

策划 | 褚杏娟

本文最初发布于 ken kantzer 的个人博客。

在过去的六个月里,我的创业公司 truss(gettruss.io)发布了多项倚重 llm 的功能,而我在 hacker news 上读到的关于 llm 的故事现在已经和我的实际情况脱节了,所以我想在处理过 5 亿多(我估计)tokens 之后,分享一些更“令人惊讶”的经验教训。

本文要点:

  • 我们正在使用 openai 模型,如果你想知道我对其他模型的看法,请阅读底部的问答部分。

  • 在我们的用例中,gpt-4 占 85%,gpt-3.5 占 15%。

  • 我们专门处理文本,因此不涉及 gpt-4-vision、sora、whisper 等。

  • 我们有一个 b2b 用例——重点是汇总 / 分析 - 提取,你的情况可能有所不同。

  • 5 亿 tokens 其实并不像想象的那多,也就大概 75 万页文本,要正确看待。

对于提示,少即是多

我们发现,不要在提示中给出确切的列表或指令——如果这些东西已经是常识的话,这样可以获得更好的结果。gpt 并不愚蠢,你提供的细节过多,反而会让它混乱。

这和编写代码不一样,代码必须明确。

下面是我们遇到的一个例子。

我们的一部分管道读取了一些文本块,并要求 gpt 根据它们与美国 50 个州或联邦政府的相关性进行归类。这不是什么很难的任务——或许用 string/regex 就可以搞定,但会有许多奇怪的边缘情况,花费的时间会更长。因此,我们首先做了(大致)这样的尝试:

here's a block of text. one field should be "locality_id", and it should be the id of one of the 50 states, or federal, using this list:[{"locality: "alabama", "locality_id": 1}, {"locality: "alaska", "locality_id": 2} ... ]

这样做有时候是可以的(我估计 98% 以上的情况都可以),但如果需要深入挖掘的话经常会失败。

经过研究,我们注意到字段 name 始终返回州的全名——即使我们没有明确要求它这样做。因此,我们改为对 name 做简单的字符串搜索来找出相应的州。从那以后,它就工作得很好了。

我认为,更好的方法应该是:

“you obviously know the 50 states, gpt, so just give me the full name of the state this pertains to, or federal if this pertains to the us government.”

(gpt,你显然知道 50 个州,文本和哪个州相关,你就告诉我这个州的全名,如果和美国政府相关,你就告诉我联邦政府。)

就是这么不可思议!你的提示模糊一点,gpt 概括的反而更好,反馈的质量反而更高——这是高阶委托 / 思维的典型标志。

(注 1:你可能会想 gpt 从根本上讲是一个随机模型,但它面对 m 开头的州失败次数最多。)

(注 2:当我们要求 gpt 从列表中选择一个 id 时,如果我们以格式化的 json 发送,每个州一行,那么它就不会那么困惑了。我认为,\n 是一个比逗号更强大的分隔符。)

你不需要 langchain,甚至不需要 openai 去年在 api 中发布的任何东西,只需聊天 api 就够了

langchain 是过早抽象的一个典型例子。

我们一开始以为必须得用它,因为网上是这么说的。而实际上,在 tokens 数量达到成百上千万、生产环境具备大概 3~4 个完全不同的 llm 特性之后,我们的 openai_service 文件中仍然只有一个 40 行的函数:

def extract_json(prompt, variable_length_input, number_retries)

我们唯一使用的 api 是 chat。我们总是提取 json。我们不需要 json mode、函数调用和助手(虽然我们都做了),我们甚至没有使用系统提示(或许我们应该)。当 gpt-4-turbo 发布的时候,我们只更新了代码库中的一个字符串。

这就是功能强大的通用模型的美妙之处——少即是多。

在这个函数的 40 行代码中,大部分代码都是用来处理普通的 500 错误或套接字关闭错误(尽管 openai api 变得越来越好,但考虑到它们的负载,出现这样的问题也并不奇怪)。

我们内置了一些自动截断代码,因为我们不必担心上下文长度限制。我们有自己的 tokens 长度估计器,如下所示:

if s.length > model_context_size * 3  # truncate it!end

在一些极端情况下,如句号或数字过多时,上述代码会不起作用。因此,我们还有下面这个特有的 try/catch 重试逻辑:

if response_error_code == "context_length_exceeded"   s.truncate(model_context_size * 3 / 1.3)

我们使用这种方法取得了不错的效果,而且也有足够的灵活性来满足我们的需求。

使用流式 api 改善延迟,向用户提供速度可变的输出,这实是 chatgpt 一个重大的用户体验创新

我们可能认为这就是一个噱头,但用户对于这个特性的反响很是积极。

gpt 特别不擅长零假设

“return an empty output if you don’t find anything(如果没有找到任何内容,则返回空)”——这可能是我们遇到的最容易导致 gpt 出错的提示语。

gpt 经常会产生幻觉,提供不那么真实的答案,而不是什么都不返回。但这样的问题会导致它缺乏信心,什么都不返回的次数会比正常情况下多。

我们大部分提示都是类似下面这样:

“here’s a block of text that’s making a statement about a company, i want you to output json that extracts these companies. if there’s nothing relevant, return a blank. here’s the text: [block of text]”

(这里有一段文字描述了一家公司,我们希望你提取这家公司并输出 json。如果未找到任何相关内容,则返回空。文本如下:[文本内容])

有一段时间,我们有一个 bug,就是 [文本块] 可以为空。gpt 会出现糟糕的幻觉。顺便说一下,gpt 喜欢幻想面包店,下面这些都很棒:

  • sunshine bakery

  • golden grain bakery

  • bliss bakery

我们的解决方案是修复这个 bug,如果没有文本就不发送提示。但难点在于,通过编程判断“它是空”比较困难,其实这时需要 gpt 参与进来了。

“上下文窗口”一词不是很妥当,只有输入窗口变大了,而输出窗口并没有

很少有人知道:gpt-4 允许的最大输入窗口为 128k,而输出窗口仍然只有 4k。显然,“上下文窗口”一词是有迷惑性的。但问题的糟糕之处在于,我们经常要求 gpt 返回一个 json 对象的列表。想象一下,一个 json 任务的数组列表,每个任务都有一个名称和标签。

gpt 实在没法返回 10 项以上。硬要让它返回 15 项的话,或许只有 15% 的情况下可以做到。

一开始,我们以为这是因为 4k 大小的上下文窗口限制,但我们发现,10 项的时候只有 700~800 个 tokens,gpt 就停下了。

现在,你当然可以把输出变成输入。你给它一个提示,要求它返回一个任务,然后把提示和任务一起提供给它,再要求下一个任务,以此类推。但现在,你在和 gpt 玩电话游戏,并且必须处理类似 langchain 这样的事情。

向量数据库和 rag/embeddings,对我们这些普通人来说几乎毫无用处

我累了,我真得累了。每次我想到一个杀手级的 rag / embeddings 用例时,我都会狼狈不堪。

我认为,数据库 /rag 事实上是为搜索而存在的,仅限于搜索,而且是像谷歌或必应那样的真正的搜索。下面是一些原因:

  1. 缺少相关性界限。这里有一些解决方案,比如你可以创建自己的相关性界限启发式,但那并不可靠。在我看来,这会扼杀 rag——总是会检索出不相关的结果,或者过于保守,错过重要的结果。

  2. 为什么要把向量存入一个专有数据库里而远离其他数据呢?除非你的规模达到了谷歌 / 必应的水平,否则是不值得丢失上下文的。

  3. 除非你做的是一个非常开放的搜索,比如整个互联网——用户通常不喜欢语义搜索,因为它会返回一些不相关的东西。对于大多数商业应用中的搜索,用户都是领域专家——他们不需要你去猜测他们的意思,他们会直接告诉你!

在我看来(没测试过),对于大多数的搜索场景,llm 更好的用法是使用正常的提示补全将用户的搜索转换为面搜索,甚至是更复杂的查询(甚至是 sql)。但这根本不是 rag。

幻觉基本不会出现

从根本上讲,我们的用例都是“这里有一个文本块,从中提取一些东西。”

一般来说,如果你让 gpt 给出一段文本中提到的公司名,它不会随机给出一个公司(除非文本中没提及任何公司——这是零假设问题!)。

如果你是一名工程师,那你肯定已经注意到了:gpt 并没有真正地生成幻觉代码,它不会创建变量,或者在重写你发送给它的代码块过程中随机引入错别字。

当你要求它给你一些东西时,它确实会产生存在标准库函数的幻觉,但我还是把那看作零假设。它不知道怎么说“我不知道”。

但如果你的用例完全是这样的:“这是全部的上下文信息,分析 / 总结 / 提取”,那么它会非常可靠。最近发布的很多产品都强调了这个严谨的用例。

因此总的来说,输入的数据好,gpt 就会给出好的响应。

小结:路在何方?

对于一些问题,我在下面直接做了回答。

q:我们会实现 agi 吗?

a:不。用这种转换器 + 互联网数据 +$xb 基础设施的方法是不行的。

q:gpt-4 真得有用吗?还是说一切都是营销?

a:它百分之百有用。现在仍然是互联网的早期阶段。

q:它会让所有人失业吗?

a:不。从根本上讲,它降低了人们进入 ml/ai 领域的门槛,而之前这是谷歌才有的能力。

q:你试过 claude、gemini 等模型吗?

a:实际上,我们并没有做任何严谨的 a/b 测试,但我在日常编码过程中测试过,感觉它们还差得比较远。主要体现在一些比较微妙的事情上,比如感知你的意图。

q:我怎么才能跟上 llms/ai 领域的最新发展动态?

a:不需要这么做。关于 the bitter lesson,我想过很多,模型性能的总体改进会远超小幅优化。如果真是这样,你所需要担心的就只有 gpt-5 何时问世,其他的都不重要。openai 在此期间发布的其他所有东西(不包括 sora 等,那是完全不同的东西)基本上都是干扰。

q:那么当 gpt-5 出现时,它会有多好?

a:和其他人一样,我一直在试图从 openai 那里寻找相关的蛛丝马迹。遗憾的是,我认为我们接下来只会看到渐进式的改进。我对“gpt-5 会改变一切”不抱多少希望。

这其中的根本原因是经济方面的。我之前以为,从 gpt-3 到 gpt-3.5 可能是模型通过训练获得超线性改进:训练难度提高 2 倍,而性能提升 2.2 倍。但显然,情况并非如此。我们看到的是对数关系。事实上,为实现增量改进,token 速度是呈指数级下降而单 token 成本是呈指数级增长的。

如果是这样的话,我们就处于某种帕累托最优曲线上,而 gpt-4 可能就是最优的:尽管与 gpt-3.5 相比,我愿意为 gpt-4 支付 20 倍的价格。但老实说,从 gpt-4 到 gpt-5,我不认为我会为每个 token,而不是为 gpt-4 所使用的任务集,支付 20 倍的价格。

gpt-5 可能会打破这一局面。或者,只是 iphone 5 与 iphone 4 的差别。我并不会为此感到失落!

声明:本文为 infoq 翻译,未经许可禁止转载。

今日荐文