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_score
doc_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 的内容token
title_tks = [t for t in sres.field[i].get("title_tks", "").split() if t] # Chunk 的文档标题 token
important_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 * vtsim
return 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)