更好的 RAG 2:单次检索已力不从心
这是关于改进检索增强生成系统系列文章的第二部分。第一部分涵盖了基础知识和系统设计中的一些注意事项,例如问题复杂性等关键问题,以及对新解决方案的需求。
在这一部分中,我们将介绍多轮检索的基础知识——它是什么,为什么需要它,以及如何实现它。如果您对 WalkingRAG 的工作原理感兴趣,本文将为您提供一个可行且可实现的构建类似系统的理解。
问题
如今,几乎所有的 RAG 系统都采用“单次检索”方式——对于一个给定的问题,它们会检索信息,进行修剪和修改,然后使用大型语言模型生成答案。乍一看这似乎没问题——直到你考虑到人类是如何回答问题的。
假设你的智力水平高于 GPT-4。你多久能通过一次检索就解决问题?在大多数情况下,你的第一次 Google 搜索加上你已有的任何残留信息,只会让你更接近于*找到*答案——你很少能仅凭第一次检索就获得全面、正确的响应所需的一切。我们需要一种方法,让核心智能——无论是你还是大型语言模型——能够请求更多信息,这些信息与已经进行的检索相关。
此外,期望单次 RAG 达到越来越高的基准,是将不可能的智能要求纯粹寄托在检索系统上。在大多数情况下,这是一个不幸的嵌入模型,它经过语义理解训练,或者甚至更简单。试图将单次检索推向人类水平,很容易导致越来越大的向量数据库、更长的上下文嵌入,以及复杂的嵌入转换,在那里你可能会花费大量的金钱和开发时间,却收效甚微。
对我们来说,WalkingRAG 的解决方案是找到一种方法,让大型语言模型——RAG 管道中最大、最智能的大脑——能够请求更多信息,并利用这些信息进行多轮检索。
为了实现有效的多轮检索,我们需要三样东西:
我们需要从检索到的源数据中提取部分信息,以便我们能够边学边做。
我们需要根据源数据和问题,找到新的查找位置。
我们需要从这些特定位置检索信息。
如果能成功地将这三者连接起来,我们就能实现多轮检索。
1. 事实提取
我们将使用 GPTs 和 Huggingface Assistants 来演示各个概念。提示语将提供,但通过交互式提示进行尝试会更好。
使用的提示故意简化以作说明之用——对于输出中可能存在的任何脆弱性,我提前道歉!
这是一个演示事实提取的 GPT。给它一些文档或文本,然后提出一个问题。COP24 报告是一个易于获取的公共资源,您可以将其用作大型文档进行测试。让我们加载文档并提出一个稍微复杂的问题。
如果你还记得第一部分,你就会知道幕后发生了什么:OpenAI 的 RAG 系统从我们上传的文档中检索一些相关的片段,这些片段根据与我们问题的嵌入相似度进行排序,然后将这些片段传递给 LLM 以用于生成答案。
然而,在这种情况下,我们要求大型语言模型从提供的片段中提取独立的事实,而不是直接给出答案,同时还要描述这些事实与最终答案的相关性。我们正在增加一个额外的步骤——几乎就像一个思维链——这将帮助我们开始列出所需的信息。可以想象成去图书馆,一边翻阅书籍,一边记下有用的信息。
我们内部将这些信息称为部分事实——从文档中提取的、至少与所提问题松散相关的信息。以下是我们使用的提示:
Make a markdown table of relevant facts, following this typespec for the columns:
"""
fact: string; // Provide information directly relevant to the question - either supplementary data, facts, or where the answer might be located, like pages and sections. Add definitions and other context from the page into the fact, so it's self-explanatory.
relevance: string; // How is this fact relevant to the answer?
"""
请注意,这里的输出是 Markdown 格式,以便于阅读——在 WalkingRAG 内部,我们将其提取为流式 JSON。这是流式传输的视频。
2. 寻找新线索
一旦我们掌握了部分事实,我们就想知道它们能告诉我们哪些新的地方可以寻找更多信息。
尝试使用这个 GPT,用同样的文档和问题。让我们问同样的问题,使用同样的文档。
它在这里做的是从检索到的块中提取参考文献。在大多数情况下,复杂的文档会直接告诉你到哪里去寻找更多信息——在脚注、参考文献中,或者通过命名主题。提取它们通常是微不足道的,而且没有太多额外成本——因为你已经在使用提取事实了。
我们所需要做的就是稍微扩展一下我们的类型规范
fact: string; // Provide information directly relevant to the question (or where to find more information in the text) - either supplementary data, facts, or where the answer might be located, like pages and sections. Add definitions and other context from the page into the fact, so it's self-explanatory.
relevance: string; // How is this fact relevant to the answer?
nextSource: string; // a page number, a section name, or other descriptors of where to look for more information.
expectedInfo: string; // What information do you expect to find there?
3. 连接线索
一旦我们有了这些参考资料——在我们的例子中,我们正在寻找“气候变化缓解策略章节”和“气候变化对健康的影响”——我们需要一种方法,能够根据这些描述符从文档中检索新的信息。
这就是嵌入非常有用的地方。我们可以嵌入描述符,并用它们来搜索文档中被前一轮检索遗漏的新信息块。
用 GPT 很难演示这一点,但尝试将这些描述符直接粘贴回对话并请求更多事实——很可能会发现更新、更相关的块和信息。
在 WalkingRAG 内部,我们嵌入了 `nextSource` 和 `expectedInfo` 描述符,效果非常好。我们试图弥合的语义距离要小得多——文档通常会以相同的方式引用相同的事物,我们可以过滤结果以确保从数据集中检索到更新的片段。
现在我们所要做的就是重复这个循环,这次是从嵌入的描述符而不是问题开始。我们保留提取的事实——随之而来的两个非常理想的特性是:增加的熵和中间输出。
熵
熵是一个用于描述句子中信息量的有用术语。用人类的语言来说,我们可以称之为上下文。每当你问“你是什么意思?”来回应一个问题时,你很可能是在寻求更多的上下文——或者说熵。
熵在基于语义搜索的系统中的作用不容小觑。人类向人工智能系统提出的问题中,通常包含了大量隐含的上下文,仅凭问题本身很难推断出来。人类喜欢简洁——尤其是在打字时。这对于其他人来说是没问题的,因为他们可以从与提问者共享的物理、职业和历史状态中推断出大量上下文。
对于自动化系统来说,你收到的问题中往往信息量少得可怜。例如,“大错特错在哪里?”这句话的嵌入方式是相同的,无论目标文档是《罗密欧与朱丽叶》还是Lyft 财报电话会议记录。
“行走”的过程有助于我们用数据集中的上下文熵来丰富原始问题,从而进一步引导最终答案。
中间结果
你会注意到,在我们的流程中,我们生成了大量的中间输出。通过一个循环,我们已经提取了事实、相关性、预期的下一个来源描述符和潜在的新信息。
这是一件很棒的事情——即使没有额外的处理,我们现在已经可以做很多事情了
我们可以向用户提供关于幕后情况的即时反馈。这使得用户更容易信任系统、验证输出,并在最终结果不符合预期时提供更多反馈。在一个更复杂的系统中,用户可以中断循环,修改输出或问题,并将控制权交还给系统,从而将系统引导到新的方向。
随着提出更多问题,中间结果会标记并分类文档——这给了我们一种长期记忆。我们已经提取了事实,这些事实是重新嵌入和搜索(而非直接搜索源文档)的良好信息源。我们还知道我们在文档中采取的因果路径——哪些是错误的开始,以及循环通常在哪里结束。
我们还在构建文档的知识图谱,并以查询时的成本效益方式进行。在 WalkingRAG 中,我们也在摄取时构建一个——这可以在以后讨论,作为权衡成本以获得更好准确性的一种方式。
结论
我们人类记录的信息——无论是 PDF、短信、Excel 表格还是书籍——都像我们自身一样,相互关联。孤立的任何事物意义都不大——而且通常第一次查看某物不足以获得全貌。
循环——或者叫 WalkingRAG,或者智能体,随你怎么称呼——是一种提升现代检索系统处理问题复杂性的方法。为了清晰起见,我省略了一些内容——我们如何在摄取时构建图谱,输入数据的转换(将在以后的文章中介绍),以及循环终止的实现方式。
在下一部分中,我们将探讨数据集中结构如何成为尚未开发的资源,以及如何利用它。