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)