摘要
1) 一句话总结 Anthropic 提出了一种名为“上下文检索”的方法,通过在文本块进行嵌入和 BM25 索引前添加专属的解释性上下文,并结合重排(Reranking)技术,将 RAG 系统的检索失败率最高降低了 67%。
2) 关键要点
- 小知识库替代方案:对于小于 20 万 Token(约 500 页)的知识库,建议直接将全文放入提示词;结合提示词缓存(Prompt Caching)功能,可将延迟降低 2 倍以上,成本降低高达 90%。
- 传统 RAG 的痛点:传统 RAG 在将文档分割为小文本块时会丢失关键的上下文信息(如代词指代的主体),导致检索失败。
- 上下文检索机制:利用 Claude 3 Haiku 为每个文本块生成 50-100 个 Token 的解释性上下文,并在进行向量嵌入和 BM25 索引之前,将该上下文前置添加到文本块中。
- 低成本实现:得益于提示词缓存功能,生成上下文化文本块的一次性预处理成本极低,约为每百万文档 Token 1.02 美元。
- 显著降低失败率:单独使用上下文嵌入可将前 20 个文本块的检索失败率降低 35%(5.7% → 3.7%);结合上下文 BM25 后,失败率降低 49%(5.7% → 2.9%)。
- 重排(Reranking)的叠加收益:在检索后引入重排模型(如 Cohere)对前 150 个文本块进行打分并筛选出前 20 个,可将整体检索失败率降低 67%(5.7% → 1.9%)。
- 最佳实践配置:实验表明,Voyage 和 Gemini 的嵌入模型效果最好;向生成模型传递前 20 个文本块的效果优于 5 个或 10 个;且各项技术(上下文嵌入 + 上下文 BM25 + 重排)的优化效果是可以叠加的。
3) 风险与局限性
- 文本块划分影响:文本块的大小、边界划分以及重叠部分的选择会直接影响最终的检索性能。
- 模型注意力分散:虽然在提示词中添加更多文本块会增加包含相关信息的几率,但过多的信息可能会分散模型的注意力(存在极限)。
- 重排带来的延迟与成本:重排在运行时增加了一个额外的步骤,不可避免地会增加少量的延迟和成本,需要在重排数量与性能之间寻找平衡点。
正文
为了让 AI 模型在特定上下文中发挥作用,它通常需要访问背景知识。例如,客户支持聊天机器人需要了解其所服务的特定业务的知识,而法律分析机器人需要了解大量过往案例。
开发者通常使用检索增强生成(RAG)来增强 AI 模型的知识。RAG 是一种从知识库中检索相关信息并将其附加到用户提示词中的方法,能显著提升模型的回复质量。问题在于,传统的 RAG 解决方案在对信息进行编码时会丢失上下文,这通常导致系统无法从知识库中检索到相关信息。
在本文中,我们概述了一种能够大幅改进 RAG 中检索步骤的方法。该方法被称为“上下文检索”(Contextual Retrieval),它使用了两项子技术:上下文嵌入(Contextual Embeddings)和上下文 BM25(Contextual BM25)。这种方法可以将检索失败率降低 49%,当与重排(Reranking)结合使用时,可降低 67%。这些代表了检索准确率的显著提升,直接转化为下游任务中更好的性能。
您可以通过我们的 Cookbook 轻松地使用 Claude 部署您自己的上下文检索解决方案。
关于直接使用更长提示词的说明
有时候最简单的解决方案就是最好的。如果您的知识库小于 200,000 个 Token(约 500 页材料),您只需将整个知识库包含在提供给模型的提示词中即可,无需使用 RAG 或类似方法。
几周前,我们发布了 Claude 的提示词缓存(prompt caching)功能,这使得这种方法变得更快、更具成本效益。开发者现在可以在 API 调用之间缓存常用的提示词,将延迟降低 2 倍以上,并将成本降低高达 90%(您可以通过阅读我们的提示词缓存 Cookbook 来了解其工作原理)。
然而,随着您的知识库不断增长,您将需要一个更具扩展性的解决方案。这就是上下文检索发挥作用的地方。
RAG 入门:扩展至更大的知识库
对于超出上下文窗口大小的更大知识库,RAG 是典型的解决方案。RAG 通过以下步骤对知识库进行预处理:
-
将知识库(文档“语料库”)分解为较小的文本块(chunks),通常不超过几百个 Token;
-
使用嵌入模型将这些文本块转换为编码了含义的向量嵌入;
-
将这些嵌入存储在允许按语义相似度进行搜索的向量数据库中。
在运行时,当用户向模型输入查询时,系统会使用向量数据库根据与查询的语义相似度来寻找最相关的文本块。然后,将最相关的文本块添加到发送给生成模型的提示词中。
虽然嵌入模型在捕获语义关系方面表现出色,但它们可能会遗漏关键的精确匹配。幸运的是,有一种较老的技术可以在这些情况下提供帮助。BM25(Best Matching 25)是一种排名函数,它使用词汇匹配来寻找精确的单词或短语匹配。它对于包含唯一标识符或技术术语的查询特别有效。
BM25 建立在 TF-IDF(词频-逆文档频率)概念的基础之上。TF-IDF 衡量一个词对集合中某篇文档的重要性。BM25 对此进行了改进,它考虑了文档长度,并对词频应用了饱和函数,这有助于防止常见词主导搜索结果。
以下是 BM25 如何在语义嵌入失败的情况下取得成功:假设用户在技术支持数据库中查询“错误代码 TS-999”。嵌入模型可能会找到关于一般错误代码的内容,但可能会遗漏精确的“TS-999”匹配。BM25 则会寻找这个特定的文本字符串,以识别相关的文档。
RAG 解决方案可以通过结合嵌入和 BM25 技术,使用以下步骤更准确地检索最适用的文本块:
-
将知识库(文档“语料库”)分解为较小的文本块,通常不超过几百个 Token;
-
为这些文本块创建 TF-IDF 编码和语义嵌入;
-
使用 BM25 根据精确匹配找到排名靠前的文本块;
-
使用嵌入根据语义相似度找到排名靠前的文本块;
-
使用排名融合(rank fusion)技术合并并去重步骤 (3) 和 (4) 的结果;
-
将前 K 个(top-K)文本块添加到提示词中以生成回复。
通过同时利用 BM25 和嵌入模型,传统的 RAG 系统可以提供更全面、更准确的结果,在精确的术语匹配和更广泛的语义理解之间取得平衡。
这种方法使您能够以经济高效的方式扩展到庞大的知识库,远远超出单个提示词所能容纳的范围。但这些传统的 RAG 系统存在一个显著的局限性:它们通常会破坏上下文。
传统 RAG 中的上下文难题
在传统的 RAG 中,文档通常被分割成较小的文本块以实现高效检索。虽然这种方法适用于许多应用,但当单个文本块缺乏足够的上下文时,就会导致问题。
例如,假设您的知识库中嵌入了一组财务信息(比如美国 SEC 文件),并且您收到了以下问题:“ACME 公司 2023 年第二季度的营收增长是多少?”
一个相关的文本块可能包含这样的文本:“该公司营收比上一季度增长了 3%。”然而,这个文本块本身并没有说明它指的是哪家公司或相关的时间段,这使得检索正确信息或有效利用该信息变得困难。
引入上下文检索
上下文检索通过在嵌入(“上下文嵌入”)和创建 BM25 索引(“上下文 BM25”)之前,将特定于该文本块的解释性上下文前置添加到每个文本块中,从而解决了这个问题。
让我们回到 SEC 文件集合的例子。以下是文本块可能被转换的示例:
值得注意的是,过去也曾提出过其他利用上下文来改进检索的方法。其他提议包括:向文本块添加通用的文档摘要(我们进行了实验,发现收益非常有限)、假设性文档嵌入(hypothetical document embedding),以及基于摘要的索引(我们进行了评估,发现性能较低)。这些方法与本文提出的方法有所不同。
实现上下文检索
当然,手动注释知识库中成千上万甚至数百万个文本块的工作量太大了。为了实现上下文检索,我们求助于 Claude。我们编写了一段提示词,指示模型提供简明的、特定于文本块的上下文,利用整个文档的上下文来解释该文本块。我们使用了以下 Claude 3 Haiku 提示词来为每个文本块生成上下文:
生成的上下文文本(通常为 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 个是这些选项中性能最好的(比较请见附录),但值得在您的用例上进行实验。
-
始终运行评估:通过将上下文化的文本块传递给模型,并区分什么是上下文、什么是文本块本身,可能会改善回复的生成。
通过重排 Reranking 进一步提升性能
在最后一步中,我们可以将上下文检索与另一种技术结合使用,以获得更多的性能提升。在传统的 RAG 中,AI 系统搜索其知识库以找到潜在相关的文本块。对于大型知识库,这种初始检索通常会返回大量相关性和重要性各异的文本块——有时多达数百个。
重排是一种常用的过滤技术,用于确保只将最相关的文本块传递给模型。重排能提供更好的回复,并降低成本和延迟,因为模型处理的信息更少了。关键步骤如下:
-
执行初始检索以获取排名靠前的潜在相关文本块(我们使用了前 150 个);
-
将前 N 个文本块连同用户的查询一起传递给重排模型;
-
使用重排模型,根据每个文本块与提示词的相关性和重要性对其进行评分,然后选择前 K 个文本块(我们使用了前 20 个);
-
将前 K 个文本块作为上下文传递给模型,以生成最终结果。
性能提升
市场上有几种重排模型。我们使用 Cohere 重排器进行了测试。Voyage 也提供重排器,尽管我们没有时间对其进行测试。我们的实验表明,在各个领域中,添加重排步骤可以进一步优化检索。
具体而言,我们发现重排后的上下文嵌入和上下文 BM25 将前 20 个文本块的检索失败率降低了 67%(5.7% → 1.9%)。
使用重排时的一个重要考虑因素是其对延迟和成本的影响,尤其是在对大量文本块进行重排时。因为重排在运行时增加了一个额外的步骤,所以不可避免地会增加少量的延迟,尽管重排器是并行对所有文本块进行评分的。在重排更多文本块以获得更好性能与重排较少文本块以降低延迟和成本之间,存在着固有的权衡。我们建议在您的特定用例上尝试不同的设置,以找到合适的平衡点。
结论
我们进行了大量测试,在各种不同的数据集类型上,比较了上述所有技术的不同组合(嵌入模型、BM25 的使用、上下文检索的使用、重排器的使用以及检索到的前 K 个结果的总数)。以下是我们的发现总结:
-
嵌入 + BM25 的效果优于单独使用嵌入;
-
在我们测试的模型中,Voyage 和 Gemini 拥有最好的嵌入效果;
-
将前 20 个文本块传递给模型比仅传递前 10 个或前 5 个更有效;
-
为文本块添加上下文能大幅提高检索准确率;
-
使用重排优于不使用重排;
-
所有这些优势是可以叠加的:为了最大化性能提升,我们可以将上下文嵌入(来自 Voyage 或 Gemini)与上下文 BM25 结合,加上重排步骤,并将 20 个文本块添加到提示词中。
我们鼓励所有使用知识库的开发者使用我们的 Cookbook 来尝试这些方法,以解锁新的性能水平。
附录 I
以下是针对 Retrievals @ 20,在不同数据集、嵌入提供商、在嵌入之外使用 BM25、使用上下文检索以及使用重排的结果细分。
有关 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 对本篇博客文章的构思与打磨。