RAG 文本分块策略总结:固定分块、语义分块、结构分块与父子分块

RAG 文本分块策略总结:从固定分块到语义分块、结构分块与父子分块

本文只聚焦 RAG 数据准备阶段中的 文本分块 Chunking
目标是理解:为什么要分块、有哪些分块策略、每种策略如何工作、输出结果会是什么样、实际项目该怎么选。


目录

  1. 为什么 RAG 需要文本分块
  2. chunk_size 和 chunk_overlap 的意义
  3. 固定大小分块:CharacterTextSplitter
  4. 递归分块:RecursiveCharacterTextSplitter
  5. 语义分块:SemanticChunker
  6. 语义分块后的超长 chunk 如何处理
  7. 基于文档结构的分块:MarkdownHeaderTextSplitter
  8. 结构分块 + 递归分块组合
  9. 父子分块:小块检索,大块返回
  10. Unstructured 中基于文档元素的分块
  11. LlamaIndex 中基于 Node 的分块
  12. 各种分块策略对比
  13. 实战场景下如何选择分块策略
  14. 最佳实践总结

1. 为什么 RAG 需要文本分块

RAG 的核心流程可以理解为:

原始文档
   ↓
文本分块
   ↓
每个 chunk 做 embedding
   ↓
存入向量数据库
   ↓
用户问题向量化
   ↓
检索最相关的 chunk
   ↓
交给 LLM 生成答案

文本分块的本质是:

把一整篇长文档拆成很多较小的“知识卡片”。

如果不分块,直接把整篇文档作为一个整体,会有几个明显问题:

  1. Embedding 模型有输入长度限制
    文本太长会被截断,导致后面的内容丢失。

  2. LLM 有上下文窗口限制
    检索出来的内容最终要塞进 Prompt,太长会放不进去。

  3. 整篇文档一个向量太粗
    一篇文章可能同时讲很多主题,如果只生成一个向量,这个向量会表达整篇文章的平均语义,无法精准匹配用户的具体问题。

  4. 分块能提高检索精度
    用户问“RAG 的工作流程是什么?”,系统可以直接找到讲“工作流程”的 chunk,而不是把整篇文章都拿出来。

可以用一个非常直观的比喻理解:

不分块:把整本书直接丢给 AI,让它自己翻。
分块:先把书整理成一张张知识卡片,问什么就找相关卡片。

2. chunk_size 和 chunk_overlap 的意义

在很多分块器里,最常见的两个参数是:

chunk_size = 500
chunk_overlap = 100

2.1 chunk_size

chunk_size 表示每个文本块的目标大小。

比如:

chunk_size=200

可以理解为:

尽量让每个 chunk 控制在 200 个字符左右。

chunk_size 太小

优点:

检索更精准,每个 chunk 主题更集中。

缺点:

上下文容易碎,LLM 可能只看到半句话或半个知识点。

chunk_size 太大

优点:

上下文更完整。

缺点:

检索变粗,chunk 里可能混入多个主题,向量语义会被稀释。

2.2 chunk_overlap

chunk_overlap 表示相邻两个 chunk 之间保留多少重复内容。

例如:

chunk_size=100
chunk_overlap=20

大致效果是:

chunk 1:第 0 ~ 100 个字符
chunk 2:第 80 ~ 180 个字符
chunk 3:第 160 ~ 260 个字符

为什么需要 overlap?

因为文本可能刚好在边界处被切断。重叠一部分内容,可以让相邻 chunk 之间保留上下文连续性。

例子

原文:

RAG 包含检索、增强和生成三个阶段。检索阶段会先把用户问题转换成向量,然后在向量数据库中查找相似内容。

如果没有 overlap,可能切成:

chunk 1:
RAG 包含检索、增强和生成三个阶段。检索阶段会先把用户问题

chunk 2:
转换成向量,然后在向量数据库中查找相似内容。

chunk 2 单独看,会缺少“谁转换成向量”。

如果有 overlap,可能是:

chunk 1:
RAG 包含检索、增强和生成三个阶段。检索阶段会先把用户问题

