上下文检索:RAG 的新水平

本文翻译自 Anthropic 官方博客:Contextual Retrieval in AI Systems

译者注:本文介绍了上下文检索技术,这是对传统 RAG 方法的重要改进,显著提高了检索准确率。

对于 AI 模型在特定上下文中发挥作用,它通常需要访问背景知识。例如,客户服务聊天机器人需要了解它们所服务的特定业务的知识,法律分析师机器人需要了解大量过去的案例。

开发者通常使用检索增强生成(RAG)来增强 AI 模型的知识。RAG 是一种从知识库中检索相关信息并将其附加到用户提示的方法,显著增强了模型的响应。问题是传统的 RAG 解决方案在编码信息时会删除上下文,这通常导致系统无法从知识库中检索相关信息。

在本文中,我们概述了一种显著改进 RAG 中检索步骤的方法。该方法称为"上下文检索(Contextual Retrieval)",使用两个子技术:上下文嵌入(Contextual Embeddings)和上下文 BM25。该方法可以将检索失败率降低 49%,当与重新排序结合使用时,可降低 67%。这些代表了检索准确性的显著改进,直接转化为下游任务更好的性能。

你可以使用我们的 cookbook 与 Claude 一起轻松部署你自己的上下文检索解决方案。

关于简单使用更长提示的说明

有时最简单的解决方案是最好的。如果你的知识库小于 200,000 token(大约 500 页的材料),你可以将整个知识库包含在提供给模型的提示中,无需 RAG 或类似方法。

几周前,我们为 Claude 发布了提示缓存,这使得这种方法明显更快和更具成本效益。开发者现在可以在 API 调用之间缓存频繁使用的提示,将延迟降低 >2 倍,成本降低高达 90%(你可以通过阅读我们的提示缓存 cookbook 了解它是如何工作的)。

然而,随着你的知识库增长,你需要一个更可扩展的解决方案。这就是上下文检索的用武之地。

RAG 入门:扩展到更大的知识库

对于不适合上下文窗口的更大知识库,RAG 是典型的解决方案。RAG 通过使用以下步骤预处理知识库来工作:

  1. 将知识库(文档的"语料库")分解为较小的文本块,通常不超过几百个 token;
  2. 使用嵌入模型将这些块转换为编码意义的向量嵌入;
  3. 将这些嵌入存储在允许通过语义相似性搜索的向量数据库中。

在运行时,当用户向模型输入查询时,向量数据库用于根据与查询的语义相似性找到最相关的块。然后,最相关的块被添加到发送给生成模型的提示中。

虽然嵌入模型擅长捕获语义关系,但它们可能会错过关键的精确匹配。幸运的是,有一种较旧的技术可以帮助在这些情况下。BM25(最佳匹配 25)是一种使用词法匹配来找到精确单词或短语匹配的排名函数。它对于包括唯一标识符或技术术语的查询特别有效。

BM25 通过建立在 TF-IDF(词频-逆文档频率)概念之上来工作。TF-IDF 测量单词对集合中的文档的重要性。BM25 通过考虑文档长度并对词频应用饱和函数来改进这一点,这有助于防止常见词主导结果。

以下是 BM25 可以在语义嵌入失败的地方成功的示例:假设用户在技术支持数据库中查询"错误代码 TS-999"。嵌入模型可能会找到关于错误代码的一般内容,但可能会错过精确的"TS-999"匹配。BM25 寻找此特定文本字符串以识别相关文档。

RAG 解决方案可以通过使用以下步骤结合嵌入和 BM25 技术来更准确地检索最适用的块:

  1. 将知识库(文档的"语料库")分解为较小的文本块,通常不超过几百个 token;
  2. 为这些块创建 TF-IDF 编码和语义嵌入;
  3. 使用 BM25 根据精确匹配找到顶级块;
  4. 使用嵌入根据语义相似性找到顶级块;
  5. 使用排名融合技术结合和去重来自(3)和(4)的结果;
  6. 将顶级 K 块添加到提示以生成响应。

通过利用 BM25 和嵌入模型,传统 RAG 系统可以提供更全面和准确的结果,平衡精确术语匹配与更广泛的语义理解。

