Anthropic 推出 Contextual Retrieval,将 RAG 检索失败率降低 49%
Introducing Contextual Retrieval
Anthropic 发布 Contextual Retrieval 方法,通过 Contextual Embeddings 和 Contextual BM25 两项子技术,将 top-20 分块检索失败率降低 49%,结合重排序后降低 67%。
Anthropic 公开了 Contextual Retrieval 的具体做法与失败率数据,读者可据此评估自家 RAG 检索环节的改进空间。
为了让 AI 模型在特定场景中发挥作用,它通常需要获取背景知识。例如,客户支持聊天机器人需要了解其被用于的具体业务的知识,而法律分析机器人则需要了解大量过往案例。
开发者通常使用检索增强生成(RAG)来增强 AI 模型的知识。RAG 是一种从知识库中检索相关信息并将其附加到用户提示中的方法,能显著增强模型的响应。问题在于,传统的 RAG 解决方案在编码信息时会移除上下文,这往往导致系统无法从知识库中检索到相关信息。
在本文中,我们概述了一种能大幅改进 RAG 检索步骤的方法。该方法称为“上下文检索”(Contextual Retrieval),使用两种子技术:上下文嵌入(Contextual Embeddings)和上下文 BM25(Contextual BM25)。该方法可以将检索失败的数量减少 49%,当与重排序结合使用时,可减少 67%。这些代表了检索准确性的显著提升,直接转化为下游任务的更好表现。
你可以使用 Claude 通过我们的 cookbook轻松部署你自己的上下文检索解决方案。
关于直接使用更长提示的说明
有时最简单的解决方案就是最好的。如果你的知识库小于 200,000 个 token(约 500 页材料),你可以直接将整个知识库包含在给模型的提示中,无需 RAG 或类似方法。
几周前,我们为 Claude 发布了提示缓存,这使得这种方法显著更快、更具成本效益。开发者现在可以在 API 调用之间缓存频繁使用的提示,将延迟降低 > 2 倍,成本降低高达 90%(你可以通过阅读我们的提示缓存 cookbook了解其工作原理)。
然而,随着你的知识库增长,你将需要一个更具可扩展性的解决方案。这正是上下文检索的用武之地。
RAG 入门:扩展到更大的知识库
对于无法容纳在上下文窗口中的较大知识库,RAG 是典型的解决方案。RAG 通过以下步骤对知识库进行预处理来工作:
- 将知识库(文档的“语料库”)分解为较小的文本块,通常不超过几百个 token;
- 使用嵌入模型将这些文本块转换为编码含义的向量嵌入;
- 将这些嵌入存储在一个向量数据库中,以便按语义相似性进行搜索。
在运行时,当用户向模型输入查询时,向量数据库用于根据与查询的语义相似性找到最相关的文本块。然后,最相关的文本块被添加到发送给生成模型的提示中。
虽然嵌入模型擅长捕捉语义关系,但它们可能会遗漏关键的精确匹配。幸运的是,有一种较老的技术可以在这些情况下提供帮助。BM25(Best Matching 25)是一种使用词汇匹配来查找精确单词或短语匹配的排序函数。它对于包含唯一标识符或技术术语的查询特别有效。
BM25 基于 TF-IDF(词频-逆文档频率)概念构建。TF-IDF 衡量一个词在文档集合中对某篇文档的重要程度。BM25 对此进行了改进,它考虑文档长度并对词频应用饱和函数,这有助于防止常见词主导结果。
以下是 BM25 在语义嵌入失败之处取得成功的方式:假设用户在技术支持数据库中查询“错误代码 TS-999”。嵌入模型可能会找到关于错误代码的一般内容,但可能会错过精确的“TS-999”匹配。BM25 会查找这个特定的文本字符串以识别相关文档。
RAG 解决方案可以通过以下步骤结合嵌入和 BM25 技术,更准确地检索最适用的文本块:
- 将知识库(文档的“语料库”)分解为更小的文本块,通常不超过几百个 token;
- 为这些文本块创建 TF-IDF 编码和语义嵌入;
- 使用 BM25 基于精确匹配找到排名靠前的文本块;
- 使用嵌入基于语义相似度找到排名靠前的文本块;
- 使用排名融合技术合并并去重 (3) 和 (4) 的结果;
- 将排名前 K 的文本块添加到提示中以生成响应。
通过同时利用 BM25 和嵌入模型,传统 RAG 系统可以提供更全面、更准确的结果,在精确词项匹配与更广泛的语义理解之间取得平衡。