chunk 2:
先把用户问题转换成向量,然后在向量数据库中查找相似内容。

这样 chunk 2 的语义更完整。


3. 固定大小分块:CharacterTextSplitter

固定大小分块是最简单的一类分块策略。

它的思路是:

不管文本内容是什么意思,每隔固定长度切一刀。

可以理解成:

拿尺子切文章。

3.1 示例代码

from langchain.text_splitter import CharacterTextSplitter
from langchain_community.document_loaders import TextLoader

loader = TextLoader("../../data/C2/txt/蜂医.txt")
docs = loader.load()

text_splitter = CharacterTextSplitter(
    chunk_size=200,
    chunk_overlap=10
)

chunks = text_splitter.split_documents(docs)

print(f"文本被切分为 {len(chunks)} 个块")
for i, chunk in enumerate(chunks[:5]):
    print("=" * 60)
    print(f"块 {i+1},长度:{len(chunk.page_content)}")
    print(chunk.page_content)

3.2 数据分块例子

原始文本:

RAG 是检索增强生成技术。它会先从外部知识库中检索相关资料,然后把这些资料和用户问题一起放入提示词中,最后由大语言模型生成答案。RAG 的优势是可以减少幻觉、支持知识更新,并提升回答的可解释性。

假设固定 chunk_size=45,不考虑语义,可能得到:

chunk 1:
RAG 是检索增强生成技术。它会先从外部知识库中检索相关资料,然后

chunk 2:
把这些资料和用户问题一起放入提示词中,最后由大语言模型生成答案。

chunk 3:
RAG 的优势是可以减少幻觉、支持知识更新,并提升回答的可解释性。

如果切分位置不巧,可能出现更糟糕的结果:

chunk 1:
RAG 是检索增强生成技术。它会先从外部知识库中检索相关

chunk 2:
资料,然后把这些资料和用户问题一起放入提示词中,最后由大语言模型

chunk 3:
生成答案。RAG 的优势是可以减少幻觉、支持知识更新,并提升回答的可解释性。

你会发现:

“检索相关资料”被切开了;
“大语言模型生成答案”也被切开了。

这就是固定大小分块的缺点。


3.3 优缺点

优点

缺点


4. 递归分块:RecursiveCharacterTextSplitter

递归分块是实际 RAG 中非常常用的策略。

它不是简单按字数切,而是:

优先按照自然边界切,实在切不开时才按字符强制切。

自然边界通常包括:

段落
换行
句号
问号
感叹号
空格
字符

4.1 示例代码

from langchain_text_splitters import RecursiveCharacterTextSplitter

text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=500,
    chunk_overlap=100,
    separators=["\n\n", "\n", "。", "!", "?", ";", ",", " ", ""]
)

chunks = text_splitter.split_documents(docs)

4.2 数据分块例子

原始文本:

RAG 是检索增强生成技术。它会先从外部知识库中检索相关资料。然后把这些资料和用户问题一起放入提示词中。最后由大语言模型生成答案。

RAG 的优势是可以减少幻觉。它还支持知识实时更新。对于企业知识库问答来说,RAG 是一种非常常见的方案。

如果使用固定大小分块,可能会在任意位置切。

但是递归分块会优先按段落和句号切,可能得到:

chunk 1:
RAG 是检索增强生成技术。它会先从外部知识库中检索相关资料。然后把这些资料和用户问题一起放入提示词中。最后由大语言模型生成答案。

chunk 2:
RAG 的优势是可以减少幻觉。它还支持知识实时更新。对于企业知识库问答来说,RAG 是一种非常常见的方案。

如果 chunk 1 仍然太长,它会继续尝试按句号切:

chunk 1:
RAG 是检索增强生成技术。它会先从外部知识库中检索相关资料。

chunk 2:
然后把这些资料和用户问题一起放入提示词中。最后由大语言模型生成答案。

它的核心优势是:

尽量不要把一句话从中间切断。

4.3 和固定分块的区别

