基于上下文的检索:增强 AI 模型知识检索能力
上下文检索:显著提高RAG性能的方法
对于AI模型在特定上下文中使用,通常需要访问背景知识。例如,客户支持聊天机器人需要了解特定业务的相关知识,而法律分析师机器人则需要了解大量过往案例。
开发者通常使用检索增强生成(RAG)来增强AI模型的知识。RAG是一种从知识库中检索相关信息并将其附加到用户提示中的方法,显著增强了模型的响应。问题在于传统的RAG解决方案在编码信息时会丢失上下文,这通常会导致系统无法从知识库中检索到相关信息。
在本文中,我们概述了一种显著提高RAG检索步骤性能的方法。该方法称为”上下文检索”,并使用两种子技术:上下文向量(Contextual Embeddings)和上下文BM25。这种方法可以将检索失败率降低49%,如果再结合重排序技术,甚至可以降低67%。这些表示显著的性能提升,直接提高了下游任务的性能。
你可以轻松地使用Claude部署自己的上下文检索解决方案,只需参考我们的食谱。
使用更长的提示的注意事项
有时候最简单的方法是最好的。如果你的知识库小于200,000个标记(大约500页的材料),你可以将整个知识库包含在提供给模型的提示中,而不需要RAG或类似的方法。
几周前,我们为Claude发布了提示缓存,使这种方法显著更快和更节省成本。开发者现在可以在API调用之间缓存经常使用的提示,将延迟减少超过2倍,并将成本降低多达90%(你可以通过阅读我们的提示缓存食谱来了解它是如何工作的)。
然而,随着你的知识库的增长,你需要一个更可扩展的解决方案。这就是上下文检索的用武之地。
RAG:扩展到更大的知识库
对于那些不适合上下文窗口的知识库,RAG是典型的解决方案。RAG通过以下步骤预处理知识库:
- 将知识库(文档的”语料库”)分解为较小的文本块,通常不超过几百个标记;
- 使用向量模型将这些块转换为向量向量,这些向量编码意义;
- 将这些向量存储在允许通过语义相似性搜索的向量数据库中。
在运行时,当用户向模型输入查询时,使用向量数据库根据语义相似性找到最相关的块。然后,将最相关的块添加到发送给生成模型的提示中。
虽然向量模型在捕捉语义关系方面表现出色,但它们可能会错过关键的精确匹配。幸运的是,有一种古老的技术可以在这方面提供帮助。BM25(最佳匹配25)是一种使用词汇匹配的排名函数,用于找到精确的单词或短语匹配。它特别适用于包含唯一标识符或技术术语的查询。
BM25 通过利用 TF-IDF(词频-逆文档频率)概念来工作。TF-IDF 衡量一个词在一个集合中的文档中的重要性。BM25 通过考虑文档长度并应用饱和函数来细化这一点,该函数有助于防止常见词在结果中占据主导地位。
BM25 可以在语义向量失败的地方成功:假设用户在一个技术支持数据库中查询”错误代码 TS-999”。一个向量模型可能会找到关于错误代码的一般内容,但可能会错过确切的”TS-999”匹配。BM25 寻找这个特定的文本字符串来识别相关的文档。
RAG 解决方案可以通过使用以下步骤结合向量和 BM25 技术来更准确地检索最相关的块:
- 将知识库(文档的”语料库”)分解为较小的文本块,通常不超过几百个标记;
- 为这些块创建 TF-IDF 编码和语义向量;
- 使用 BM25 根据确切匹配找到顶部块;
- 使用向量根据语义相似性找到顶部块;
- 使用 rank fusion 技术结合和去重 (3) 和 (4) 的结果;
- 将顶部块添加到提示中以生成响应。
通过结合 BM25 和向量模型,传统的 RAG 系统可以提供更全面和准确的结果,平衡精确的词匹配与更广泛的语义理解。
这种方法允许你有效地扩展到巨大的知识库,远远超过单个提示所能容纳的范围。但这些传统的 RAG 系统有一个显著的限制:它们经常破坏上下文。
传统RAG中的上下文困境
在传统的 RAG 中,文档通常被拆分为较小的块以进行高效检索。虽然这种方法对许多应用效果很好,但当单个块缺乏足够的上下文时,可能会导致问题。
例如,想象一下,你的知识库中包含了一个财务信息集合(例如,美国 SEC 文件),并且你收到了以下问题:“ACME 公司在 2023 年第二季度的收入增长是多少?”
一个相关的块可能包含以下文本:“公司收入比上一季度增长了 3%。” 然而,这个块本身没有指明它指的是哪家公司或相关时间范围,这使得很难检索到正确的信息或有效地使用信息。
引入上下文检索
上下文检索通过在每个块之前添加特定于块的解释性上下文,在向量(“上下文向量”)和创建 BM25 索引(“上下文 BM25”)之前解决了这个问题。
让我们回到我们的 SEC 文件集合示例。以下是一个块可能被转换的方式:
original_chunk = "公司收入比上一季度增长了 3%。"
contextualized_chunk = "该段落摘自ACME公司2023年第二季度的SEC文件;上一季度的收入为3.14亿美元。该公司的收入比上一季度增长了3%。"
值得注意的是,其他方法已经提出使用上下文来提高检索性能。其他建议包括:在块中添加通用文档摘要(我们尝试过并发现效果有限),假设文档向量,以及基于摘要的索引(我们评估过并发现性能较低)。这些方法与本文提出的方法不同。
实现上下文检索
当然,手动注释知识库中的数千甚至数百万个块将是一项极其繁重的工作。为了实现上下文检索,我们求助于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个标记,在向量之前和创建BM25索引之前附加到块。
以下是预处理流程的实际应用:
使用提示缓存减少上下文检索的成本
上下文检索在Claude中以低成本实现,得益于上述的特殊提示缓存功能。使用提示缓存,你不需要为每个块传递参考文档。你只需将文档加载到缓存中一次,然后引用之前缓存的内容。假设每个块有800个标记,8k标记的文档,50个标记的上下文指令,每个块有100个标记的上下文,生成上下文块的一次性成本为每百万文档标记1.02美元。
方法论
我们尝试了各种知识领域(代码库、小说、ArXiv论文、科学论文)、向量模型、检索策略和评估指标。我们在附录II中包含了一些我们用于每个领域的示例问题和答案。
下图显示了所有知识领域中所有向量配置的平均性能,使用最佳向量配置(Gemini Text 004)检索前20个块。我们使用1减去召回@20作为评估指标,该指标测量在20个块中未检索到的相关文档的百分比。你可以在附录中查看完整的结果——上下文化在所有评估的向量源组合中都提高了性能。
性能提升
我们的实验表明:
- 上下文向量将前20个块的检索失败率降低了35% (5.7% → 3.7%).
- 结合上下文向量和上下文BM25将前20个块的检索失败率降低了49% (5.7% → 2.9%).
实现考虑
在实现上下文检索时,有几点需要考虑:
- Chunk boundaries: 考虑如何将文档拆分为块。块大小、块边界和块重叠的选择会影响检索性能。
- Embedding model: 上下文检索在所有测试的向量模型中都提高了性能,但某些模型可能比其他模型表现更好。我们发现 Gemini 和 Voyage 向量特别有效。
- Custom contextualizer prompts: 虽然我们提供的通用提示效果很好,但你可以使用针对特定领域或用例的提示来实现更好的结果(例如,包括一个可能只在知识库中的其他文档中定义的关键术语词汇表)。
- Number of chunks: 将更多块添加到上下文窗口中会增加包含相关信息的机会。然而,更多的信息可能会分散模型的注意力,因此有一个限制。我们尝试了5、10和20个块,发现使用20个块是这些选项中性能最好的(见附录中的比较),但值得在特定用例上进行实验。
- Always run evals: 响应生成可以通过传递上下文块并区分上下文和块来改进。
进一步提高性能的重排序
在最后一步中,我们可以将上下文检索与另一种技术结合使用,以获得更多的性能提升。在传统的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 个块添加到提示中。
我们鼓励所有使用知识库的开发者使用我们的指南来尝试这些方法,以解锁新的性能水平。
附录 I
以下是各数据集、向量提供商、是否使用 BM25 补充向量、是否使用上下文检索以及是否使用重排序的前 20 个检索结果的细分。
有关前 10 个和前 5 个检索结果的细分以及每个数据集的示例问题和答案,请参见附录 II。
各数据集和向量提供商的 1 减去召回率 @ 20 的结果。