这种方法使你能够经济高效地扩展到庞大的知识库,远远超出单个提示所能容纳的范围。但这些传统 RAG 系统有一个显著的局限:它们往往会破坏上下文。
传统 RAG 中的上下文难题
在传统 RAG 中,文档通常被拆分为更小的文本块以实现高效检索。虽然这种方法在许多应用中效果良好,但当单个文本块缺乏足够上下文时,可能会导致问题。
例如,假设你的知识库中嵌入了一组财务信息(比如美国 SEC 文件),并且你收到了以下问题:“ACME Corp 在 2023 年第二季度的收入增长是多少?”
一个相关的文本块可能包含这样的文本:“该公司的收入较上一季度增长了 3%。” 然而,这个文本块本身并未指明它指的是哪家公司或相关时间段,这使得检索正确信息或有效使用信息变得困难。
上下文检索通过在嵌入前为每个文本块前置特定于该文本块的解释性上下文(“上下文嵌入”)并创建 BM25 索引(“上下文 BM25”)来解决这个问题。
让我们回到 SEC 文件集合的示例。以下是一个文本块可能如何被转换的示例:
original_chunk = "The company's revenue grew by 3% over the previous quarter."
contextualized_chunk = "This chunk is from an SEC filing on ACME corp's performance in Q2 2023; the previous quarter's revenue was $314 million. The company's revenue grew by 3% over the previous quarter."值得注意的是,过去也曾提出过其他利用上下文改进检索的方法。其他提案包括:向文本块添加通用文档摘要(我们进行了实验,发现收益非常有限)、假设文档嵌入以及基于摘要的索引(我们进行了评估,发现性能较低)。这些方法与本博文中提出的方法不同。
实现上下文检索
当然,手动标注知识库中的数千甚至数百万个文本块工作量太大。为了实现上下文检索,我们求助于 Claude。我们编写了一个提示,指示模型提供简洁的、针对特定文本块的上下文,利用整个文档的上下文来解释该文本块。我们使用以下 Claude 3 Haiku 提示为每个文本块生成上下文:
<document>
{{WHOLE_DOCUMENT}}
</document>
Here is the chunk we want to situate within the whole document
<chunk>
{{CHUNK_CONTENT}}
</chunk>
Please give a short succinct context to situate this chunk within the overall document for the purposes of improving search retrieval of the chunk. Answer only with the succinct context and nothing else. 生成的上下文文本通常为 50-100 个 token,在嵌入文本块之前以及创建 BM25 索引之前,会将其前置到文本块中。
以下是预处理流程在实际中的样子:

如果你有兴趣使用上下文检索,可以从我们的 cookbook 开始。
使用提示缓存降低上下文检索的成本
借助我们上面提到的特殊提示缓存功能,上下文检索在 Claude 上可以以低成本独特实现。使用提示缓存,你无需为每个文本块传入参考文档。你只需将文档加载到缓存中一次,然后引用之前缓存的内容。假设文本块为 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%)。