使用嵌入和最佳匹配 25 (BM25) 检索信息的标准检索增强生成 (RAG) 系统。TF-IDF(词频-逆文档频率)测量单词重要性,形成 BM25 的基础。

这种方法使你可以经济高效地扩展到巨大的知识库,远远超出可以放入单个提示的范围。但这些传统 RAG 系统有一个显着的限制:它们通常会破坏上下文。

传统 RAG 中的上下文困境

在传统 RAG 中,文档通常被分割成较小的块以便有效检索。虽然这种方法对许多应用程序效果很好,但当单个块缺乏足够的上下文时可能会导致问题。

例如,假设你有一个嵌入在知识库中的财务信息集合(例如,美国 SEC 文件),你收到以下问题:"ACME Corp 2023 年第二季度收入增长是多少?"

一个相关的块可能包含文本:_"公司收入比上一季度增长 3%。"_然而,这个块本身并没有指定它指的是哪个公司或相关时间段,使得难以检索正确的信息或有效地使用信息。

介绍上下文检索

上下文检索通过在嵌入("上下文嵌入")和创建 BM25 索引("上下文 BM25")之前为每个块添加块特定的解释性上下文来解决这个问题。

让我们回到我们的 SEC 文件集合示例。以下是块可能如何转换的示例:

original_chunk = "公司收入比上一季度增长 3%。"

contextualized_chunk = "此块来自 ACME 公司 2023 年第二季度业绩的 SEC 文件;上一季度的收入为 3.14 亿美元。公司收入比上一季度增长 3%。"

值得注意的是,过去还提出了其他使用上下文来改进检索的方法。其他建议包括:向块添加通用文档摘要(我们实验并看到非常有限的收益)、假设文档嵌入和基于摘要的索引(我们评估并看到低性能)。这些方法与本文中提出的不同。

实现上下文检索

当然,手动注释知识库中的数千甚至数百万个块将是太多的工作。为了实现上下文检索,我们转向 Claude。我们编写了一个提示,指示模型提供简洁的、块特定的上下文,使用整个文档的上下文解释块。我们使用以下 Claude 3 Haiku 提示为每个块生成上下文:

<document>
{{WHOLE_DOCUMENT}}
</document>
这里是我们想要在整个文档中定位的块
<chunk>
{{CHUNK_CONTENT}}
</chunk>
请提供一个简短的简洁上下文,以便在整个文档中定位此块,以改进块的搜索检索。只回答简洁上下文,没有其他内容。

生成的上下文文本,通常 50-100 个 token,在嵌入之前和创建 BM25 索引之前被添加到块之前。

以下是预处理流程在实践中看起来像:

上下文检索是一种提高检索准确性的预处理技术。

如果你对使用上下文检索感兴趣,你可以从我们的 cookbook 开始。

使用提示缓存降低上下文检索的成本

上下文检索以低成本独特地成为可能,这要归功于我们上面提到的特殊提示缓存功能。使用提示缓存,你不需要为每个块传递参考文档。你只需将文档加载到缓存中一次,然后引用先前缓存的内容。假设 800 token 块、8k token 文档、50 token 上下文指令和每个块 100 token 上下文,生成上下文化块的一次性成本是每百万文档 token $1.02

方法论

我们跨各种知识领域(代码库、小说、ArXiv 论文、科学论文)、嵌入模型、检索策略和评估指标进行了实验。我们在附录 II 中包含了我们在每个领域使用的问题和答案的一些示例。

下面的图表显示了所有知识域在最佳嵌入配置(Gemini Text 004)和检索前 20 个块的平均性能。我们使用 1 减去 recall@20 作为我们的评估指标,它测量在前 20 个块中未检索到的相关文档的百分比。你可以在附录中看到完整结果 - 上下文化改善了我们要评估的每个嵌入-源组合的性能。

性能改进

我们的实验表明:

  • 上下文嵌入将前 20 个块检索失败率降低了 35%(5.7% → 3.7%)。
  • 结合上下文嵌入和上下文 BM25 将前 20 个块检索失败率降低了 49%(5.7% → 2.9%)。

结合上下文嵌入和上下文 BM25 将前 20 个块检索失败率降低 49%。

实现注意事项