对比项 固定大小分块 递归分块
切分依据 字符数量 段落、换行、句子、字符
是否考虑自然边界 基本不考虑 会优先考虑
语义完整性 较差 更好
实战常用程度 一般 很常用

5. 语义分块:SemanticChunker

语义分块更加“智能”。

它的核心不是看长度,而是看:

文本的意思有没有发生明显变化。

可以理解成:

这一段还在讲同一个主题,就继续放一起;
突然换了主题,就在这里切开。

5.1 示例代码

import os
# os.environ["HF_ENDPOINT"] = "https://hf-mirror.com"

from langchain_experimental.text_splitter import SemanticChunker
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_community.document_loaders import TextLoader

embeddings = HuggingFaceEmbeddings(
    model_name="BAAI/bge-small-zh-v1.5",
    model_kwargs={"device": "cpu"},
    encode_kwargs={"normalize_embeddings": True}
)

text_splitter = SemanticChunker(
    embeddings,
    breakpoint_threshold_type="percentile"
)

loader = TextLoader("../../data/C2/txt/蜂医.txt")
documents = loader.load()

docs = text_splitter.split_documents(documents)

5.2 语义分块的内部流程

它大致做这几步:


5.3 数据分块例子

原始文本:

蜜蜂是一种昆虫。蜜蜂会采花蜜。蜂群中有蜂王、工蜂和雄蜂。人工智能是一门研究机器智能的学科。大语言模型可以生成文本。RAG 可以让大模型结合外部知识回答问题。

先拆句:

句子1:蜜蜂是一种昆虫。
句子2:蜜蜂会采花蜜。
句子3:蜂群中有蜂王、工蜂和雄蜂。
句子4:人工智能是一门研究机器智能的学科。
句子5:大语言模型可以生成文本。
句子6:RAG 可以让大模型结合外部知识回答问题。

计算相邻句子的语义距离:

句子1 - 句子2:距离小,都在讲蜜蜂
句子2 - 句子3:距离小,都在讲蜜蜂
句子3 - 句子4:距离大,从蜜蜂跳到人工智能
句子4 - 句子5:距离小,都在讲 AI
句子5 - 句子6:距离小,都在讲大模型/RAG

最终切分结果:

chunk 1:
蜜蜂是一种昆虫。蜜蜂会采花蜜。蜂群中有蜂王、工蜂和雄蜂。

chunk 2:
人工智能是一门研究机器智能的学科。大语言模型可以生成文本。RAG 可以让大模型结合外部知识回答问题。

这就是语义分块的核心:

在“话题发生明显变化”的地方切开。

5.4 buffer_size 的意义

buffer_size 的作用是:

判断一个句子的语义时,不只看当前句子,还会带上前后几个句子。

例如:

句子1:RAG 会先检索外部知识。
句子2:它可以提高回答准确性。

如果单独看句子2:

它可以提高回答准确性。

不知道“它”是谁。

如果带上前一句,就知道“它”指的是 RAG。

所以 buffer_size 可以减少语义判断误差。


5.5 breakpoint_threshold_type 的几种方式

percentile

把所有语义距离排序,选出最大的那一批作为断点。

通俗理解:

差异最大的前 5% 位置,就认为是换话题了。

standard_deviation

计算所有距离的平均值和标准差。

如果某个距离明显大于平均水平,就认为是断点。

interquartile

用四分位距 IQR 判断异常值。

如果某个距离远远超过正常范围,就认为是断点。

gradient

不只看距离大小,还看距离变化率。

如果距离突然变大,就认为可能是断点。


5.6 优缺点

优点

缺点


6. 语义分块后的超长 chunk 如何处理

语义分块不保证每个 chunk 一定小。

因为它看的是:

语义有没有明显变化

不是:

长度有没有超过限制

如果一整节都在讲同一个主题,它可能会切出一个很大的 chunk。


6.1 示例

原始文本:

RAG 的工作流程包括检索、增强和生成。检索阶段会把用户问题转成向量。然后在向量数据库中查找相关文档。增强阶段会把问题和检索内容拼入 Prompt。生成阶段由大语言模型输出答案。这个流程可以减少幻觉并提升可解释性。除此之外,RAG 还可以支持知识库的实时更新。