实施注意事项
在实施上下文检索时,有几点需要考虑:
- 文本块边界:考虑如何将文档拆分为文本块。文本块大小、文本块边界和文本块重叠的选择会影响检索性能1。
- 嵌入模型:虽然上下文检索在我们测试的所有嵌入模型上都提高了性能,但有些模型可能比其他模型受益更多。我们发现 Gemini 和 Voyage 嵌入特别有效。
- 自定义上下文化提示:虽然我们提供的通用提示效果很好,但使用针对你特定领域或用例定制的提示可能会取得更好的结果(例如,包含可能仅在知识库中其他文档中定义的关键术语词汇表)。
- 块的数量:向上下文窗口中添加更多块会增加包含相关信息的机会。然而,更多信息可能会分散模型的注意力,因此存在一个限制。我们尝试了提供 5、10 和 20 个块,发现使用 20 个是这些选项中最有效的(比较见附录),但值得根据您的用例进行实验。
始终运行评估:通过传递上下文化的块并区分什么是上下文和什么是块,可以改进响应生成。
通过重排序进一步提升性能
在最后一步中,我们可以将上下文检索与另一种技术结合,以获得更多的性能改进。在传统的 RAG 中,AI 系统搜索其知识库以找到可能相关的信息块。对于大型知识库,这种初始检索通常会返回大量块——有时数百个——具有不同的相关性和重要性。
重排序是一种常用的过滤技术,用于确保只有最相关的块被传递给模型。重排序提供更好的响应并降低成本和延迟,因为模型处理的信息更少。关键步骤是:
- 执行初始检索以获取最可能相关的块(我们使用了前 150 个);
- 将前 N 个块以及用户的查询传递给重排序模型;
- 使用重排序模型,根据每个块与提示的相关性和重要性为其评分,然后选择前 K 个块(我们使用了前 20 个);
- 将前 K 个块作为上下文传递给模型以生成最终结果。

性能改进
市场上有几种重排序模型。我们使用 Cohere reranker 进行了测试。Voyage 也提供了一个重排序器,尽管我们没有时间测试它。我们的实验表明,在各种领域中,添加重排序步骤进一步优化了检索。
具体来说,我们发现重排序的上下文嵌入和上下文 BM25 将前 20 个块的检索失败率降低了 67%(5.7% → 1.9%)。

成本和延迟考虑
重排序的一个重要考虑因素是对延迟和成本的影响,尤其是在重排序大量块时。因为重排序在运行时添加了一个额外步骤,它不可避免地会增加少量延迟,即使重排序器并行地对所有块进行评分。在重排序更多块以获得更好性能与重排序更少块以降低延迟和成本之间存在固有的权衡。我们建议在您的特定用例上尝试不同的设置以找到正确的平衡。
结论
我们进行了大量测试,比较了上述所有技术的不同组合(嵌入模型、BM25 的使用、上下文检索的使用、重排序器的使用以及检索到的前 K 个结果的总数),涵盖了各种不同的数据集类型。以下是我们发现的总结:
- 嵌入+BM25 比单独的嵌入更好;
- Voyage 和 Gemini 在我们测试的嵌入中表现最佳;
- 将前 20 个块传递给模型比仅传递前 10 个或前 5 个更有效;
- 为块添加上下文大大提高了检索准确性;
- 重排序比不重排序更好;
- 所有这些优势可以叠加:为了最大化性能提升,我们可以将上下文嵌入(来自 Voyage 或 Gemini)与上下文 BM25 结合,再加上重排序步骤,并将 20 个块添加到提示中。
我们鼓励所有使用知识库的开发者使用我们的 cookbook 来试验这些方法,以解锁新的性能水平。
附录 I
以下是各数据集、嵌入提供商、在嵌入之外使用 BM25、使用上下文检索以及使用重排序在 Retrievals @ 20 上的结果细分。
有关 Retrievals @ 10 和 @ 5 的细分以及每个数据集的示例问题和答案,请参见附录 II。

致谢
研究和撰写:Daniel Ford。感谢 Orowa Sikder、Gautam Mittal 和 Kenneth Lien 提供的重要反馈,Samuel Flamini 实现 cookbook,Lauren Polansky 进行项目协调,以及 Alex Albert、Susan Payne、Stuart Ritchie 和 Brad Abrams 对这篇博客文章的完善。
来源:Anthropic News · anthropic.com