五种RAG分块策略详解 + LlamaIndex代码演示

📅 发布时间:2026/7/4 5:09:20 👁️ 浏览次数:
五种RAG分块策略详解 + LlamaIndex代码演示
先前文章中提到不断优化原始文档解析和分块策略是控制变量法下是提高最后检索效果天花板的务实做法前面已经介绍了 MinerU vs DeepDoc 在文档解析方面的效果对比。参考文章《MinerU vs DeepDoc集成方案图片显示优化》关于文档解析部分简单的结论是MinerU 无疑是值得关注和尝试的一个文档解析框架但具体效果还要结合特定项目文档做仔细横评。我目前在常规项目中主要是对照使用 DeepDoc 和 MinerU 两个方法。后续计划花时间再针对 PaddleOCR、Mistra OCR 等工具做进一步的测评感兴趣的可以蹲一下。这篇试图说清楚业界常用的五种 RAG 分块策略核心思想、LlamaIndex 代码演示以及 RAGFlow/Dify 等框架实践思路。本篇中 RAG 分块相关图示均来自https://blog.dailydoseofds.com/p/5-chunking-strategies-for-rag 下述相关图片出处不再做单独说明以下enjoy:1、RAG 与分块的重要性在正式开始前老规矩温故知新先来复习下传统 RAG 出现的背景、典型 RAG 流程以及分块的重要意义所在。1.1、典型 RAG 流程为了让大模型能够回答私有知识的问题抛开高成本的 LLM 微调方法外我们可以选择在提问时直接传入私有知识相关的参考信息这种方法更加简便且高效。然而这种方法的缺点很明显。毕竟提示词长度有限当私有数据量过大时传入所有背景信息可能导致提示词过长从而影响模型的处理效率或达到长度上限。而 RAG 巧妙地将 LLM 的生成能力与外部知识库的信息检索能力结合起来接收问题 (Query): 用户向系统提出查询。信息检索 (Retrieval):系统在向量数据库或搜索引擎中查找与问题相关的文档片段。上下文增强 (Augmentation):将检索到的信息片段整合进发送给 LLM 的提示 (Prompt) 中。答案生成 (Generation):LLM 基于原始问题和增强的上下文生成最终回答。在这个流程中要实现高质量的检索需要对原始知识文档进行有效的预处理这也就引出了 RAG 流程中一个至关重要的准备工作——文档分块 (Chunking)。1.2、为啥分块这么重要分块简单来说就是将原始文档按照某种策略分割成更小的、语义相对完整、适合进行索引和检索的文本单元Chunks的过程。 这一步通常在文档被送入向量数据库进行 Embedding向量化之前完成。分块策略选择和执行质量是构建一个 RAG 应用的准确性基础。结合以下三种情形会更好理解些分块过大可能导致检索到的单个块包含过多无关信息噪音增加了 LLM 理解上下文的难度降低了答案的精确性甚至可能超出 LLM 的上下文窗口限制。分块过小或切分不当可能破坏原文的语义连贯性导致一个完整的知识点被拆散到多个块中。检索时可能只召回了部分信息使得 LLM 无法获得完整的背景难以生成全面、准确的答案。未能适应文档结构不同的文档类型如论文、手册、报告、网页具有不同的结构特点。死板的分块方式可能无法有效利用标题、列表、表格等结构信息影响信息提取的完整性。2、五种分块策略详解2.1、固定大小分块核心思想这是最直观、最简单的分块方式。按照预先设定的固定长度 最大 token 数将文本进行切割。为了尽量减少信息损失通常会在相邻的块之间保留一部分重叠内容Overlap。优点:实现简单处理速度快。块大小统一便于批量处理和管理能精确控制输入 LLM 的 token 数量。缺点:容易在句子或语义完整的表达中间被“拦腰斩断”破坏文本的语义连贯性。可能将关联紧密的关键信息分散到不同的块中影响后续检索和理解的完整性。适用场景:处理结构简单、对语义连贯性要求不高的文本。需要快速实现或作为基线对比策略时。对块大小有严格限制的应用。2.2、语义分块核心思想尝试根据文本的语义含义进行切分将语义关联紧密的句子或段落聚合在一起。通常做法是先将文本分成基础单元如句子然后计算相邻单元的语义相似度例如通过嵌入向量的余弦相似度如果相似度高于某个阈值则合并这些单元直到相似度显著下降时才创建一个新的块。优点能更好地保持文本的语义连贯性和上下文的完整性。生成的块通常包含更集中的信息有助于提升检索精度。为 LLM 提供更高质量的上下文有助于生成更连贯、相关的回答。缺点实现相对复杂依赖于嵌入模型的质量和相似度阈值的设定。阈值可能需要根据不同的文档类型进行调整和优化。计算成本通常高于固定大小分块。适用场景对上下文理解和语义连贯性要求较高的场景如问答系统、聊天机器人、文档摘要等。处理叙事性或论述性较强的长文本。2.3、递归分块核心思想:采用“分而治之”的策略。首先尝试使用一组优先级较高的、较大的文本分隔符如段落、章节标记来分割文本。然后检查分割出的每个块的大小。如果某个块仍然超过预设的大小限制就换用下一组优先级更低、更细粒度的分隔符如句子结束符、换行符对其进行再次分割此过程递归进行直到所有块都满足大小要求。优点:在保持一定语义结构的同时能灵活地控制块的大小。比固定大小分块更能尊重原文的自然结构。适应性较好是一种常用的通用分块策略。缺点:实现比固定大小分块更复杂一些。需要预先定义好一组有效的分隔符及其优先级顺序。注两个段落紫色被识别为初始块接着第一个段落再被拆成更小的块。这种方式既保留了语义完整性又能灵活控制分块大小。适用场景:适用于大多数类型的文档特别是那些具有一定层次结构如章节、段落、列表但又不完全规整的文档。是许多 RAG 框架如 LangChain中推荐的默认策略之一。2.4、基于文档结构的分块核心思想:直接利用文档本身固有的、明确的结构元素如标题层级、章节、列表项、表格、代码块、Markdown 标记等来定义块的边界。目标是使每个块尽可能对应文档中的一个逻辑组成部分。优点:能最大程度地保留文档的原始逻辑结构和上下文信息。块的划分方式自然符合人类的阅读和理解习惯。缺点:强依赖于文档本身具有清晰、一致的结构这在现实世界的文档中并非总是得到满足。生成的块大小可能差异巨大某些块可能非常长超出 LLM 的处理限制。通常需要结合递归分块等方法来处理过大的块。适用场景:特别适合处理结构化或半结构化特征明显的文档如技术手册、法律合同、API 文档、教程、带有章节的书籍、格式良好的 Markdown 文件等。注某些结构下的分块长度差异较大可能会超出模型支持的 token 限制。可以考虑结合递归切分法来处理。2.5、基于 LLM 的分块核心思想:不再依赖固定的规则或启发式方法而是利用大型语言模型 (LLM) 自身的理解能力来判断文本的最佳分割点。可以通过设计合适的提示 (Prompt)让 LLM 将文本分割成语义上内聚且与其他部分相对独立的块。优点:理论上具有最高的潜力能实现最符合语义逻辑的分割效果因为 LLM 能更深入地理解文本内容、上下文和细微差别。缺点:计算成本最高昂处理速度最慢因为涉及到多次调用 LLM。需要精心设计有效的 Prompt 来指导 LLM 完成分块任务。同样受到 LLM 本身上下文窗口大小的限制。适用场景:对分块质量有极致要求并且能够承担高昂计算成本和较慢处理速度的场景。用于处理语义关系特别复杂、传统方法难以处理的文本。也可能作为更复杂策略如生成摘要树、构建知识图谱的一部分。3、LlamaIndex 分块策略演示为了更好的理解五种不同的分块策略原理和实现细节下面以 LlamaIndex 为例展示五种策略对应的示例 python 代码。在具体介绍前先补充说明两个问题LlamaIndex 是什么以及为什么不选择 RAGFlow/Dify 等框架来进行分块策略的演示3.1、LlamaIndex 扫盲介绍LlamaIndex 也是一个非常流行的开源数据框架与 Ragflow 不同LlamaIndex 更像是一个灵活的工具箱或库提供了丰富、模块化的组件来处理 RAG 流程中的各个阶段特别是数据加载、转换包括分块/节点解析、索引和查询。主要组件特点数据连接器 (Data Connectors): 支持从各种来源文件、API、数据库等加载数据。数据索引 (Data Indexes):提供多种索引结构如向量存储索引、列表索引、关键词表索引、树状索引、知识图谱索引等来组织数据以适应不同的查询需求。节点解析/文本分割 (Node Parsing / Text Splitting):这是 LlamaIndex 处理分块的核心部分。它提供了多种可配置的文本分割器 (Text Splitters)让开发者可以精细地控制文档如何被分割成“节点 (Nodes)”LlamaIndex 中对“块/Chunk”的称呼。检索器 (Retrievers):基于索引提供不同的方式来检索与查询相关的节点。查询引擎 (Query Engines):结合检索器和 LLM构建端到端的查询和响应能力。代理 (Agents):构建更复杂的、可以自主规划和使用工具的 LLM 应用。模块化和可扩展性:开发者可以方便地组合、替换或自定义各个组件。演示底层机制的优势LlamaIndex 的设计哲学就是提供明确的、可编程的接口。对于分块节点解析我们可以直接在代码中导入特定的 TextSplitter 类:LlamaIndex 提供了与多种分块策略对应的类。实例化分割器并配置参数:显式地设置块大小、重叠大小、分隔符、模型用于语义分割、结构解析规则等。调用分割器的 split_text 或类似方法:将原始文本输入直接获得分割后的节点列表。下文会通过具体的 Python 代码清晰和直接地展示五种分块策略的实现。3.2、高集成度的局限像 Ragflow、Dify 这样封装度较高的框架提供的开箱即用的端到端解决方案必然会通过 UI 或 API 参数将底层的实现细节抽象化。这虽然方便用户快速搭建应用但对于希望理解“引擎盖”下面发生了什么的用户来说会不够透明难以窥见不同策略的具体代码实现和细微差别。具体来说抽象层级的差异:Ragflow/Dify 等平台为了易用性会将底层的分块逻辑封装在更高级的选项如 Ragflow 的 chunk_method 下拉菜单和相关配置。用户无法直接编写或修改 LlamaIndex 那样的底层分块代码。平台特定实现:即便 Ragflow 提供了一个名为 Paper 的分块方法其内部的具体实现逻辑、使用的分隔符、递归策略等细节与 LlamaIndex 的 MarkdownNodeParser 组合有所不同。配置而非编码:在这些平台上用户更多的是通过图形界面 (UI) 或平台的 Python API 来配置分块选项而不是直接编写分块算法的代码。3.3、LlamaIndex 五种分块策略代码参考固定大小分块:可以使用SentenceSplitter(设置 chunk_size控制token数chunk_overlap 控制重叠) 或 TokenTextSplitter。代码会清晰展示如何设置大小和重叠。from llama_index.core.node_parser import SentenceSplitter splitter SentenceSplitter(chunk_size128, chunk_overlap20) nodes splitter.get_nodes_from_documents(documents) # documents 是加载后的文档对象 # 可以打印 nodes[0].get_content() 查看第一个块的内容语义分块:LlamaIndex 提供了 SemanticSplitterNodeParser。需要配置一个嵌入模型并设定相似度阈值 (breakpoint_percentile_threshold)。from llama_index.core.node_parser import SemanticSplitterNodeParser from llama_index.embeddings.openai import OpenAIEmbedding # 或其他嵌入模型 embed_model OpenAIEmbedding() splitter SemanticSplitterNodeParser( buffer_size1, breakpoint_percentile_threshold95, embed_modelembed_model ) nodes splitter.get_nodes_from_documents(documents)递归分块SentenceSplitter 本身就具有一定递归特性它会按顺序尝试使用不同的分隔符默认从 \n\n 到 . 到 等。可以自定义 paragraph_separator 等参数。更复杂的递归如构建摘要树可能涉及 HierarchicalNodeParser。from llama_index.core.node_parser import SentenceSplitter # 默认行为就是递归的可以定制分隔符 splitter SentenceSplitter( separator , # 可以简化分隔符用于演示 chunk_size256, chunk_overlap30, paragraph_separator\n\n\n, # 示例自定义段落分隔符 ) nodes splitter.get_nodes_from_documents(documents)基于文档结构的分块LlamaIndex 有专门的 MarkdownNodeParser 或 JSONNodeParser。对于 PDF 中提取的 MarkdownMarkdownNodeParser 能很好地利用标题、列表等结构。from llama_index.core.node_parser import MarkdownNodeParser parser MarkdownNodeParser() nodes parser.get_nodes_from_documents(documents) # 假设 documents 是加载的 Markdown 内容 # 对于从 PDF 解析得到的 Markdown 尤其有效基于 LLM 的分块虽然没有一个现成的 LLMTextSplitter但 LlamaIndex 的灵活性允许我们构建自定义的 NodeParser在其中调用 LLM 来决定分割点或生成摘要块。例如结合 LLM 进行章节总结或主题划分或者利用 LLM 对元数据进行分析来指导分割。这部分后续我结合具体案例再做演示。4、写在最后4.1、如何在 Ragflow/Dify 中应用不同分块策略上述 LlamaIndex 的演示只是提供了评估的思路和方向不能替代在目标平台上的实际验证。各位可以利用上述LlamaIndex 演示中的原理去解读 Ragflow/Dify等框架提供的分块选项。以RAGFlow为例相关分块选项和上述的五种分块策略很难一一映射结合官方的python api解读参考如下固定大小分块:Ragflow 的 naive 方法最接近这个概念尤其是当配置了 chunk_token_num 时。它可能也结合了 delimiter 进行分割。语义分块:API 文档中没有直接命名为 semantic 的选项。然而某些高级方法像 qa、knowledge_graph 某种程度上可能隐含了语义理解。但 API 没有提供一个像 LangChain 那样明确的 Semantic Splitter 选项。递归分块:像 book 或 paper 这样的方法内部猜测是采用了递归或基于结构的分块逻辑会先尝试按大纲章节、标题分割如果块太大再按段落或句子递归分割。但API 文档没有明确说明其内部递归逻辑。基于文档结构的分块:paper, book, laws, presentation, table 这些方法明显是针对特定文档结构的它们会优先利用文档的固有结构标题、章节、表格结构、幻灯片等来定义块边界。基于 LLM 的分块:API 中没有通用的、直接让 LLM 决定分块边界的选项。这里需要特别说明的是在RAGFlow中创建知识库环节自动关键词/问题提取不是LLM-based Chunking而是分块后的 LLM 增强。但RAPTOR(递归摘要树多粒度检索) 策略涉及对初始块进行聚类并使用 LLM 对聚类进行摘要生成新的、更高层次的“摘要块”。 这个策略可以被认为是“基于 LLM 的分块”的一种高级形式或应用。虽然它可能建立在初始分块之上但它确实利用 LLM 生成了新的、语义层级更高的块摘要这些新块是文档内容的一种 LLM 驱动的重新组织和分割。它不仅仅是分析现有块而是创造了新的块边界和内容。4.2、成熟框架的分块策略定制问题如果你想实现的功能例如一种非常特定的、Ragflow 没有内置或通过 API 参数暴露的分块方法或者你想彻底改变其检索逻辑超出了 Ragflow 提供的 API 和配置选项的范畴理论上唯一的途径就是修改 Ragflow 的源代码。但像 Ragflow 这样的框架通常有复杂的内部结构和依赖关系理解并安全地修改其核心代码需要投入大量时间和精力。而且鉴于 RAGFlow 在常态化更新中创建一个 Ragflow 的“定制分支”意味着官方的更新、补丁或新功能无法直接合并需要自己手动同步或重新应用你的修改维护成本很高。类似Dify 支持编写插件来添加自定义功能而无需修改核心代码。RAGFlow 等类似框架后续预期都会陆续支持更加灵活的扩展机制建议短期内不要在二开上耗费精力。4.3、使用 LlamaIndex 替代 Ragflow/Dify这是我近期被问到比较多的一个问题我在实施或咨询的一些项目中部分企业选择直接基于 LlamaIndex或 LangChain 等类似库来构建自己的企业级 RAG 应用而不是使用封装好的平台。除了可以更好的与现有技术栈集成外企业可以完全掌控 RAG 流程的每一个环节选择最适合需求的模型嵌入、LLM、重排、向量数据库、索引策略、检索逻辑等并进行深度定制和优化。但问题也随着而来这需要开发或集成几乎所有“外围”组件包括 UI、API、数据库、部署运维在内的所有周边系统。 毕竟LlamaIndex 虽然提供了 RAG 的核心“引擎”和“管道”但它本身不是一个可以直接部署给最终用户的完整应用。一般的建议是针对企业级知识库项目落地如果需求与某个成熟框架的功能高度匹配且对定制化要求不高或者时间紧迫需要快速验证那么选择 Ragflow/Dify 显然更高效。但如果企业对 RAG 的性能、逻辑有深度定制的需求希望完全掌控技术栈拥有较强的内部研发能力或者需要将 RAG 深度嵌入现有复杂系统那么基于 LlamaIndex 自建通常是更长远、更灵活的选择尽管前期投入更大。注基于 LlamaIndex 的实践预计 6 月份我会陆续更新些案例在知识星球中供已经在企业内部完成了初步成熟框架 POC 测试的星友进一步进行自定义开发。