跳过正文
Contextual Retrieval-Anthropic
  1. Blog/

Contextual Retrieval-Anthropic

·5527 字·12 分钟
目录

原文engineering/contextual-retrieval

TL;DR
#

Anthropic提出了一种RAG改进方法——Contextual Retrieval,通过使用Claude自动为文档块添加背景上下文,将检索失败率降低了49%(结合重排序可降低67%)。该方法成本极低(每百万token仅$1.02),能显著提升大规模知识库的检索准确性,适用于需要精确检索特定领域信息的AI应用。

一、核心问题:传统RAG的上下文丢失
#

传统RAG系统为了高效检索会将文档分割成小块(chunks),但这会导致关键上下文信息丢失

示例场景:在SEC财报文件中,一个文本块只写着"公司收入较上季度增长3%",但没有说明是哪家公司、哪个季度。当用户查询"ACME公司2023年Q2收入增长多少"时,系统很难准确检索到这个信息。

二、解决方案:Contextual Retrieval
#

Anthropic提出的方法是在嵌入之前,为每个文档块自动添加特定上下文信息。包含两个子技术:

1. Contextual Embeddings(上下文嵌入)
#

  • 在生成向量嵌入前,为每个块添加解释性上下文
  • 上下文通常50-100个token,说明该块的来源和背景

2. Contextual BM25(上下文BM25)
#

  • 同样在建立BM25索引前添加相同上下文
  • 结合精确关键词匹配和语义搜索的优势

改造示例

原始块:"公司收入较上季度增长3%"
改造后:"该块来自ACME公司2023年Q2的SEC财报文件;上季度收入为3.14亿美元。公司收入较上季度增长3%"

三、性能提升效果
#

实验基于多个知识领域(代码库、小说、ArXiv论文等)测试,使用1 - recall@20(前20个结果中未检索到相关文档的失败率)作为指标:

技术方案检索失败率改善幅度
传统RAG(嵌入+BM25)5.7%基准线
+ Contextual Embeddings3.7%↓35%
+ Contextual Embeddings & BM252.9%↓49%
+ 再增加Reranking重排序1.9%↓67%

结论:所有技术叠加使用效果最好,失败率从5.7%降至1.9%。

核心洞察:RAG的失败往往不在于检索算法,而在于检索对象本身缺乏足够上下文。通过低成本自动化添加上下文,可以显著提升AI系统回答特定领域问题的准确性。


原文译文
#

要让 AI 模型在特定语境下发挥作用,它往往需要获取背景知识。


要让 AI 模型在特定语境下发挥作用,它往往需要获取背景知识。例如,客户支持聊天机器人需要了解其服务对象的特定业务知识,而法律分析机器人需要了解大量的过往案例。

开发人员通常使用检索增强生成 (Retrieval-Augmented Generation, RAG) 来增强 AI 模型的知识。RAG 是一种从知识库中检索相关信息并将其附加到用户提示词 (prompt) 中的方法,它能显著提升模型的回答质量。问题在于,传统的 RAG 解决方案在对信息进行编码时会丢失上下文,这导致系统往往无法从知识库中检索到相关信息。

在这篇文章中,我们将概述一种能显著改进 RAG 检索步骤的方法。该方法被称为“上下文检索 (Contextual Retrieval)”,它使用了两种子技术:上下文嵌入 (Contextual Embeddings)上下文 BM25 (Contextual BM25)。该方法可以将检索失败的数量减少 49%,如果结合重排序 (reranking) 技术,则可减少 67%。这代表了检索准确性的显著提升,并直接转化为下游任务中更好的性能表现。

您可以使用 Claude 通过我们的 Cookbook 轻松部署您自己的上下文检索 (Contextual Retrieval) 解决方案。

关于仅使用更长提示词的说明
#

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

几周前,我们发布了针对 Claude 的 提示词缓存 (Prompt Caching),这使得该方法明显更快且更具成本效益。开发人员现在可以在 API 调用之间缓存常用的提示词,将延迟降低 2 倍以上,并将成本降低高达 90%(您可以通过阅读我们的 提示词缓存 Cookbook 了解其工作原理)。

