当前位置: 首页 > article >正文

dify实现原理分析-rag-检索(Retrieval)服务的实现

概述

本文对dify的检索服务的检索过程的实现逻辑进行了分析。通过本文可以对检索服务的检索过程有一个比较清晰的理解,若是要关注实现细节,可以阅读对应部分的代码。

检索的分类

dify实现了三种类型的检索

  • 语义搜索
  • 全文搜索
  • 混合搜索

检索的类型定义如下:

class RetrievalMethod(Enum):
    SEMANTIC_SEARCH = "semantic_search"
    FULL_TEXT_SEARCH = "full_text_search"
    HYBRID_SEARCH = "hybrid_search"

在进行检索的过程中,会根据检索类型来执行不同的流程。

检索服务的总流程

检索服务的实际执行过程在RetrievalService.retrieve(…)函数中实现。该函数的主要实现逻辑如下:

  1. 若检索方法为关键词检索(retrieval_method == "keyword_search"),启动关键词检索线程,来根据查询语句来查询相关文档。结果保存到all_documents中。
  2. 若检索服务支持语义检索(is_support_semantic_search),则根据参数query查询嵌入向量,返回top_k个最相似的documents。结果保存到all_documents中。
  3. 若检索服务支持全文检索(is_support_fulltext_search),则根据参数query在对应向量数据库中进行全文检索。注意,有些向量数据库不支持全文检索。结果保存到all_documents中。
  4. 若检索方法是混合检索retrieval_method == RetrievalMethod.HYBRID_SEARCH.value,则启动数据后处理相关流程。结果保存到all_documents中。

关键词检索的实现

关键词检索功能是在函数:RetrievalService.keyword_search()中实现,该函数在 Flask应用程序的上下文中进行关键词搜索操作。该函数的实现逻辑如下:

  1. 在给定的应用程序上下文中,从数据库中获取指定ID的数据集。
  2. 使用该数据集创建一个关键词检索对象(Keyword),并执行关键词搜索。检索的时候,会根据关键词的存储类来构建检索的类。默认是:JIEBA。然后再调用Keyword对象的search函数来查询对应关键词的文档。
  3. 将找到的文档添加到传入的 all_documents 列表中。
  4. 如果发生异常,将错误信息添加到 exceptions 列表中。
关键词搜索search函数的执行逻辑

该函数从关键词表中查询关键词列表,并去掉停用词,然后对关键词所在的文档id进行计数和排序。具体的实现逻辑如下:

  1. 获取保存数据集关键词的表名,若该数据表不存在,则创建一个。数据表名为:dataset_keyword_tables。
  2. 获取top_k参数值,默认为4;
  3. 在数据集关键词表中,根据查询语句获取关键词列表,对每个关键词对应的文本ID进行计数,然后根据计数值对文本片段(chunk)ID进行从大到小排序。
  4. 从DocumentSegment表中查出对应的segment的详细内容,然后把segment的内容保存到Document对象中,返回Document列表。

语义检索(semantic_search)的实现

若检索方法支持语义检索RetrievalMethod.is_support_semantic_search(retrieval_method),则会进行启动语义检索线程。该线程调用RetrievalService.embedding_search函数,并把结果保存到all_documents结果列表中。

让我们来看一下RetrievalService.embedding_search函数的实现逻辑:

  1. 查询数据集id对应的数据集;
  2. 在向量数据库种搜素与查询文档相似的嵌入向量,查询过程可以参考《dify实现原理分析-rag-文本的嵌入向量的计算和存储第2步》的“嵌入向量的查询”章节的内容。这里不再赘述。

全文检索(full_text_search)的实现

若检索方法支持全文检索RetrievalMethod.is_support_fulltext_search(retrieval_method),则会进行启动全文检索线程。该线程调用RetrievalService.full_text_index_search函数,并把结果保存到all_documents结果列表中。

  1. 查询对应数据集id的数据集,然后创建一个向量数据库对象。vector = Vector(dataset)
  2. 调用vector.search_by_full_text函数通过全文检索,找到最相似的文档。不同向量数据库实现的全文检索的方式不同。
  3. 全文检索的过程是通过bm25算法来进行搜索的。有些向量数据库不支持bm25算法,也就不支持全文检索方式。
  4. 这里通过用PGVecotor举例说明全文检索的具体实现:

​ 在PGVector中执行全文检索,其实就是执行了一条SQL语句,该语句如下:

                f"""SELECT meta, text, ts_rank(to_tsvector(coalesce(text, '')), plainto_tsquery(%s)) AS score
                FROM {self.table_name}
                WHERE to_tsvector(text) @@ plainto_tsquery(%s)
                ORDER BY score DESC
                LIMIT {top_k}""",
                # f"'{query}'" is required in order to account for whitespace in query
                (f"'{query}'", f"'{query}'"),
            )

​ 其中query是给出的查询内容。

混合检索(hybrid_search)的实现

若retrieval_method的方法是混合检索,执行的过程如下:

        # 混合搜索
        if retrieval_method == RetrievalMethod.HYBRID_SEARCH.value:
            data_post_processor = DataPostProcessor(
                str(dataset.tenant_id), reranking_mode, reranking_model, weights, False
            )
            all_documents = data_post_processor.invoke(
                query=query, documents=all_documents, score_threshold=score_threshold, top_n=top_k
            )
DataPostProcessor的构建

DataPostProcessor构建时,就是获取RerankRunner对象和ReorderRunner对象。

这里的RerankRunner有2种:

  • 基于模型的重排模式:RerankModelRunner
  • 基于权重的重排模式:WeightRerankRunner
基于模型的重排模式(RerankModelRunner)的执行
  1. 对文档列表去重,针对dify的文档,记录doc_id;
  2. 调用rerank模型对文档进行重排,该方法返回一个包含重新排序后文档的信息(如文本和索引);
  3. 格式化重新排序后的文档:新建Document对象,并把重排序的内容保存到该对象中。包括:将重新排序后的分数添加到新文档的元数据中,把文档内容添加到
  4. 最后把重排序的结果保存到:rerank_documents结果列表中;

调用重排序模型对文档进行重排序的过程如下:

  1. 把需要重排序的文档和查询条件发送给重排序模型;
  2. 重排序模型会返回重排序完成后的结果列表;
  3. 对结果列表进行过滤,过滤掉所得分数不满足要求的文档(小于分数阈值的文档);
基于权重的重排模式(WeightRerankRunner)的执行
  1. 通过文档id对文档进行去重;
  2. 根据关键词匹配计算每个文档的分数;
  3. 针对每个文档:将基于关键词和向量的两个分数按照给定的权重综合,得到最终的评分。如果设置了 score_threshold 且文档的综合评分为负数,则跳过;
  4. 将所有符合要求的文档按分数从高到低排序;
  5. 根据 top_n 参数返回评分最高的前 N 个文档;如果不设置 top_n,则返回所有符合条件的文档。

总结

本文分析了dify的检索服务的实现逻辑。从检索服务的实现来看,总体的检索步骤大体分为:(1)从关键词数据库或向量数据库中获取满足查询条件的文档。(2)对查询到的多个文档进行rerank和去重,过滤,合并等操作。(3)然后对结果进行排序后得到最终结果。


http://www.kler.cn/a/526289.html

相关文章:

  • hive:基本数据类型,关于表和列语法
  • qt内部的特殊技巧【QT】
  • [论文总结] 深度学习在农业领域应用论文笔记14
  • 2501,20个窗口常用操作
  • 大一计算机的自学总结:异或运算
  • Qt Ribbon使用实例
  • 信号处理以及队列
  • 一文讲解Java中的异常处理机制
  • 变量和简单数据类型(字符串)
  • doris:导入时实现数据转换
  • Java 分布式与微服务架构:现代企业应用开发的新范式
  • JAVASE入门十二脚-IO流charArrayReader,bufferedReader,输入与输出,采集百度网页,分块操作
  • Golang 并发机制-1:Golang并发特性概述
  • 实战:如何快速让新网站被百度收录?
  • 11 Spark面试真题
  • Redis常用命令合集【一】
  • 春节旅游高峰,人力资源如何巧妙应对?‌
  • Python标准库 - os (3) 调度策略、系统信息
  • 数据结构--数组链表
  • 大模型时代下的具身智能
  • 实验五---控制系统的稳定性分析---自动控制原理实验课
  • LabVIEW温度修正部件测试系统
  • 图漾相机——C++语言属性设置
  • Java 知识速记:全面解析 final 关键字
  • Linux《基础指令》
  • 动态规划DP 最长上升子序列模型 登山(题目分析+C++完整代码)