因为这整段都在讲 RAG 工作流程,所以语义分块可能输出:

chunk 1:
RAG 的工作流程包括检索、增强和生成。检索阶段会把用户问题转成向量。然后在向量数据库中查找相关文档。增强阶段会把问题和检索内容拼入 Prompt。生成阶段由大语言模型输出答案。这个流程可以减少幻觉并提升可解释性。除此之外,RAG 还可以支持知识库的实时更新。

这个 chunk 语义完整,但可能太长。


6.2 解决方案:语义分块 + 递归分块兜底

推荐做法:

第一步:用 SemanticChunker 按语义切
第二步:检查每个 chunk 的长度
第三步:如果太长,再用 RecursiveCharacterTextSplitter 切小

示例代码:

from langchain_experimental.text_splitter import SemanticChunker
from langchain_text_splitters import RecursiveCharacterTextSplitter

semantic_splitter = SemanticChunker(
    embeddings,
    breakpoint_threshold_type="percentile",
    breakpoint_threshold_amount=95
)

fallback_splitter = RecursiveCharacterTextSplitter(
    chunk_size=800,
    chunk_overlap=100,
    separators=["\n\n", "\n", "。", "!", "?", ";", ",", " ", ""]
)

semantic_chunks = semantic_splitter.split_documents(documents)

final_chunks = []

for doc in semantic_chunks:
    if len(doc.page_content) <= 800:
        final_chunks.append(doc)
    else:
        smaller_chunks = fallback_splitter.split_documents([doc])
        final_chunks.extend(smaller_chunks)

可以总结成一句话:

语义分块负责聪明地找边界;
递归分块负责兜底控制大小。

7. 基于文档结构的分块:MarkdownHeaderTextSplitter

对于 Markdown、HTML、LaTeX 这类有明显结构的文档,可以优先使用结构分块。

结构分块的核心是:

按标题、章节、标签等文档结构来切。

例如 Markdown:

# RAG 入门
## 工作流程
## 技术优势
## 局限性

这些标题本身就告诉我们文章的结构。


7.1 示例代码

from langchain_text_splitters import MarkdownHeaderTextSplitter

markdown_text = """
# RAG 入门

## 工作流程

RAG 包含检索、增强和生成三个阶段。
检索阶段会先把用户问题向量化。
增强阶段会把问题和检索内容拼接进 Prompt。
生成阶段由大语言模型输出答案。

## 技术优势

RAG 可以利用外部知识。
RAG 可以减少幻觉。
RAG 可以支持知识实时更新。
"""

headers_to_split_on = [
    ("#", "Header 1"),
    ("##", "Header 2"),
    ("###", "Header 3"),
]

markdown_splitter = MarkdownHeaderTextSplitter(
    headers_to_split_on=headers_to_split_on
)

chunks = markdown_splitter.split_text(markdown_text)

7.2 数据分块例子

原始 Markdown:

# RAG 入门

## 工作流程

RAG 包含检索、增强和生成三个阶段。
检索阶段会先把用户问题向量化。

## 技术优势

RAG 可以利用外部知识。
RAG 可以减少幻觉。

输出结果可能是:

chunk 1 content:
RAG 包含检索、增强和生成三个阶段。
检索阶段会先把用户问题向量化。

chunk 1 metadata:
{
  "Header 1": "RAG 入门",
  "Header 2": "工作流程"
}
chunk 2 content:
RAG 可以利用外部知识。
RAG 可以减少幻觉。

chunk 2 metadata:
{
  "Header 1": "RAG 入门",
  "Header 2": "技术优势"
}

这里的 metadata 很重要。

它相当于 chunk 的“地址”:

RAG 入门 > 工作流程
RAG 入门 > 技术优势

7.3 优缺点

优点

缺点


8. 结构分块 + 递归分块组合

单纯按标题切,有时会导致某个章节太长。

所以更推荐:

