一文说清大模型RAG应用中的两种高级检索模式:你还只知道向量检索吗?

如果你知道RAG(检索增强生成),那么一定知道这里面的R代表Retrieval即检索,也一定知道这个环节对于最终的生成质量有多重要,而基于向量(Vector)的语义检索是最为熟知的一种基础检索模式。但在实际应用中,如果只是简单的使用一次向量检索,往往很难满足多样化的需求。所以在模块化RAG的时代,很多新的范式与算法都对检索这个环节进行了大量的创新与优化,本文将深入探讨两种在RAG应用中可灵活应用的高级检索模式:

  • 融合检索(Fusion Retrieval)

  • 递归检索(Recursive Retrieval)

融合检索

融合检索(Fusion Retrieval)是一种结合多维度检索的方法。简单的说,就是通过多个不同的检索方法进行查询检索,并对召回的结果通过排序算法(比如RRF)重新排序融合后用于后续生成。融合检索可以组合多路不同维度的检索结果,以弥补单个向量索引在检索精确性上的不足。

图片

在融合检索中如何实现多路不同的检索?在实际应用中可以根据自身需要选择不同的形式,比如不同角度的重写问题、不同的检索算法、或者不同的索引类型等。

【基于问题重写与扩展】

借助查询重写(或查询转换器)对输入问题进行扩展,生成多个不同表达形式或不同角度细化的输入问题,并对每个问题分别进行检索,然后对召回的知识块chunks通过重排(reranker)模块进行重新排序,并取最终的top_K用于后续生成环节。

图片

通过Rewriter模块进行问题扩展

【基于多种类型的索引】

尽管基于高纬向量的索引在很多场景下的基于自然语言的语义检索可以工作的不错,但其也并非全能:比如你可能想借助知识图谱索引来更精确的获取实体之间的复杂关系;借助树状的摘要索引来更好的回答概要性的问题等。因此,你可以同时通过多路不同类型的索引(比如向量索引与关键词索引)来对输入问题进行检索,并对召回的知识块进行重排序处理后获取top_K。

(也可以是同种索引,但是采用不同的检索与评分算法)

图片基于向量索引与关键词索引对问题进行多路检索

【基于复合方案的多路检索】

这是对上面两种方法的组合。即同时借助于问题扩展与索引扩展来实现更多路的知识召回,再通过重新排序获得top_K。这种组合方法由于召回了更多的chunks,有利于获取更相关的知识,但同时也增加了系统性能的消耗与模型使用的成本,在实际使用时需要根据测试结果进行取舍。

图片

基于问题扩展+索引扩展的融合检索

【融合检索的关键技术】

实现融合检索并不复杂,不管在LangChain还是LlamaIndex框架中,都有转换器(rewriter)、检索器(retriever)、排序器(reranker)的概念与组件,可以自行组合来实现融合检索。

  • **查询扩展:**借助LLM自行实现;或者借助已有组件,比如LlamaIndex中的QueryTransform组件
  • **Reranker:**自行实现RRF算法函数,或者借助Cohere Reranker这样的专业排序模型实现
  • **检索融合:**扩展已有的Retriever组件,实现自定义融合检索器;有的框架有现成的融合检索器,比如LlamaIndex的QueryFusionRetriever。这是一个典型的融合检索器扩展:
......
class FusionRetriever(BaseRetriever):

#基于多个检索器构造融合检索器
#参数:检索器列表与top_k
    def __init__(
        self,
        retrievers: List[BaseRetriever],
        similarity_top_k: int = 3,
    ) -> None:
        self._retrievers = retrievers
        self._similarity_top_k = similarity_top_k
        super().__init__()

#实现检索方法
    def _retrieve(self, query_bundle: QueryBundle) -> List[NodeWithScore]:
        
       #查询重写,自行实现rewrite_query
        querys = rewrite_query(query_bundle.query_str,num=3)
        
        #调用辅助方法得到全部检索结果,自行实现run_queries
        results_dict = asyncio.run(run_queries(querys, self._retrievers))

        #RRF重新排序,自行实现rerank_results
        final_results = rerank_results(results_dict,similarity_top_k=self._similarity_top_k)

        return final_results

递归检索

相对于易于理解的融合检索,另外一种较复杂的检索方法是递归检索。想象一下,如果你想在一大堆书中找到你所关注的一段文字,最快的方法不是简单粗暴的逐本翻阅,你可能会这么做:

  • 做一些基本过滤,比如出版社(如果事先知道)

  • 查看书籍的简介,定位需要查看的少量几本书

  • 在最后的几本书中,借助目录与翻阅,找到所关注的文字内容

这本质上就是一种递归检索:**在不同层次上构建chunks节点与检索器(比如摘要层与内容层、主要内容层与嵌入内容层),并建立层次之间的链接关系,使得能够在每次检索时自动的实现向下递归探索,直至达到结束条件。**我们画一张图简单表示其原理:

图片

从图里可以看出,递归检索的技术基础是能够从一级索引检索出来的chunks链接到二级的chunks或者其他具备检索功能的装置(比如检索器、Chain、RAG引擎或者Agent)等。实现这种链接的方式通常是在一级的chunk对象中(比如LangChain中的Document对象或者LlamaIndex中的Node对象)保存二级对象的引用。