在实现上下文检索时,需要记住一些注意事项:

  1. 块边界:考虑如何将文档分割成块。块大小、块边界和块重叠的选择可能会影响检索性能。
  2. 嵌入模型:虽然上下文检索改善了我们要测试的所有嵌入模型的性能,但某些模型可能比其他模型受益更多。我们发现 Gemini 和 Voyage 嵌入特别有效。
  3. 自定义上下文化提示:虽然我们提供的通用提示效果很好,但你可能通过针对你的特定领域或用例定制的提示实现更好的结果(例如,包括可能仅在知识库中的其他文档中定义的关键术语的词汇表)。
  4. 块数量:向上下文窗口添加更多块会增加你包括相关信息的几率。然而,更多的信息可能会分散模型的注意力,因此对此有限制。我们尝试提供 5、10 和 20 个块,发现使用 20 是这些选项中最高效的(参见附录进行比较),但值得在你的用例上进行实验。

始终运行评估:响应生成可能会通过向其传递上下文化的块并区分什么是上下文、什么是块来改进。

使用重新排序进一步提高性能

在最后一步,我们可以结合上下文检索与另一种技术,以获得更多性能改进。在传统 RAG 中,AI 系统搜索其知识库以找到潜在相关的信息块。对于大型知识库,这种初始检索通常会返回很多块——有时是数百个——具有不同的相关性和重要性。

重新排序是一种常用的过滤技术,用于确保只有最相关的块被传递给模型。重新排序提供更好的响应并降低成本和延迟,因为模型处理的信息更少。关键步骤是:

  1. 执行初始检索以获得顶级潜在相关块(我们使用了前 150 个);
  2. 将前 N 个块连同用户的查询一起通过重新排序模型;
  3. 使用重新排序模型,根据每个块与提示的相关性和重要性给它一个分数,然后选择前 K 个块(我们使用了前 20 个);
  4. 将前 K 个块作为上下文传递给模型以生成最终结果。

结合上下文检索和重新排序以最大化检索准确性。

性能改进

市场上有几种重新排序模型。我们使用 Cohere 重新排序器运行了测试。Voyage 也提供重新排序器,尽管我们没有时间测试它。我们的实验表明,跨各种域,添加重新排序步骤进一步优化了检索。

具体来说,我们发现重新排序的上下文嵌入和上下文 BM25 将前 20 个块检索失败率降低了 67%(5.7% → 1.9%)。

重新排序的上下文嵌入和上下文 BM25 将前 20 个块检索失败率降低 67%。

成本和延迟注意事项

重新排序的一个重要注意事项是对延迟和成本的影响,尤其是在重新排序大量块时。因为重新排序在运行时添加了一个额外步骤,所以它不可避免地会添加少量延迟,即使重新排序器并行地给所有块打分。为了获得更好的性能而重新排序更多块与为了降低延迟和成本而重新排序更少块之间存在固有的权衡。我们建议在你的特定用例上实验不同的设置以找到正确的平衡。

结论

我们运行了大量测试,比较了上述所有技术的不同组合(嵌入模型、使用 BM25、使用上下文检索、使用重新排序器以及检索的顶级 K 结果的总数),跨各种不同的数据集类型。以下是我们发现的总结:

  1. 嵌入 + BM25 比单独的嵌入更好;
  2. Voyage 和 Gemini 在我们要测试的嵌入中是最好的;
  3. 将前 20 个块传递给模型比仅前 10 个或前 5 个更有效;
  4. 向块添加上下文大大提高了检索准确性;
  5. 重新排序比不重新排序更好;
  6. 所有这些好处都是叠加的:为了最大化性能改进,我们可以结合上下文嵌入(来自 Voyage 或 Gemini)与上下文 BM25,加上重新排序步骤,并将 20 个块添加到提示。

我们鼓励所有使用知识库的开发者使用我们的 cookbook 来实验这些方法,以解锁新的性能水平。

致谢

研究和撰写由 Daniel Ford 完成。感谢 Orowa Sikder、Gautam Mittal 和 Kenneth Lien 的关键反馈,Samuel Flamini 实现了 cookbook,Lauren Polansky 协调项目,Alex Albert、Susan Payne、Stuart Ritchie 和 Brad Abrams 塑造了这篇博客文章。