先按标题切,保留结构;
再按大小切,控制长度。

8.1 示例代码

from langchain_text_splitters import MarkdownHeaderTextSplitter
from langchain_text_splitters import RecursiveCharacterTextSplitter

headers_to_split_on = [
    ("#", "Header 1"),
    ("##", "Header 2"),
    ("###", "Header 3"),
]

markdown_splitter = MarkdownHeaderTextSplitter(
    headers_to_split_on=headers_to_split_on
)

header_chunks = markdown_splitter.split_text(markdown_text)

recursive_splitter = RecursiveCharacterTextSplitter(
    chunk_size=200,
    chunk_overlap=50
)

final_chunks = recursive_splitter.split_documents(header_chunks)

8.2 数据分块例子

原始 Markdown:

# RAG 入门

## 工作流程

RAG 包含检索、增强和生成三个阶段。
检索阶段会先把用户问题向量化。
然后在向量数据库中查找相似文档。
增强阶段会把问题和检索内容拼进 Prompt。
最后由大语言模型生成答案。

第一步按标题切:

大块 1:
Header 1 = RAG 入门
Header 2 = 工作流程

内容:
RAG 包含检索、增强和生成三个阶段。
检索阶段会先把用户问题向量化。
然后在向量数据库中查找相似文档。
增强阶段会把问题和检索内容拼进 Prompt。
最后由大语言模型生成答案。

如果这个大块太长,第二步递归切小:

chunk 1:
RAG 包含检索、增强和生成三个阶段。
检索阶段会先把用户问题向量化。

metadata:
{
  "Header 1": "RAG 入门",
  "Header 2": "工作流程"
}
chunk 2:
然后在向量数据库中查找相似文档。
增强阶段会把问题和检索内容拼进 Prompt。
最后由大语言模型生成答案。

metadata:
{
  "Header 1": "RAG 入门",
  "Header 2": "工作流程"
}

重点是:

虽然内容被进一步切小了,但标题 metadata 仍然保留。

9. 父子分块:小块检索,大块返回

父子分块是高质量 RAG 中非常常见的策略。

核心思想是:

小块负责找得准;
大块负责答得全。

也可以叫:

Parent-Child Chunking
小块检索,大块返回
检索粒度和回答粒度分离

9.1 普通分块的问题

只用小块

优点:

检索精准。

缺点:

上下文不完整。

只用大块

优点:

上下文完整。

缺点:

检索不够精准,向量语义容易变混。

父子分块就是为了解决这个矛盾。


9.2 数据分块例子

原始文本:

2.1 满足模型上下文限制

将文本分块的首要原因,是为了适应 RAG 系统中两个核心组件的硬性限制。

嵌入模型负责将文本块转换为向量。这类模型有严格的输入长度上限。如果文本块太长,超出 embedding 模型窗口,就会被截断,导致信息丢失。

大语言模型负责根据检索到的上下文生成答案。LLM 同样有上下文窗口限制。如果单个块过大,可能会导致只能容纳少数几个相关块。

父块 parent chunk:

Parent 1:
2.1 满足模型上下文限制

将文本分块的首要原因,是为了适应 RAG 系统中两个核心组件的硬性限制。

嵌入模型负责将文本块转换为向量。这类模型有严格的输入长度上限。如果文本块太长,超出 embedding 模型窗口,就会被截断,导致信息丢失。

大语言模型负责根据检索到的上下文生成答案。LLM 同样有上下文窗口限制。如果单个块过大,可能会导致只能容纳少数几个相关块。

子块 child chunks:

Child 1-1:
将文本分块的首要原因,是为了适应 RAG 系统中两个核心组件的硬性限制。
parent_id = Parent 1
Child 1-2:
嵌入模型负责将文本块转换为向量。这类模型有严格的输入长度上限。
parent_id = Parent 1
Child 1-3:
如果文本块太长,超出 embedding 模型窗口,就会被截断,导致信息丢失。
parent_id = Parent 1
Child 1-4:
大语言模型负责根据检索到的上下文生成答案。LLM 同样有上下文窗口限制。
parent_id = Parent 1