从一级的chunks链接到的二级对象引用可以是下面几种:

  • 一个可以直接读取内容的二级chunk块

  • 一个可以检索出多个二级chunk块的检索器

  • 一个可以查询后输出答案的RAG引擎

  • 一个可以规划与完成问答的Agent智能体

下面对这几种不同的链接形态及其应用场景、递归流程做简单阐述。

【从chunk链接到chunk】

这种递归检索本质是上一个chunk关联查找的过程:通过检索出来的一级chunk找到对应的二级chunk返回即可。在RAG上这么做的主要意义通常是来自于一个老生常谈的问题,即chunk的语义精确性与丰富性往往是矛盾的,所以有时候需要针对这两种需求做分离设计。

常见的应用场景有:

  • 在一个小的chunk中保存对应的大的chunk引用(父子块)

    用于在更精准的语义搜索基础上,提供更丰富的上下文

  • 在一个摘要chunk中保存对应的详细内容chunk的引用

    用于增强回答偏概要性或笼统性的提问方式的能力

  • 在一个假设性问题的chunk中保存对应的内容chunk的引用

    通过假设性问题来兼容更多的提问形式,提高召回精确性

下图表示了这种方式下的检索流程(注意并非所有的一级chunk都一定要有二级chunk的链接),蓝色部分表示实际检索过程:

图片

**【从chunk链接到检索器】

**

这种情况下通过检索出来的一级chunk找到对应的二级检索器(注意不是chunk),并递归调用这个检索器再次检索,并将二次检索出来的chunks用于后续生成。

这种方式的一个典型的场景是在多文档问答的场景中,通过生成摘要文档来实现分层过滤,以帮助降低单一层次检索下的精度不足、知识干扰等问题:

  • 在摘要与原始文档层面分别做嵌入与索引,并链接

  • 在摘要级别做检索,获得相关的摘要知识块

  • 根据摘要块的关联信息,可以关联到原始文档块的检索器

  • 递归检索原始文档的知识块,找到top-k用于生成

下图表示了这种方式下的检索流程:

图片

【从chunk链接到RAG引擎】

这种方式与上一种的区别在于:一级的chunk链接到的对象不再是输出检索结果的检索器(retriever),而是一个RAG引擎,其输出的答案将作为后续生成的上下文。所以,上一种方式场景中的检索器也可以改用RAG引擎,能达到类似的效果。

除此之外,**还有一种典型的场景是用于对文档中嵌入的复杂内容进行深度查询。**比如我们现在有这样的一个网页:

图片

在这个HTML页面(也可以是其他的PDF文档)中,除了正常的文字内容以外,还有一个或者多个嵌入的结构化表格,很多时候我们需要对这种嵌入式的表格做更复杂的自然语言提问(比如需要借助sql或者pandas)。那么就可以借助递归检索:

  • 对解析出来的表格元素创建独立RAG引擎,这可以基于强大的Pandas组件甚至SQL数据库,以满足对这个嵌入表格的复杂查询

  • 对结构化的表格生成一个摘要内容chunk,并链接到表格的独立RAG引擎

  • 这些表格的摘要chunk与文档的其他chunk一起创建向量索引,并构建RAG引擎提供对外查询

下图表示了这种方式下的检索流程:

图片

【从chunk链接到Agent】

最后一种方式是从chunk节点链接到Agent:在检索出基础chunk后,根据其中保存的Agent引用继续探索,调用Agent获取答案作为后续生成的上下文。这种模式本质上与”chunk+RAG引擎“类似,区别就是后端Agent与RAG引擎的区别:由于Agent具备更强大的查询与工具能力,因此可以在后端提供更丰富的二次输出能力。

仍然以多文档的RAG应用为例,我们做如下的设计以支持更复杂的问答场景:

  • 对每个文档创建一个Agent,每个Agent有两个可以使用的工具:一个是用来回答事实性问题的工具;一个是用来回答高层总结与分析类问题的工具

  • 对每个文档创建摘要文档与chunks,并将其链接到对应的后端Agent

  • 对摘要文档的chunks创建一级向量索引与RAG引擎,提供对外查询

【此场景也可以使用两级的RAG Agent实现,参考文章:手把手教你构建Agentic RAG:一种基于多文档RAG应用的AI Agent智能体

下图表示了这种方式下的检索流程:

图片

结束语

检索在复杂RAG应用中的重要性不言而喻,基于单一的向量语义检索很难满足实际企业生产环境下的复杂应用需求,以原型去应对生产的需求会导致举步维艰。所以,了解与学习不同的检索策略、算法、场景与范式对于提高RAG应用的“生产就绪”能力必不可少。

本文对RAG应用中较为复杂也最强大的融合检索与递归检索进行了详细剖析,而实际上在目前的主流开发框架中(LangChain或LlamaIndex)都有着更丰富的检索索引、算法与组件的支持,包括但不限于:

  • 基于知识图谱索引的检索
  • 基于关键词表的检索
  • 基于向量的BM25检索算法
  • 带语义路由的多路检索器
  • 自动元数据过滤的检索
  • 多chunk size下的自动合并检索
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值