五種RAG分塊策略詳解 + LlamaIndex代碼演示
先前文章中提到,不斷優(yōu)化原始文檔解析和分塊策略是控制變量法下,是提高最后檢索效果天花板的務(wù)實做法,前面已經(jīng)介紹了 MinerU vs DeepDoc 在文檔解析方面的效果對比。
MinerU vs DeepDoc:集成方案+圖片顯示優(yōu)化
關(guān)于文檔解析部分簡單的結(jié)論是,MinerU 無疑是值得關(guān)注和嘗試的一個文檔解析框架,但具體效果還要結(jié)合特定項目文檔做仔細橫評。
我目前在常規(guī)項目中,主要是對照使用 DeepDoc 和 MinerU 兩個方法。后續(xù)計劃花時間再針對 PaddleOCR、Mistra OCR 等工具做進一步的測評,感興趣的可以蹲一下。
這篇試圖說清楚:
業(yè)界常用的五種 RAG 分塊策略核心思想、LlamaIndex 代碼演示以及 RAGFlow/Dify 等框架實踐思路。
本篇中 RAG 分塊相關(guān)圖示均來自https://blog.dailydoseofds.com/p/5-chunking-strategies-for-rag 下述相關(guān)圖片出處不再做單獨說明
以下,enjoy:
1、RAG 與分塊的重要性
在正式開始前,老規(guī)矩溫故知新,先來復習下傳統(tǒng) RAG 出現(xiàn)的背景、典型 RAG 流程以及分塊的重要意義所在。
1.1典型 RAG 流程
為了讓大模型能夠回答私有知識的問題,拋開高成本的 LLM 微調(diào)方法外,我們可以選擇在提問時,直接傳入私有知識相關(guān)的參考信息,這種方法更加簡便且高效。
然而,這種方法的缺點很明顯。畢竟提示詞長度有限,當私有數(shù)據(jù)量過大時,傳入所有背景信息可能導致提示詞過長,從而影響模型的處理效率或達到長度上限。
而 RAG 巧妙地將 LLM 的生成能力與外部知識庫的信息檢索能力結(jié)合起來:
接收問題 (Query): 用戶向系統(tǒng)提出查詢。
信息檢索 (Retrieval): 系統(tǒng)在向量數(shù)據(jù)庫或搜索引擎中查找與問題相關(guān)的文檔片段。
上下文增強 (Augmentation): 將檢索到的信息片段整合進發(fā)送給 LLM 的提示 (Prompt) 中。
答案生成 (Generation): LLM 基于原始問題和增強的上下文生成最終回答。
在這個流程中,要實現(xiàn)高質(zhì)量的檢索,需要對原始知識文檔進行有效的預處理,這也就引出了 RAG 流程中一個至關(guān)重要的準備工作——文檔分塊 (Chunking)。
1.2為啥分塊這么重要
分塊,簡單來說就是將原始文檔按照某種策略分割成更小的、語義相對完整、適合進行索引和檢索的文本單元(Chunks)的過程。 這一步通常在文檔被送入向量數(shù)據(jù)庫進行 Embedding(向量化)之前完成。分塊策略選擇和執(zhí)行質(zhì)量,是構(gòu)建一個 RAG 應用的準確性基礎(chǔ)。結(jié)合以下三種情形,會更好理解些:
分塊過大: 可能導致檢索到的單個塊包含過多無關(guān)信息(噪音),增加了 LLM 理解上下文的難度,降低了答案的精確性,甚至可能超出 LLM 的上下文窗口限制。
分塊過小或切分不當: 可能破壞原文的語義連貫性,導致一個完整的知識點被拆散到多個塊中。檢索時可能只召回了部分信息,使得 LLM 無法獲得完整的背景,難以生成全面、準確的答案。
未能適應文檔結(jié)構(gòu): 不同的文檔類型(如論文、手冊、報告、網(wǎng)頁)具有不同的結(jié)構(gòu)特點。死板的分塊方式可能無法有效利用標題、列表、表格等結(jié)構(gòu)信息,影響信息提取的完整性。
2、五種分塊策略詳解
2.1固定大小分塊
核心思想
這是最直觀、最簡單的分塊方式。按照預先設(shè)定的固定長度( 最大 token 數(shù))將文本進行切割。為了盡量減少信息損失,通常會在相鄰的塊之間保留一部分重疊內(nèi)容(Overlap)。
優(yōu)點
實現(xiàn)簡單,處理速度快。
塊大小統(tǒng)一,便于批量處理和管理,能精確控制輸入 LLM 的 token 數(shù)量。
缺點
容易在句子或語義完整的表達中間被“攔腰斬斷”,破壞文本的語義連貫性。
可能將關(guān)聯(lián)緊密的關(guān)鍵信息分散到不同的塊中,影響后續(xù)檢索和理解的完整性。
適用場景
處理結(jié)構(gòu)簡單、對語義連貫性要求不高的文本。
需要快速實現(xiàn)或作為基線對比策略時。
對塊大小有嚴格限制的應用。
2.2語義分塊
核心思想
嘗試根據(jù)文本的語義含義進行切分,將語義關(guān)聯(lián)緊密的句子或段落聚合在一起。通常做法是先將文本分成基礎(chǔ)單元(如句子),然后計算相鄰單元的語義相似度(例如通過嵌入向量的余弦相似度),如果相似度高于某個閾值,則合并這些單元,直到相似度顯著下降時才創(chuàng)建一個新的塊。
優(yōu)點
能更好地保持文本的語義連貫性和上下文的完整性。
生成的塊通常包含更集中的信息,有助于提升檢索精度。
為 LLM 提供更高質(zhì)量的上下文,有助于生成更連貫、相關(guān)的回答。
缺點
實現(xiàn)相對復雜,依賴于嵌入模型的質(zhì)量和相似度閾值的設(shè)定。
閾值可能需要根據(jù)不同的文檔類型進行調(diào)整和優(yōu)化。
計算成本通常高于固定大小分塊。
適用場景
對上下文理解和語義連貫性要求較高的場景,如問答系統(tǒng)、聊天機器人、文檔摘要等。
處理敘事性或論述性較強的長文本。
2.3遞歸分塊
核心思想
采用“分而治之”的策略。首先嘗試使用一組優(yōu)先級較高的、較大的文本分隔符(如段落、章節(jié)標記)來分割文本。然后,檢查分割出的每個塊的大小。如果某個塊仍然超過預設(shè)的大小限制,就換用下一組優(yōu)先級更低、更細粒度的分隔符(如句子結(jié)束符、換行符)對其進行再次分割,此過程遞歸進行,直到所有塊都滿足大小要求。
優(yōu)點
在保持一定語義結(jié)構(gòu)的同時,能靈活地控制塊的大小。
比固定大小分塊更能尊重原文的自然結(jié)構(gòu)。
適應性較好,是一種常用的通用分塊策略。
缺點
實現(xiàn)比固定大小分塊更復雜一些。
需要預先定義好一組有效的分隔符及其優(yōu)先級順序。
注:兩個段落(紫色)被識別為初始塊,接著第一個段落再被拆成更小的塊。這種方式既保留了語義完整性,又能靈活控制分塊大小。
適用場景
適用于大多數(shù)類型的文檔,特別是那些具有一定層次結(jié)構(gòu)(如章節(jié)、段落、列表)但又不完全規(guī)整的文檔。
是許多 RAG 框架(如 LangChain)中推薦的默認策略之一。
2.4基于文檔結(jié)構(gòu)的分塊
核心思想
直接利用文檔本身固有的、明確的結(jié)構(gòu)元素(如標題層級、章節(jié)、列表項、表格、代碼塊、Markdown 標記等)來定義塊的邊界。目標是使每個塊盡可能對應文檔中的一個邏輯組成部分。
優(yōu)點
能最大程度地保留文檔的原始邏輯結(jié)構(gòu)和上下文信息。
塊的劃分方式自然,符合人類的閱讀和理解習慣。
缺點
強依賴于文檔本身具有清晰、一致的結(jié)構(gòu),這在現(xiàn)實世界的文檔中并非總是得到滿足。
生成的塊大小可能差異巨大,某些塊可能非常長,超出 LLM 的處理限制。
通常需要結(jié)合遞歸分塊等方法來處理過大的塊。
適用場景
特別適合處理結(jié)構(gòu)化或半結(jié)構(gòu)化特征明顯的文檔,如技術(shù)手冊、法律合同、API 文檔、教程、帶有章節(jié)的書籍、格式良好的 Markdown 文件等。
注:某些結(jié)構(gòu)下的分塊長度差異較大,可能會超出模型支持的 token 限制。可以考慮結(jié)合遞歸切分法來處理。
2.5基于 LLM 的分塊
核心思想
不再依賴固定的規(guī)則或啟發(fā)式方法,而是利用大型語言模型 (LLM) 自身的理解能力來判斷文本的最佳分割點。可以通過設(shè)計合適的提示 (Prompt),讓 LLM 將文本分割成語義上內(nèi)聚且與其他部分相對獨立的塊。
優(yōu)點
理論上具有最高的潛力,能實現(xiàn)最符合語義邏輯的分割效果,因為 LLM 能更深入地理解文本內(nèi)容、上下文和細微差別。
缺點
計算成本最高昂,處理速度最慢,因為涉及到多次調(diào)用 LLM。
需要精心設(shè)計有效的 Prompt 來指導 LLM 完成分塊任務(wù)。
同樣受到 LLM 本身上下文窗口大小的限制。
適用場景場景:
對分塊質(zhì)量有極致要求,并且能夠承擔高昂計算成本和較慢處理速度的場景。
用于處理語義關(guān)系特別復雜、傳統(tǒng)方法難以處理的文本。
也可能作為更復雜策略(如生成摘要樹、構(gòu)建知識圖譜)的一部分。
3、LlamaIndex 分塊策略演示
為了更好的理解五種不同的分塊策略原理和實現(xiàn)細節(jié),下面以 LlamaIndex 為例,展示五種策略對應的示例 python 代碼。
在具體介紹前,先補充說明兩個問題:LlamaIndex 是什么?以及為什么不選擇 RAGFlow/Dify 等框架來進行分塊策略的演示?
3.1LlamaIndex 掃盲介紹
LlamaIndex 也是一個非常流行的開源數(shù)據(jù)框架,與 Ragflow 不同,LlamaIndex 更像是一個靈活的工具箱或庫,提供了豐富、模塊化的組件來處理 RAG 流程中的各個階段,特別是數(shù)據(jù)加載、轉(zhuǎn)換(包括分塊/節(jié)點解析)、索引和查詢。
主要組件特點
數(shù)據(jù)連接器 (Data Connectors): 支持從各種來源(文件、API、數(shù)據(jù)庫等)加載數(shù)據(jù)。
數(shù)據(jù)索引 (Data Indexes): 提供多種索引結(jié)構(gòu)(如向量存儲索引、列表索引、關(guān)鍵詞表索引、樹狀索引、知識圖譜索引等)來組織數(shù)據(jù),以適應不同的查詢需求。
節(jié)點解析/文本分割 (Node Parsing / Text Splitting): 這是 LlamaIndex 處理分塊的核心部分。它提供了多種可配置的文本分割器 (Text Splitters),讓開發(fā)者可以精細地控制文檔如何被分割成“節(jié)點 (Nodes)”(LlamaIndex 中對“塊/Chunk”的稱呼)。
檢索器 (Retrievers): 基于索引,提供不同的方式來檢索與查詢相關(guān)的節(jié)點。
查詢引擎 (Query Engines): 結(jié)合檢索器和 LLM,構(gòu)建端到端的查詢和響應能力。
代理 (Agents): 構(gòu)建更復雜的、可以自主規(guī)劃和使用工具的 LLM 應用。
模塊化和可擴展性: 開發(fā)者可以方便地組合、替換或自定義各個組件。
演示底層機制的優(yōu)勢
LlamaIndex 的設(shè)計哲學就是提供明確的、可編程的接口。對于分塊(節(jié)點解析),我們可以直接在代碼中:
導入特定的 TextSplitter 類: LlamaIndex 提供了與多種分塊策略對應的類。
實例化分割器并配置參數(shù): 顯式地設(shè)置塊大小、重疊大小、分隔符、模型(用于語義分割)、結(jié)構(gòu)解析規(guī)則等。
調(diào)用分割器的 split_text 或類似方法: 將原始文本輸入,直接獲得分割后的節(jié)點列表。
下文會通過具體的 Python 代碼清晰和直接地展示五種分塊策略的實現(xiàn)。
3.2高集成度的局限
像 Ragflow、Dify 這樣封裝度較高的框架,提供的開箱即用的端到端解決方案,必然會通過 UI 或 API 參數(shù)將底層的實現(xiàn)細節(jié)抽象化。
這雖然方便用戶快速搭建應用,但對于希望理解“引擎蓋”下面發(fā)生了什么的用戶來說會不夠透明,難以窺見不同策略的具體代碼實現(xiàn)和細微差別。具體來說:
抽象層級的差異: Ragflow/Dify 等平臺為了易用性,會將底層的分塊邏輯封裝在更高級的選項(如 Ragflow 的 chunk_method 下拉菜單和相關(guān)配置)。用戶無法直接編寫或修改 LlamaIndex 那樣的底層分塊代碼。
平臺特定實現(xiàn): 即便 Ragflow 提供了一個名為 "Paper" 的分塊方法,其內(nèi)部的具體實現(xiàn)邏輯、使用的分隔符、遞歸策略等細節(jié),與 LlamaIndex 的 MarkdownNodeParser 組合有所不同。
配置而非編碼: 在這些平臺上,用戶更多的是通過圖形界面 (UI) 或平臺的 Python API 來配置分塊選項,而不是直接編寫分塊算法的代碼。
3.3LlamaIndex 五種分塊策略代碼參考
固定大小分塊:
可以使用SentenceSplitter(設(shè)置 chunk_size控制token數(shù),chunk_overlap 控制重疊) 或 TokenTextSplitter。代碼會清晰展示如何設(shè)置大小和重疊。
from llama_index.core.node_parser import SentenceSplitter
splitter = SentenceSplitter(chunk_size=128, chunk_overlap=20)
nodes = splitter.get_nodes_from_documents(documents) # 'documents' 是加載后的文檔對象
# 可以打印 nodes[0].get_content() 查看第一個塊的內(nèi)容
語義分塊:
LlamaIndex 提供了 SemanticSplitterNodeParser。需要配置一個嵌入模型,并設(shè)定相似度閾值 (breakpoint_percentile_threshold)。
from llama_index.core.node_parser import SemanticSplitterNodeParser
from llama_index.embeddings.openai import OpenAIEmbedding # 或其他嵌入模型
embed_model = OpenAIEmbedding()
splitter = SemanticSplitterNodeParser(
buffer_size=1, breakpoint_percentile_threshold=95, embed_model=embed_model
)
nodes = splitter.get_nodes_from_documents(documents)
遞歸分塊
SentenceSplitter 本身就具有一定遞歸特性,它會按順序嘗試使用不同的分隔符(默認從 \n\n 到 . 到 等)。可以自定義 paragraph_separator 等參數(shù)。更復雜的遞歸(如構(gòu)建摘要樹)可能涉及 HierarchicalNodeParser。
from llama_index.core.node_parser import SentenceSplitter
# 默認行為就是遞歸的,可以定制分隔符
splitter = SentenceSplitter(
separator=" ", # 可以簡化分隔符用于演示
chunk_size=256,
chunk_overlap=30,
paragraph_separator="\n\n\n", # 示例:自定義段落分隔符
)
nodes = splitter.get_nodes_from_documents(documents)
基于文檔結(jié)構(gòu)的分塊
LlamaIndex 有專門的 MarkdownNodeParser 或 JSONNodeParser。對于 PDF 中提取的 Markdown,MarkdownNodeParser 能很好地利用標題、列表等結(jié)構(gòu)。
from llama_index.core.node_parser import MarkdownNodeParser
parser = MarkdownNodeParser()
nodes = parser.get_nodes_from_documents(documents) # 假設(shè) documents 是加載的 Markdown 內(nèi)容
# 對于從 PDF 解析得到的 Markdown 尤其有效
基于 LLM 的分塊
雖然沒有一個現(xiàn)成的 LLMTextSplitter,但 LlamaIndex 的靈活性允許我們構(gòu)建自定義的 NodeParser,在其中調(diào)用 LLM 來決定分割點或生成摘要塊。例如結(jié)合 LLM 進行章節(jié)總結(jié)或主題劃分,或者利用 LLM 對元數(shù)據(jù)進行分析來指導分割。這部分后續(xù)我結(jié)合具體案例再做演示。
4、寫在最后
4.1如何在 Ragflow/Dify 中應用不同分塊策略
上述 LlamaIndex 的演示只是提供了評估的思路和方向,不能替代在目標平臺上的實際驗證。各位可以利用上述LlamaIndex 演示中的原理,去解讀 Ragflow/Dify等框架提供的分塊選項。
以RAGFlow為例,相關(guān)分塊選項和上述的五種分塊策略很難一一映射,結(jié)合官方的python api解讀參考如下:
固定大小分塊: Ragflow 的 "naive" 方法最接近這個概念,尤其是當配置了 chunk_token_num 時。它可能也結(jié)合了 delimiter 進行分割。
語義分塊: API 文檔中沒有直接命名為 "semantic" 的選項。然而,某些高級方法(像 "qa"、"knowledge_graph" 某種程度上)可能隱含了語義理解。但 API 沒有提供一個像 LangChain 那樣明確的 Semantic Splitter 選項。
遞歸分塊: 像 "book" 或 "paper" 這樣的方法內(nèi)部猜測是采用了遞歸或基于結(jié)構(gòu)的分塊邏輯,會先嘗試按大綱(章節(jié)、標題)分割,如果塊太大再按段落或句子遞歸分割。但API 文檔沒有明確說明其內(nèi)部遞歸邏輯。
基于文檔結(jié)構(gòu)的分塊: "paper", "book", "laws", "presentation", "table" 這些方法明顯是針對特定文檔結(jié)構(gòu)的,它們會優(yōu)先利用文檔的固有結(jié)構(gòu)(標題、章節(jié)、表格結(jié)構(gòu)、幻燈片等)來定義塊邊界。
基于 LLM 的分塊:API 中沒有通用的、直接讓 LLM 決定分塊邊界的選項。
這里需要特別說明的是,在RAGFlow中創(chuàng)建知識庫環(huán)節(jié),自動關(guān)鍵詞/問題提取不是LLM-based Chunking,而是分塊后的 LLM 增強。但RAPTOR(遞歸摘要樹,多粒度檢索) 策略涉及對初始塊進行聚類,并使用 LLM 對聚類進行摘要,生成新的、更高層次的“摘要塊”。 這個策略可以被認為是“基于 LLM 的分塊”的一種高級形式或應用。雖然它可能建立在初始分塊之上,但它確實利用 LLM 生成了新的、語義層級更高的塊(摘要),這些新塊是文檔內(nèi)容的一種 LLM 驅(qū)動的重新組織和分割。它不僅僅是分析現(xiàn)有塊,而是創(chuàng)造了新的塊邊界和內(nèi)容。
4.2成熟框架的分塊策略定制問題
如果你想實現(xiàn)的功能(例如一種非常特定的、Ragflow 沒有內(nèi)置或通過 API 參數(shù)暴露的分塊方法,或者你想徹底改變其檢索邏輯)超出了 Ragflow 提供的 API 和配置選項的范疇,理論上唯一的途徑就是修改 Ragflow 的源代碼。
但像 Ragflow 這樣的框架通常有復雜的內(nèi)部結(jié)構(gòu)和依賴關(guān)系,理解并安全地修改其核心代碼需要投入大量時間和精力。而且鑒于 RAGFlow 在常態(tài)化更新中,創(chuàng)建一個 Ragflow 的“定制分支”意味著官方的更新、補丁或新功能無法直接合并,需要自己手動同步或重新應用你的修改,維護成本很高。
類似Dify 支持編寫插件來添加自定義功能,而無需修改核心代碼。RAGFlow 等類似框架后續(xù)預期都會陸續(xù)支持更加靈活的擴展機制,建議短期內(nèi)不要在二開上耗費精力。
4.3使用 LlamaIndex 替代 Ragflow/Dify?
這是我近期被問到比較多的一個問題,我在實施或咨詢的一些項目中,部分企業(yè)選擇直接基于 LlamaIndex(或 LangChain 等類似庫)來構(gòu)建自己的企業(yè)級 RAG 應用,而不是使用封裝好的平臺。
除了可以更好的與現(xiàn)有技術(shù)棧集成外,企業(yè)可以完全掌控 RAG 流程的每一個環(huán)節(jié),選擇最適合需求的模型(嵌入、LLM、重排)、向量數(shù)據(jù)庫、索引策略、檢索邏輯等,并進行深度定制和優(yōu)化。
但問題也隨著而來,這需要開發(fā)或集成幾乎所有“外圍”組件,包括 UI、API、數(shù)據(jù)庫、部署運維在內(nèi)的所有周邊系統(tǒng)。 畢竟LlamaIndex 雖然提供了 RAG 的核心“引擎”和“管道”,但它本身不是一個可以直接部署給最終用戶的完整應用。
一般的建議是,針對企業(yè)級知識庫項目落地,如果需求與某個成熟框架的功能高度匹配,且對定制化要求不高,或者時間緊迫需要快速驗證,那么選擇 Ragflow/Dify 顯然更高效。但如果企業(yè)對 RAG 的性能、邏輯有深度定制的需求,希望完全掌控技術(shù)棧,擁有較強的內(nèi)部研發(fā)能力,或者需要將 RAG 深度嵌入現(xiàn)有復雜系統(tǒng),那么基于 LlamaIndex 自建通常是更長遠、更靈活的選擇,盡管前期投入更大。