用户提问:

为什么 embedding 模型需要分块?

检索阶段:

系统检索到 Child 1-2 和 Child 1-3。

但最终不是把 child 给 LLM,而是根据 parent_id 找回:

Parent 1

然后把完整 parent 给 LLM 回答。

这样答案会更完整。


9.3 LangChain 示例

from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain.storage import InMemoryStore
from langchain.retrievers import ParentDocumentRetriever
from langchain_chroma import Chroma
from langchain_huggingface import HuggingFaceEmbeddings

embeddings = HuggingFaceEmbeddings(
    model_name="BAAI/bge-small-zh-v1.5",
    model_kwargs={"device": "cpu"},
    encode_kwargs={"normalize_embeddings": True}
)

vectorstore = Chroma(
    collection_name="child_chunks",
    embedding_function=embeddings
)

store = InMemoryStore()

parent_splitter = RecursiveCharacterTextSplitter(
    chunk_size=1200,
    chunk_overlap=200
)

child_splitter = RecursiveCharacterTextSplitter(
    chunk_size=300,
    chunk_overlap=50
)

retriever = ParentDocumentRetriever(
    vectorstore=vectorstore,
    docstore=store,
    child_splitter=child_splitter,
    parent_splitter=parent_splitter
)

retriever.add_documents(documents)

docs = retriever.invoke("为什么文本分块很重要?")

9.4 推荐参数

child chunk:200 ~ 500 字符
parent chunk:800 ~ 2000 字符

可以理解成:

child 小一些,用于精准检索;
parent 大一些,用于完整回答。

10. Unstructured 中基于文档元素的分块

Unstructured 的分块思想和普通 TextSplitter 不太一样。

它通常先做:

Partitioning:把 PDF、HTML、Word 等文档解析成元素。

元素可能包括:

Title
NarrativeText
ListItem
Table
Header
Footer

然后再做:

Chunking:把这些元素组合成 chunk。

10.1 basic 策略

basic 是默认方式。

它的逻辑是:

按文档顺序连续组合元素,直到达到 max_characters 上限。

数据分块例子

Unstructured 解析后的元素:

Element 1 (Title):
RAG 工作流程

Element 2 (NarrativeText):
RAG 包含检索、增强和生成三个阶段。

Element 3 (ListItem):
检索:从知识库中获取相关信息。

Element 4 (ListItem):
增强:将查询和检索内容放入 Prompt。

Element 5 (ListItem):
生成:由大语言模型生成答案。

使用 basic 后可能组合成:

chunk 1:
RAG 工作流程

RAG 包含检索、增强和生成三个阶段。

检索:从知识库中获取相关信息。
增强:将查询和检索内容放入 Prompt。
生成:由大语言模型生成答案。

如果超过 max_characters,它会开启新的 chunk。


10.2 by_title 策略

by_title 会把 Title 元素视为新章节的开始。

也就是说:

遇到标题,就尽量从这里开始一个新 chunk。

数据分块例子

Unstructured 解析后的元素:

Element 1 (Title):
RAG 工作流程

Element 2 (NarrativeText):
RAG 包含检索、增强和生成三个阶段。

Element 3 (ListItem):
检索:从知识库中获取相关信息。

Element 4 (Title):
RAG 技术优势

Element 5 (NarrativeText):
RAG 可以减少幻觉,并支持知识实时更新。

使用 by_title 后可能得到:

chunk 1:
RAG 工作流程

RAG 包含检索、增强和生成三个阶段。

检索:从知识库中获取相关信息。
chunk 2:
RAG 技术优势

RAG 可以减少幻觉,并支持知识实时更新。

这样就不会把“工作流程”和“技术优势”混在同一个 chunk 里。


10.3 Unstructured 分块适合什么场景

适合:

PDF
HTML
Word
PPT
网页转 PDF
报告
论文
书籍
复杂版面文档

它的优势是:

先理解文档元素,再组合元素。

不是简单把纯文本按字符切开。


11. LlamaIndex 中基于 Node 的分块