然而,随着您的知识库不断增长,您将需要更具扩展性的解决方案。这正是上下文检索 (Contextual Retrieval) 发挥作用的地方。

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

对于无法放入上下文窗口 (context window) 的较大型知识库,RAG 是典型的解决方案。RAG 通过以下步骤对知识库进行预处理:

  1. 将知识库(文档“语料库”)分解为较小的文本块 (chunks),通常不超过几百个 Token;
  2. 使用嵌入模型 (embedding model) 将这些文本块转换为编码了含义的向量嵌入;
  3. 将这些嵌入存储在允许通过语义相似度 (semantic similarity) 进行搜索的向量数据库 (vector database) 中。

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

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

BM25 建立在 TF-IDF (词频-逆文档频率) 概念之上。TF-IDF 衡量一个词对集合中某个文档的重要性。BM25 对此进行了改进,它考虑了文档长度并对词频应用了饱和函数,这有助于防止常见词主导结果。

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

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

  1. 将知识库(文档“语料库”)分解为较小的文本块,通常不超过几百个 Token;
  2. 为这些文本块创建 TF-IDF 编码和语义嵌入;
  3. 使用 BM25 根据精确匹配查找排名靠前的文本块;
  4. 使用嵌入根据语义相似度查找排名靠前的文本块;
  5. 使用排名融合 (rank fusion) 技术组合并去重步骤 (3) 和 (4) 的结果;
  6. 将前 K 个 (top-K) 文本块添加到提示词中以生成回答。

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

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

这种方法允许您以经济高效的方式扩展到巨大的知识库,远超单个提示词所能容纳的范围。但这些传统的 RAG 系统有一个显著的局限性:它们往往会破坏上下文。

传统 RAG 中的上下文难题 (The context conundrum)
#

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

例如,假设您的知识库中嵌入了一组财务信息(比如美国证券交易委员会 (SEC) 的备案文件),而您收到了以下问题:“What was the revenue growth for ACME Corp in Q2 2023?"(ACME 公司 2023 年第二季度的收入增长是多少?)

一个相关的文本块可能包含以下文本:“The company’s revenue grew by 3% over the previous quarter."(公司收入较上一季度增长了 3%。)然而,这个文本块本身并没有说明它指的是哪家公司或相关的时间段,这使得检索正确信息或有效使用该信息变得困难。

介绍上下文检索 (Introducing Contextual Retrieval)
#

