RAGFlow Agent 知识检索节点深度解析:从查询到重排序的完整流程
1. 总体架构概览
RAGFlow Agent 中的知识检索(Retrieval)节点是整个RAG系统的核心组件,负责从知识库中找到与用户查询最相关的文档片段。检索流程可以分为以下几个核心阶段:
用户查询 → 查询预处理 → 粗排阶段(ES混合检索) → 重排阶段(LLM重排序) → 结果输出
- 查询预处理:清理用户输入、收集知识库ID、绑定 Embedding 和 ReRank 模型等
- 粗排阶段(ES混合检索):结合文本检索(BM25)和向量检索(KNN)从ES中召回候选 Chunk
- 重排阶段(LLM重排序):使用 LLM ReRank 模型对候选 Chunk 进行重排序
- 结果输出:按输出格式整理数据,返回最终满足条件的 Chunk
2. 源码位置与调用链路详解
主要源码 对应于:
- 检索节点入口 (
agent/component/retrieval.py
):其中的类 Retrieval 对应了 RAGFlow 前端页面 Agent 中的 知识检索节点 - 核心检索逻辑 (
rag/nlp/search.py
):其中的类 Dealer 是类 Retrieval 的底层实现,实现了粗排、重排等逻辑。主要方法有:retrieval、search(粗排)、rerank_by_model(重排) - ES存储层 (
rag/utils/es_conn.py
):对应 ES 层的实现,封装了 ES 的操作方法 - ReRank模型 (
rag/llm/rerank_model.py
):对应了 LLM Rerank 模型的实现,封装了各种模型的调用方法
完整的调用链路 如下:
Retrieval._run() [agent/component/retrieval.py] // 前端 Agent 的知识检索节点,调用下层服务
└── Dealer.retrieval() [rag/nlp/search.py] // 检索核心逻辑:调用 粗排 + 重排├── Dealer.search() [rag/nlp/search.py] // 实现粗排逻辑│ └── ESConnection.search() [rag/utils/es_conn.py] // 调用 ES 执行 BM25 / KNN 查询│└── Dealer.rerank_by_model() [rag/nlp/search.py] // 实现基于 LLM 的重排逻辑└── QWenRerank.similarity() [rag/llm/rerank_model.py] // 调用 LLM 计算 Question/Chunk 语义相关性得分,此处以 QWen 为例
3. 粗排阶段(ES混合检索)
3.1 检索策略概述
RAGFlow采用混合检索策略,结合两种互补的检索方法:
- 文本检索:基于关键词匹配,擅长精确匹配和术语查找
- 向量检索:基于语义相似度,擅长理解查询意图和同义词匹配
3.2 ES 实际查询 DSL 语句
测试条件如下,以下数值都是通过 RAGFlow 前端界面设置与输入的:
- 用户问题:RAGFlow和FastGPT是什么?
- 相似度阈值:0.11
- 关键字权重:0.16
- Top-K:1024 (粗排结果 Chunk 限制数)
- Top-N:14 (重排结果 Chunk 限制数)
- Rerank模型:阿里的 get-rerank
通过在 ESConnection.search 函数调用 es.search(index=indexNames, body=q, …) 之前打印 q 变量,可以得到以下实际执行的 ES DSL 语句(DSL 是 Elasticsearch 的查询语言,类似于 MySQL 的 SQL 语句):
{"query": {"bool": {"must": [{"query_string": {"fields": ["title_tks^10","title_sm_tks^5","important_kwd^30","important_tks^20","question_tks^20","content_ltks^2","content_sm_ltks"],"type": "best_fields","query": "(ragflow^0.4312 ) (和^0.1377 ) (fastgpt^0.4312 ) \"ragflow 和\"^0.8623 \"和 fastgpt\"^0.8623","minimum_should_match": "0%","boost": 1}}],"filter": [{"terms": {"kb_id": ["cb7e95d8683311f080cb725e7685c9ee","03316fc06e7c11f0a10ec67c9f2d779f"]}},{"bool": {"must_not": [{"range": {"available_int": {"lt": 1}}}]}}],"boost": 0.050000000000000044}},"knn": {"field": "q_1024_vec","k": 1024,"num_candidates": 2048,"query_vector": [-0.07246076315641403,0.007768463809043169,...,-0.02680240198969841],"filter": {"bool": {"must": [{"query_string": {"fields": ["title_tks^10","title_sm_tks^5","important_kwd^30","important_tks^20","question_tks^20","content_ltks^2","content_sm_ltks"],"type": "best_fields","query": "(ragflow^0.4312 ) (和^0.1377 ) (fastgpt^0.4312 ) \"ragflow 和\"^0.8623 \"和 fastgpt\"^0.8623","minimum_should_match": "0%","boost": 1}}],"filter": [{"terms": {"kb_id": ["cb7e95d8683311f080cb725e7685c9ee","03316fc06e7c11f0a10ec67c9f2d779f"]}},{"bool": {"must_not": [{"range": {"available_int": {"lt": 1}}}]}}],"boost": 0.050000000000000044}},"similarity": 0.11},"from": 0,"size": 70
}
通过 DSL 语句可以看到,ES搜索结合了文本搜索与向量搜索,分别对应 “query” (文本搜索)与 “knn” (向量搜索)两部分:
- "query"文本搜索部分:通过 fields 参数指定了被搜索的字段、字段权重,然后使用最匹配的字段的分数作为文本搜索分数 (“type”: “best_fields”)。
- "knn"向量搜索部分:传入 User Question 向量 query_vector,指定需要的Chunk数 k 1024(对应RAGFlow前端中指定的 Top-K)、相似度阈值 similarity 0.11
ES会综合文本搜索、向量搜索两部分数得到最终粗排召回的 Chunk 。
在 "query"文本搜索的 fields 字段解读:
- "important_kwd^30"表示对 important_kwd 字段搜索,权重调整系数为 30,权重调整值越大表示该字段越重要。
- 从 fields 中几个字段的权重可以看出 ES 文本搜索时几个字段重要程度为 important_kwd/important_tks (Chunk 绑定的关键词) > question_tks (Chunk 绑定的问题) > title_tks/title_sm_tks (Chunk 对应的文档标题) > content_ltks/content_sm_ltks (Chunk 文本内容)
- 关于 Chunk 的这几个字段是如何生成的,可以参考我上一篇文章:《 Ragflow 文档处理深度解析:从解析到存储的完整流程 》
4. 重排阶段(基于 Rerank 模型)
4.1 重排序策略选择
RAGFlow 其实是支持两种重排序策略:
- 基于模型的重排序 (源码
Dealer.rerank_by_model
):基于前端界面中为知识检索节点设定的 Rerank 模型实现重排。 - 非模型的重排序 (源码
Dealer.rerank
):基于User Question/ES Chunk的文本特征、现有Embedding余弦相似度相似度、Chunk 排名等计算对Chunk进行重排序。
下文只介绍 基于模型的重排序。
4.2 LLM Rerank 重排模型概念介绍
Embedding 检索方法通过分别编码 Query 和 Chunk 得到向量,并用余弦相似度评估相关性。优点是可以提前计算Chunk的向量并存储,检索效率高、可大规模向量召回,适合在粗排阶段使用。但这种独立编码方式无法建模两者之间的语义交互。
而 Rerank 模型会将 Query 和 Chunk 作为一个成对的输入,同时送入模型进行处理。模型可以在编码过程中捕捉两者之间的深层语义关系和交互信息,更准确地评估相关性。这种方式虽然计算开销较大,但在排序质量上具有明显优势,常用于精排阶段。
调用阿里的 gte-rerank 重排模型的代码示例
import dashscope# 请替换为您自己的DashScope API密钥
API_KEY = "sk-d9a37e073d3e446ab359e445315d8394"# 查询问题
query = "如何学习Python编程语言"# 候选文档列表
documents = ["Java是一种面向对象的编程语言,广泛用于企业级应用开发。它具有跨平台性和强类型系统。","Python是一种简单易学的编程语言,语法清晰,适合初学者。它有丰富的第三方库,在数据科学、Web开发等领域应用广泛。","机器学习是人工智能的一个分支,通过算法让计算机从数据中学习模式。常用的框架包括TensorFlow和PyTorch。","Python学习建议:先掌握基础语法,然后通过实际项目练习。推荐从官方教程开始,多写代码多实践。","JavaScript是一种脚本语言,主要用于Web前端开发。现在也可以用Node.js进行后端开发。","数据结构和算法是编程的基础,包括数组、链表、树、图等。掌握这些对提高编程能力很重要。",
]# 调用重排序模型 gte-rerank API
response = dashscope.TextReRank.call(api_key=API_KEY,model="gte-rerank",query=query,documents=documents
)# 输出重排序结果
for rank, result in enumerate(response.output.results, 1):score = result.relevance_scoredoc_text = documents[result.index]print(f"文档排名: {rank}; 相关度: {score:.4f}; 文档内容: {doc_text}")
输出结果:
文档排名: 1; 相关度: 0.6545; 文档内容: Python学习建议:先掌握基础语法,然后通过实际项目练习。推荐从官方教程开始,多写代码多实践。
文档排名: 2; 相关度: 0.5206; 文档内容: Python是一种简单易学的编程语言,语法清晰,适合初学者。它有丰富的第三方库,在数据科学、Web开发等领域应用广泛。
文档排名: 3; 相关度: 0.2310; 文档内容: 数据结构和算法是编程的基础,包括数组、链表、树、图等。掌握这些对提高编程能力很重要。
文档排名: 4; 相关度: 0.0737; 文档内容: JavaScript是一种脚本语言,主要用于Web前端开发。现在也可以用Node.js进行后端开发。
文档排名: 5; 相关度: 0.0701; 文档内容: 机器学习是人工智能的一个分支,通过算法让计算机从数据中学习模式。常用的框架包括TensorFlow和PyTorch。
文档排名: 6; 相关度: 0.0527; 文档内容: Java是一种面向对象的编程语言,广泛用于企业级应用开发。它具有跨平台性和强类型系统。
4.3 RAGFlow LLM Rerank 重排序
RAGFlow 前端界面中如果在知识检索节点配置了 Rerank 模型,那么会调用 Dealer.rerank_by_model,执行逻辑大致如下:
- 计算 tksim:基于文本特征计算 Question/Chunk 的 tksim (Token Similarity,其实是知识检索节点前端配置页中指代的 关键词相似度)
- 计算 vtsim:基于 LLM Rerank 模型计算 Question/Chunk 的 vtsim (Vector Similarity,向量相似度)
- 计算最终 sim:使用 vtsim 与 tksim 加权计算最终的 Question/Chunk 的 sim (Similarity,最终相似度)。
源码如下:
# sres :包含 ES Chunk 的内容
# query :User Question;
# tkweight :前端配置的“关键字相似度权重”
# vtweight :是公式 “1 - tkweight” 计算的结果,表示 rerank 得分的权重
def rerank_by_model(self, rerank_mdl, sres, query, tkweight=0.3,vtweight=0.7, cfield="content_ltks",rank_feature: dict | None = None):# 1. 提取 Query 的关键词 _, keywords = self.qryr.question(query) # 2. 基于 Chunk 的 token 构建 ins_tw for i in sres.ids:if isinstance(sres.field[i].get("important_kwd", []), str):sres.field[i]["important_kwd"] = [sres.field[i]["important_kwd"]]ins_tw = []for i in sres.ids:content_ltks = sres.field[i][cfield].split() # Chunk 的内容tokentitle_tks = [t for t in sres.field[i].get("title_tks", "").split() if t] # Chunk 的文档标题 tokenimportant_kwd = sres.field[i].get("important_kwd", []) # Chunk 的关键词tks = content_ltks + title_tks + important_kwd ins_tw.append(tks) # 组合各种 Token 拼接为 Ins_tw# 3. 计算 Query Keywords 与 Chunk token 间的 关键词相似度 tksim (基于词汇匹配和TF-IDF权重)tksim = self.qryr.token_similarity(keywords, ins_tw)# 4. 计算 向量相似度 vtsim(使用 Rerank Model)vtsim, _ = rerank_mdl.similarity(query, [rmSpace(" ".join(tks)) for tks in ins_tw])# 5. 获取 知识库配置 页中设置的 “页面排名” (Page Rank)参数数值rank_fea = self._rank_feature_scores(rank_feature, sres)# 6. 融合多种相似度得到最终排序分数,并返回三个项:sim, tsim, vsim# 最终分数 sim = tkweight * (tksim + “页面排名”) + vtweight * vtsimreturn tkweight * (np.array(tksim)+rank_fea) + vtweight * vtsim, tksim, vtsim
这个 Dealer.rerank_by_model 函数返回 “sim, tsim, vsim” 三个返回值后,Dealer.retrieval 会基于 sim 变量得到每个 Chunk 的排名(rank)
# 部分源码如下
def retrieval(...):sim, tsim, vsim = self.rerank_by_model(...) # 得到粗排召回的 question/chunk 的精排相似度 sim idx = np.argsort(sim * -1)[(page - 1) * page_size:page * page_size] # 基于 sim 从大到小重排 chunk ranks = {"chunks": [], ...} # 基于排序结果 idx 将每个重排后的 chunk 添加到最终召回结果 ranks 中for i in idx:ranks["chunks"].append(d)