LlamaIndex 里常用的概念不是 chunk,而是:

Node

可以理解为:

Node = LlamaIndex 里的文档片段对象。

LlamaIndex 的分块通常是通过:

Node Parser

完成的。


11.1 常规分块:SentenceSplitter

SentenceSplitter 会尽量按句子边界切。

数据分块例子

原文:

RAG 是检索增强生成技术。它会先检索外部知识。然后大模型根据检索结果生成答案。RAG 可以减少幻觉。

切分后可能是:

Node 1:
RAG 是检索增强生成技术。它会先检索外部知识。

Node 2:
然后大模型根据检索结果生成答案。RAG 可以减少幻觉。

11.2 TokenTextSplitter

TokenTextSplitter 按 token 数量控制 chunk 大小。

适合你想严格控制模型输入长度的场景。

数据分块例子

原文:

RAG 包含检索、增强和生成三个阶段。检索阶段负责查找外部知识,增强阶段负责构造 Prompt,生成阶段负责输出答案。

如果每个 Node 控制在较小 token 数,可能得到:

Node 1:
RAG 包含检索、增强和生成三个阶段。

Node 2:
检索阶段负责查找外部知识,增强阶段负责构造 Prompt。

Node 3:
生成阶段负责输出答案。

11.3 MarkdownNodeParser

MarkdownNodeParser 按 Markdown 结构切。

数据分块例子

原始 Markdown:

# RAG 入门

## 工作流程

RAG 包含检索、增强和生成。

## 技术优势

RAG 可以减少幻觉。

切分后可能得到:

Node 1:
内容:RAG 包含检索、增强和生成。
metadata:RAG 入门 > 工作流程
Node 2:
内容:RAG 可以减少幻觉。
metadata:RAG 入门 > 技术优势

11.4 SemanticSplitterNodeParser

它和 LangChain 的 SemanticChunker 类似。

核心是:

根据句子之间的语义距离决定在哪里切。

数据分块例子

原文:

RAG 是检索增强生成技术。它可以结合外部知识回答问题。Redis 是一种内存数据库。Redis 常用于缓存和分布式锁。

切分后可能得到:

Node 1:
RAG 是检索增强生成技术。它可以结合外部知识回答问题。
Node 2:
Redis 是一种内存数据库。Redis 常用于缓存和分布式锁。

因为从 RAG 跳到了 Redis,语义发生明显变化。


11.5 SentenceWindowNodeParser

这是一个非常适合 RAG 的策略。

它的核心是:

检索时用单句,回答时带上前后窗口。

数据分块例子

原文:

句子1:RAG 是检索增强生成技术。
句子2:它会先检索外部知识。
句子3:然后大模型根据检索结果生成答案。
句子4:这种方式可以减少幻觉。

生成的 Node 可能是:

Node text:
它会先检索外部知识。

metadata window:
RAG 是检索增强生成技术。
它会先检索外部知识。
然后大模型根据检索结果生成答案。

检索时:

用 Node text 做精准匹配。

回答时:

把 metadata window 中的上下文交给 LLM。

它的思想和父子分块类似:

小粒度负责精准检索;
大上下文负责完整回答。

11.6 CodeSplitter

CodeSplitter 适合代码文件。

它不是简单按字符切代码,而是尽量按类、函数、方法结构切。

数据分块例子

原始代码:

public class UserService {

    public User getUserById(Long id) {
        return userMapper.selectById(id);
    }

    public void updateUser(User user) {
        userMapper.updateById(user);
    }
}

普通固定分块可能会把一个方法从中间切断。

CodeSplitter 更可能得到:

Node 1:
public User getUserById(Long id) {
    return userMapper.selectById(id);
}
Node 2:
public void updateUser(User user) {
    userMapper.updateById(user);
}

这样更适合代码检索。


12. 各种分块策略对比