上下文检索通过在嵌入(“上下文嵌入 (Contextual Embeddings)”)和创建 BM25 索引(“上下文 BM25 (Contextual 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."

值得注意的是,过去也有人提出过其他利用上下文改进检索的方法。其他的提议包括:向文本块添加通用文档摘要(我们进行了实验,发现增益非常有限)、假设性文档嵌入 (hypothetical document embedding) 和基于摘要的索引(我们评估后发现性能较低)。这些方法与本文提出的方法不同。

实现上下文检索
#

当然,手动为知识库中成千上万甚至数百万个文本块添加注释的工作量太大了。为了实现上下文检索,我们将目光投向 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 开始。

使用提示词缓存 (Prompt Caching) 降低上下文检索的成本
#

由于我们上面提到的特殊提示词缓存功能,使用 Claude 进行上下文检索具有独特的低成本优势。有了提示词缓存,您不需要为每个文本块都传入参考文档。您只需将文档加载到缓存中一次,然后引用之前缓存的内容。假设文本块为 800 Token,文档为 8k Token,上下文指令为 50 Token,每个文本块的上下文为 100 Token,那么生成上下文嵌入块的一次性成本为每百万文档 Token 1.02 美元。

方法论 (Methodology)
#

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

下图展示了在使用表现最佳的嵌入配置 (Gemini Text 004) 并检索前 20 个文本块 (top-20-chunks) 时,所有知识领域的平均性能。我们使用 1 minus recall@20 作为评估指标,该指标衡量的是相关文档未能被检索到前 20 个文本块中的百分比。您可以在附录中看到完整结果——在我们评估的每一个嵌入源组合中,上下文处理都提高了性能。

性能提升
#

我们的实验表明:

  • 上下文嵌入 (Contextual Embeddings) 将前 20 个文本块的检索失败率降低了 35% (5.7% → 3.7%)。
  • 结合 上下文嵌入上下文 BM25 (Contextual BM25) 将前 20 个文本块的检索失败率降低了 49% (5.7% → 2.9%)。
结合上下文嵌入和上下文 BM25 将前 20 个文本块的检索失败率降低了 49%。
结合上下文嵌入和上下文 BM25 将前 20 个文本块的检索失败率降低了 49%。
实现注意事项
#

在实现上下文检索时,有几个注意事项需要牢记:

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

始终运行评估 (Always run evals):通过传递上下文化的文本块并区分什么是上下文、什么是文本块,可能会改善响应生成。

通过重排序 (Reranking) 进一步提升性能
#

在最后一步中,我们可以将上下文检索与另一种技术相结合,以进一步提升性能。在传统的 RAG 中,AI 系统搜索其知识库以找到潜在的相关信息块。对于大型知识库,这种初始检索通常会返回大量相关性和重要性各异的文本块——有时数以百计。

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

  1. 执行初始检索以获取排名靠前的潜在相关文本块(我们使用了前 150 个);
  2. 将前 N 个 (top-N) 文本块连同用户的查询一起传递给重排序模型;
  3. 使用重排序模型,根据文本块与提示词的相关性和重要性给每个文本块打分,然后选择前 K 个 (top-K) 文本块(我们使用了前 20 个);
  4. 将前 K 个文本块作为上下文传入模型以生成最终结果。
结合上下文检索和重排序以最大化检索准确性。
结合上下文检索和重排序以最大化检索准确性。

性能提升
#

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

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

重排序后的上下文嵌入和上下文 BM25 将前 20 个文本块的检索失败率降低了 67%。
重排序后的上下文嵌入和上下文 BM25 将前 20 个文本块的检索失败率降低了 67%。
成本和延迟考量
#

重排序的一个重要考量是其对延迟和成本的影响,尤其是在重排序大量文本块时。因为重排序在运行时增加了一个额外的步骤,它不可避免地会增加少量的延迟,即使重排序器是并行对所有文本块进行评分的。在重排序更多文本块以获得更好性能与重排序更少文本块以降低延迟和成本之间,存在着固有的权衡 (trade-off)。我们建议针对您的具体用例尝试不同的设置,以找到适当的平衡点。

结论
#

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

  1. 嵌入 + BM25 优于单独使用嵌入;
  2. Voyage 和 Gemini 在我们测试的模型中拥有最好的嵌入;
  3. 向模型传递前 20 个文本块比仅传递前 10 个或前 5 个更有效;
  4. 向文本块添加上下文极大地提高了检索准确性;
  5. 重排序比不重排序要好;
  6. 所有这些优势是可以叠加的 (All these benefits stack):为了最大化性能提升,我们可以结合上下文嵌入(来自 Voyage 或 Gemini)与上下文 BM25,加上一个重排序步骤,并将 20 个文本块添加到提示词中。

我们鼓励所有使用知识库的开发人员使用我们的 Cookbook 来试验这些方法,以解锁更高水平的性能。

附录 I
#

以下是各个数据集、嵌入提供商、除嵌入外是否使用 BM25、是否使用上下文检索以及是否使用重排序在 Retrievals @ 20 下的结果细分。

请参阅 附录 II 查看 Retrievals @ 10 和 @ 5 的细分,以及每个数据集的示例问题和答案。

各数据集和嵌入提供商的 1 minus recall @ 20 结果。
各数据集和嵌入提供商的 1 minus recall @ 20 结果。