分块策略 核心思想 优点 缺点 适合场景
固定大小分块 按字符数切 简单、快 死板、易切断语义 Demo、日志、简单文本
递归分块 优先按段落/句子切 通用、稳定 不真正理解语义 大多数普通文本
语义分块 按语义变化切 主题更集中 慢、依赖 embedding、大小不稳定 主题变化明显的长文
语义 + 递归兜底 先按语义切,超长再递归切 兼顾语义和长度 实现稍复杂 高质量文本分块
Markdown 结构分块 按标题层级切 保留文档结构和 metadata 标题下内容可能太长 Markdown、技术文档
结构 + 递归 先按标题切,再控制大小 保留结构且大小合理 两阶段处理 文档结构清晰的知识库
父子分块 小块检索,大块返回 检索准、回答全 实现更复杂 高质量 RAG
Unstructured basic 连续组合元素 适合复杂文档解析结果 章节边界不一定清晰 PDF、HTML、Word
Unstructured by_title 遇到标题开新块 更符合章节结构 依赖 Title 识别准确性 报告、论文、书籍
SentenceWindow 单句检索,窗口回答 精准且上下文完整 metadata 处理复杂 LlamaIndex 高质量检索
CodeSplitter 按代码结构切 保留函数/类完整性 依赖语言解析支持 代码知识库

13. 实战场景下如何选择分块策略

普通中文长文本

推荐:

RecursiveCharacterTextSplitter

原因:

通用、稳定、简单,比固定分块更自然。

Markdown 技术文档

推荐:

MarkdownHeaderTextSplitter + RecursiveCharacterTextSplitter

原因:

先保留标题结构和 metadata,再控制 chunk 大小。

主题跳跃明显的文章

推荐:

SemanticChunker + RecursiveCharacterTextSplitter 兜底

原因:

先按语义边界切,超长块再二次切小。

企业知识库 / 高质量 RAG

推荐:

父子分块 Parent-Child Chunking

或者:

SentenceWindowNodeParser

原因:

小粒度检索更精准,大上下文返回更完整。

PDF / Word / HTML

推荐:

Unstructured partition + basic / by_title chunking

原因:

复杂文档需要先解析成 Title、NarrativeText、ListItem 等元素。

代码文件

推荐:

CodeSplitter

原因:

按函数、类、方法切分,比按字符切代码更合理。

14. 最佳实践总结

14.1 不要迷信单一分块策略

实际项目中常用的是组合策略:

结构分块 + 递归分块
语义分块 + 递归分块
小块检索 + 大块返回
Unstructured 元素解析 + by_title 分块

14.2 优先保证语义完整性

一个好的 chunk 应该尽量满足:


14.3 metadata 很重要

对于结构化文档,尽量保留 metadata:

文件名
章节标题
页码
标题路径
文档来源

metadata 就像 chunk 的地址,可以提高 RAG 的可解释性和可追踪性。


14.4 chunk_size 不是越小越好

小 chunk 虽然检索精准,但上下文容易不足。

大 chunk 虽然上下文完整,但检索容易变粗。

所以要根据数据类型调参。

一般可以从下面配置开始实验:

chunk_size=500
chunk_overlap=100

对于更长的技术文档,可以尝试:

chunk_size=800
chunk_overlap=150

14.5 高质量 RAG 推荐父子分块

如果你希望系统回答更稳定、更完整,可以考虑:

child chunk:用于向量检索
parent chunk:用于最终回答

一句话总结:

小块负责定位,大块负责解释。

15. 最终记忆口诀

可以用下面几句话记住所有分块策略:

固定分块:按尺子切,简单但死板。

递归分块:按段落和句子切,通用稳定。

语义分块:按意思变化切,更聪明但更慢。

结构分块:按标题目录切,适合 Markdown 和技术文档。

父子分块:小块负责找,大块负责答。

Unstructured:先识别文档元素,再组合成块。

LlamaIndex:先转成 Node,再通过 Node Parser 切分。

最终目标只有一个:

让每个 chunk 既能被模型完整处理,又能在检索时准确命中用户问题,并为 LLM 提供足够完整的上下文。

posted @ 2026-05-06 16:21  代码丰  阅读(0)  评论(0)    收藏  举报