6.3 文档分块 Chunking:切分策略与最佳实践
在上一节中,我们深入探讨了 Embedding 技术——如何将文本转化为高维向量,使语义相近的内容在向量空间中彼此靠近。我们已经知道,Embedding 模型能够把一段文字"翻译"成一串数字,然后通过计算向量距离来衡量语义相似度。但这里有一个前提问题尚未回答:我们到底应该把多长的文本喂给 Embedding 模型? 是把整篇万字长文直接编码成一个向量,还是先把它拆成小段再分别编码?
答案显而易见——必须拆。但怎么拆、拆多大、在哪里下刀,这些细节直接决定了 RAG 系统的检索质量。本节就来回答这些问题。
6.3.1 为什么需要分块?
一个直觉类比:把长文章剪成卡片
想象你在读一篇 50 页的研究报告,需要为每个关键知识点制作一张索引卡片,以便日后快速翻阅。你会怎么剪?
- 如果每张卡片只抄一句话——卡片太多,翻找费劲,而且单句话经常缺乏上下文,看不懂。
- 如果每张卡片抄整整 5 页——卡片虽少,但每张上信息太杂,关键词被淹没在大量无关文字里,检索时什么都匹配不到。
- 最理想的做法是:每张卡片记录一个完整的知识点——有背景、有结论、能独立理解,翻到就能用。
文档分块(Chunking)做的正是这件事。它把长文档"剪"成一个个大小适中、语义相对完整的片段(Chunk),每个片段单独做 Embedding 并存入向量数据库。检索时,系统找到最相关的几个 Chunk 返回给 LLM,作为生成回答的上下文。
两个硬约束
大语言模型和 Embedding 模型有两个物理约束,迫使我们不能将整篇文档直接编码:
约束 1:Embedding 模型的输入长度限制
- text-embedding-3-small:最大 8191 token
- bge-m3:最大 8192 token
- 超长文档必须切分,否则无法编码
约束 2:LLM 的 "Lost in the Middle" 效应
- 即使上下文窗口很大(如 128K),模型对中间位置信息的关注度会下降
- 检索过长文本会稀释关键信息,导致回答质量降低分块的核心矛盾
分块本质上是在"碎片化"和"信息稀释"之间寻找平衡点:
┌──────────────────────────────────────────────────────────────┐
│ │
│ 块太小(如 100 token) 块太大(如 2000 token) │
│ ├─ 语义碎片化 ├─ 语义完整 │
│ ├─ 检索可能遗漏上下文 ├─ 检索噪声增加 │
│ ├─ 索引数量多,存储大 ├─ 索引数量少,存储小 │
│ └─ 可能找不到准确答案 └─ 可能稀释关键信息 │
│ │
│ 最佳块大小:需要根据场景调优! │
└──────────────────────────────────────────────────────────────┘不存在"一刀切"的完美分块大小。不同的文档类型、不同的检索需求,最佳 Chunk Size 各不相同。接下来我们详细讲解三种主流分块策略。
6.3.2 固定大小分块(Fixed-size Chunking)
原理:按固定的字符数或 token 数机械地切分文档,就像用尺子量好长度后一刀一刀地裁。
"剪卡片"类比:这就像你不管内容是什么,每写满 30 个字就剪一刀。简单粗暴,但有时会在句子中间截断。
原文:"Transformer 是一种神经网络架构。它使用自注意力机制。"
"多头注意力是其核心创新。位置编码解决了序列顺序问题。"
固定大小切分(chunk_size=30, overlap=10):
Chunk 1: "Transformer 是一种神经网络架构。它使用自注意力"
Chunk 2: "它使用自注意力机制。多头注意力是其核心创新。位置"
Chunk 3: "位置编码解决了序列顺序问题。"注意 Chunk 1 和 Chunk 2 之间有 10 个字符的重叠("它使用自注意力"),这是为了防止关键信息恰好被切分边界割裂。
优点:实现简单、速度极快、行为可预测 缺点:可能在句子或词语中间切断,破坏语义完整性
# ── 固定大小分块示例 ──
from langchain.text_splitter import CharacterTextSplitter # 导入字符级分块器
text = "Transformer 是一种神经网络架构。它使用自注意力机制。" \ # 准备待切分的文本
"多头注意力是其核心创新。位置编码解决了序列顺序问题。"
splitter = CharacterTextSplitter(
separator="", # separator 为空字符串:不按特定分隔符切,纯按字符数切
chunk_size=30, # 每个块最多 30 个字符
chunk_overlap=10, # 相邻块之间重叠 10 个字符,防止边界信息丢失
)
chunks = splitter.split_text(text) # 执行切分,返回字符串列表
for i, chunk in enumerate(chunks): # 遍历每个块,打印编号和内容
print(f"Chunk {i}: '{chunk}'")固定大小分块适合快速原型验证或处理格式统一的纯文本。但在生产环境中,我们通常需要更聪明的策略。
6.3.3 递归分块(Recursive Character Text Splitting)
原理:按优先级递减的分隔符列表递归切分,确保尽量在自然边界(段落、句子)断开,而非在词语中间生硬截断。
"剪卡片"类比:这次你不再盲目地每 30 个字剪一刀,而是有一套优先规则——先尝试沿段落折痕剪,剪不开就沿句子剪,再不行才沿词剪,最后万不得已才按字符剪。
分隔符优先级(从高到低):
"\n\n" -> "\n" -> "。" -> "!" -> "?" -> ";" -> "," -> " " -> ""
先尝试用段落分隔(\n\n)切
↓ 如果某个块还是超过 chunk_size
再用换行符(\n)切
↓ 如果还是太大
再用句号(。)切
↓ 以此类推...
最后才用字符级切分(空字符串 "")这是 RAG 中最常用的分块策略! 它在保持语义完整性和实现效率之间取得了良好平衡。
# ── 递归分块示例 ──
from langchain.text_splitter import RecursiveCharacterTextSplitter # 导入递归分块器
text = """第一章:Transformer 架构
1.1 自注意力机制
自注意力机制是 Transformer 的核心。它允许模型在处理每个词时,
关注输入序列中的所有其他词,从而捕获长距离依赖关系。
1.2 多头注意力
多头注意力通过并行运行多个注意力头,让模型从不同角度学习信息。
每个注意力头可以关注不同的语义关系。
1.3 位置编码
由于 Transformer 不包含循环结构,需要位置编码来注入序列信息。
""" # 定义包含多段落、多层级的示例文本
splitter = RecursiveCharacterTextSplitter(
chunk_size=100, # 目标块大小:100 个字符
chunk_overlap=20, # 相邻块重叠 20 个字符
separators=["\n\n", "\n", "。", "!", "?", ",", " ", ""], # 分隔符优先级列表
length_function=len, # 用 Python len() 计算长度(按字符数)
)
chunks = splitter.split_text(text) # 执行递归切分
for i, chunk in enumerate(chunks): # 逐块输出编号、字符数和内容
print(f"--- Chunk {i} ({len(chunk)} 字符) ---")
print(chunk)
print()递归分块的关键在于分隔符列表的顺序。对于中文文档,建议使用上述配置:段落优先,其次换行、句号、感叹号、问号、分号、逗号、空格,最后才是字符级切分。这样能最大程度地保证每个块的语义完整性。
6.3.4 语义分块(Semantic Chunking)
原理:基于文本的语义相似度变化来确定分块边界。当相邻句子的语义相似度突然下降时,说明话题发生了转变,这里就是一个天然的分块点。
"剪卡片"类比:这次你不看长度,而是看内容——一边读一边判断"这句话和上一句说的是同一件事吗?"如果是,就继续写在同一张卡片上;如果话题突然变了,就在这里剪一刀,开始新卡片。
句子序列:
S1: "自注意力机制是 Transformer 的核心。" ─┐
S2: "它允许模型关注所有位置的词。" ├─ 语义相似度高 -> 同一块
S3: "通过计算注意力权重来加权求和。" ─┘
S4: "---" ← 相似度骤降!在此分块
S5: "位置编码用于注入序列顺序信息。" ─┐
S6: "正弦位置编码是最常用的方法。" ├─ 语义相似度高 -> 同一块
S7: "可学习位置编码也是一种选择。" ─┘
分块结果:
Chunk 1: [S1, S2, S3] ← 关于自注意力
Chunk 2: [S5, S6, S7] ← 关于位置编码语义分块的核心步骤:
- 将文本按句子拆分
- 对每个句子计算 Embedding 向量
- 计算相邻句子向量之间的余弦距离(相似度)
- 当距离超过阈值时,在此处切分
# ── 语义分块示例 ──
from langchain_experimental.text_splitter import SemanticChunker # 导入语义分块器(实验性模块)
from langchain_openai import OpenAIEmbeddings # 导入 OpenAI Embedding 模型
text = """自注意力机制是 Transformer 的核心。它允许模型关注所有位置的词。
通过计算注意力权重来加权求和。多头注意力是自注意力的扩展。
位置编码用于注入序列顺序信息。正弦位置编码是最常用的方法。
可学习位置编码也是一种选择。位置编码解决了并行计算中的顺序问题。
Transformer 的编码器由多个相同的层堆叠而成。每层包含多头注意力和前馈网络。
残差连接和层归一化是每个子层的标配。""" # 包含三个不同主题段落的测试文本
splitter = SemanticChunker(
embeddings=OpenAIEmbeddings(model="text-embedding-3-small"), # 用 Embedding 模型计算句子向量
breakpoint_threshold_type="percentile", # 阈值类型:百分位法
breakpoint_threshold_amount=90, # 取第 90 百分位作为分界阈值
)
chunks = splitter.split_text(text) # 执行语义切分
for i, chunk in enumerate(chunks): # 逐块打印结果
print(f"--- Chunk {i} ---")
print(chunk)
print()语义分块的优点是语义完整性最佳——它能在话题切换的地方精准切割,而非机械地按字数切。缺点也很明显:需要调用 Embedding 模型对每个句子编码,计算成本高,处理速度慢。因此,它适合对检索质量要求极高的场景。
三种策略对比:
| 策略 | 原理 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 固定大小 | 按固定字符/token 数切 | 简单、快速、可预测 | 可能破坏语义 | 简单文本、快速原型 |
| 递归分块 | 按分隔符优先级递归切 | 保持句子/段落完整 | 不考虑语义关联 | 通用场景,最推荐 |
| 语义分块 | 按 Embedding 相似度切 | 语义完整性最佳 | 计算成本高 | 高质量检索要求 |
6.3.5 Chunk Size 与 Overlap 调优
分块策略选定后,两个参数至关重要:Chunk Size(块大小)和 Chunk Overlap(块重叠)。
Chunk Size 选择指南
Chunk Size 太小(< 200 token):
✗ 信息碎片化,检索时可能丢失关键上下文
✗ 索引数量多,存储成本高
✓ 检索精度高,匹配更精确
Chunk Size 太大(> 1000 token):
✗ 检索噪声大,可能包含不相关内容
✗ LLM 处理时关键信息被稀释
✓ 语义完整,上下文丰富
✓ 索引数量少,存储成本低
推荐范围:
中文文档:300-800 字符
英文文档:256-512 token
代码文档:500-1000 token(函数/类级别)Chunk Overlap 的作用
Overlap(重叠)确保被切分边界割裂的信息能在相邻块中重复出现,相当于给相邻卡片之间留一点"重复区域":
无 Overlap(overlap=0):
Chunk 1: "...自注意力机制是 Transformer"
Chunk 2: "的核心。它允许模型关注..."
→ "Transformer 的核心" 被切断了!
有 Overlap(overlap=20):
Chunk 1: "...自注意力机制是 Transformer"
Chunk 2: "机制是 Transformer 的核心。它允许..."
→ 关键信息在 Chunk 2 中保留了推荐 Overlap = Chunk Size 的 10-20%。太大会导致冗余存储和检索重复,太小则无法起到桥接作用。
# ── 针对不同文档类型的推荐配置 ──
from langchain.text_splitter import RecursiveCharacterTextSplitter # 导入递归分块器
# 为不同文档类型预设最佳分块参数
configs = {
"中文技术文档": {"chunk_size": 500, "chunk_overlap": 50}, # 中等块大小,平衡精度与上下文
"英文技术文档": {"chunk_size": 400, "chunk_overlap": 60}, # 英文 token 密度更高,略小
"法律合同": {"chunk_size": 800, "chunk_overlap": 100}, # 条款需要完整上下文,块更大
"FAQ 问答": {"chunk_size": 200, "chunk_overlap": 40}, # 每条问答独立,块可以小
"学术论文": {"chunk_size": 600, "chunk_overlap": 80}, # 段落级语义,中等偏大
"代码文件": {"chunk_size": 1000, "chunk_overlap": 150}, # 函数/类级别,块最大
}
for doc_type, config in configs.items(): # 遍历每种文档类型
splitter = RecursiveCharacterTextSplitter(
chunk_size=config["chunk_size"], # 该类型的块大小
chunk_overlap=config["chunk_overlap"], # 该类型的重叠量
separators=["\n\n", "\n", "。", "!", "?", ";", ",", " ", ""], # 中文优先分隔符
)
print(f"{doc_type}: chunk_size={config['chunk_size']}, "
f"overlap={config['chunk_overlap']}")调优不是一蹴而就的。一个实用的方法是:先用推荐值作为起点,然后准备一组测试问答(如 20 条),手动评估检索结果质量,逐步调整参数直到满意。
6.3.6 多模态文档分块
现实中的文档往往包含文本、表格、图片等多种模态。对于 PDF 报告、合同、论文等复杂文档,简单的文本分块策略往往不够用。
PDF 文档处理
# ── 使用 LlamaParse 处理复杂 PDF ──
# pip install llama-parse llama-index
from llama_parse import LlamaParse # 导入 LlamaParse 解析器
# LlamaParse 可以智能识别 PDF 中的文本、表格、图片
parser = LlamaParse(
result_type="markdown", # 输出为 Markdown 格式,保留结构
parsing_instruction="请提取文档中的所有文本和表格", # 给解析器的自然语言指令
use_vendor_multimodal_model=True, # 启用多模态模型,能理解图片中的文字
)
documents = parser.load_data("./complex_report.pdf") # 解析 PDF 文件
print(f"解析出 {len(documents)} 个文档片段") # 输出解析结果数量解析完成后,得到的 Markdown 文本就可以用前面介绍的分块策略继续处理了。
表格处理策略
表格在 RAG 中需要特殊处理,因为表格的行列结构在纯文本中容易丢失:
策略 1:表格转文本
原始表格 → 转为 Markdown/CSV 文本 → 正常分块
适合:简单表格
策略 2:表格摘要
原始表格 → LLM 生成摘要 → 摘要 Embedding
适合:复杂的大表格,无法直接转文本
策略 3:混合策略
表格 → 表头描述 + 关键行数据 → 文本 Embedding
适合:需要精确数值查询的场景# ── 表格转 Markdown 示例 ──
def table_to_markdown(table_data):
"""将表格数据转为 Markdown 格式字符串"""
rows = table_data["rows"] # 取出所有数据行
headers = table_data["headers"] # 取出表头列表
# 构建表头行
md = "| " + " | ".join(headers) + " |\n"
# 构建分隔行(Markdown 语法要求)
md += "| " + " | ".join(["---"] * len(headers)) + " |\n"
# 逐行构建数据行
for row in rows:
md += "| " + " | ".join(str(cell) for cell in row) + " |\n"
return md # 返回完整的 Markdown 表格字符串
# 示例:Embedding 模型对比表
table = {
"headers": ["模型", "MTEB 得分", "维度", "价格"],
"rows": [
["text-embedding-3-small", "62.3", "1536", "$0.00002/1K"],
["text-embedding-3-large", "64.6", "3072", "$0.00013/1K"],
["bge-large-zh-v1.5", "63.5", "1024", "免费"],
],
}
print(table_to_markdown(table)) # 输出 Markdown 格式表格将表格转为 Markdown 文本后,可以作为一个独立 Chunk 存入向量数据库。检索时如果命中表格 Chunk,LLM 能完整看到表格结构,从而正确回答数值查询。
6.3.7 完整实战:分块策略对比实验
下面通过一个完整示例,对比不同分块策略对检索质量的影响:
# ── 分块策略对比实验 ──
# pip install langchain langchain-openai langchain-chroma chromadb
from langchain.text_splitter import (
CharacterTextSplitter, # 固定大小分块器
RecursiveCharacterTextSplitter, # 递归分块器
)
from langchain_openai import OpenAIEmbeddings # Embedding 模型
from langchain_chroma import Chroma # 向量数据库
from langchain_core.documents import Document # 文档对象
# 准备测试文档(一段关于 Transformer 的多段落文本)
long_text = """
第三节 自注意力机制
自注意力机制(Self-Attention)是 Transformer 架构的核心创新。
它允许模型在处理序列中的每个元素时,动态地关注序列中的所有其他元素。
具体来说,自注意力通过计算 Query、Key、Value 三个矩阵来实现。
多头注意力(Multi-Head Attention)是自注意力的扩展。
它将输入分割成多个"头",每个头独立计算注意力,最后将结果拼接。
这样做的好处是让模型能够从不同的表示子空间学习信息。
第四节 位置编码
由于 Transformer 摒弃了循环结构,模型无法感知序列中的位置信息。
位置编码(Positional Encoding)就是用来解决这个问题的。
它通过给每个位置添加一个唯一的向量,让模型知道词的顺序。
常见的位置编码方法包括:
1. 正弦位置编码:使用正弦和余弦函数生成固定的位置编码。
2. 可学习位置编码:将位置编码作为可训练的参数。
3. 旋转位置编码(RoPE):通过旋转矩阵来编码相对位置信息。
"""
# 创建 LangChain Document 对象
doc = Document(page_content=long_text, metadata={"source": "transformer_notes"})
# 策略 1:固定大小分块(chunk_size=100)
fixed_splitter = CharacterTextSplitter(
chunk_size=100, # 每块 100 字符
chunk_overlap=20, # 重叠 20 字符
separator="" # 纯按字符数切
)
fixed_chunks = fixed_splitter.split_documents([doc]) # 执行切分
# 策略 2:递归分块(chunk_size=200)
recursive_splitter = RecursiveCharacterTextSplitter(
chunk_size=200, # 每块 200 字符
chunk_overlap=40, # 重叠 40 字符
separators=["\n\n", "\n", "。", "!", "?", ",", " ", ""] # 分隔符优先级
)
recursive_chunks = recursive_splitter.split_documents([doc]) # 执行切分
# 对比分块结果
print("=" * 60)
print("策略 1:固定大小分块")
print(f"块数量: {len(fixed_chunks)}")
for i, chunk in enumerate(fixed_chunks):
print(f" Chunk {i} ({len(chunk.page_content)}字符): {chunk.page_content[:80]}...")
print("\n" + "=" * 60)
print("策略 2:递归分块")
print(f"块数量: {len(recursive_chunks)}")
for i, chunk in enumerate(recursive_chunks):
print(f" Chunk {i} ({len(chunk.page_content)}字符): {chunk.page_content[:80]}...")
# 检索质量对比
query = "什么是位置编码?" # 测试查询
embedding = OpenAIEmbeddings(model="text-embedding-3-small") # 初始化 Embedding 模型
# 用固定大小分块的 Chunk 构建向量库并检索
fixed_store = Chroma.from_documents(fixed_chunks, embedding) # 构建 Chroma 向量库
fixed_results = fixed_store.similarity_search(query, k=2) # 检索 Top 2
# 用递归分块的 Chunk 构建向量库并检索
recursive_store = Chroma.from_documents(recursive_chunks, embedding)
recursive_results = recursive_store.similarity_search(query, k=2)
print("\n" + "=" * 60)
print(f"检索测试: '{query}'")
print("\n固定大小分块 - Top 2:")
for i, r in enumerate(fixed_results):
print(f" {i+1}. {r.page_content[:100]}...")
print("\n递归分块 - Top 2:")
for i, r in enumerate(recursive_results):
print(f" {i+1}. {r.page_content[:100]}...")运行这个实验,你会发现递归分块通常能检索到更完整、更有上下文的片段,而固定大小分块的检索结果可能只是某个句子的片段,缺乏完整语义。
6.3.8 Chunk Size 调优实验
下面用一组不同参数来观察 Chunk Size 对分块结果的影响:
# ── Chunk Size 调优分析 ──
import pandas as pd # 数据分析库,用于表格展示
from langchain.text_splitter import RecursiveCharacterTextSplitter # 递归分块器
def analyze_chunk_configs(text, configs):
"""分析不同分块配置的效果"""
results = [] # 收集每组的分析结果
for config in configs: # 遍历每组配置
splitter = RecursiveCharacterTextSplitter(
chunk_size=config["chunk_size"], # 该组的块大小
chunk_overlap=config["chunk_overlap"], # 该组的重叠量
separators=["\n\n", "\n", "。", "!", "?", ",", " ", ""],
)
chunks = splitter.split_text(text) # 执行切分
# 计算平均块长度
avg_chunk_len = sum(len(c) for c in chunks) / len(chunks) if chunks else 0
results.append({
"chunk_size": config["chunk_size"],
"overlap": config["chunk_overlap"],
"num_chunks": len(chunks), # 切出的块数
"avg_chunk_len": round(avg_chunk_len), # 平均块长度
"overlap_ratio": f"{config['chunk_overlap']/config['chunk_size']*100:.0f}%", # 重叠比例
})
return pd.DataFrame(results) # 返回 DataFrame 方便查看
# 测试文本
test_text = """
在现代自然语言处理中,Transformer 架构已经成为事实上的标准。它由 Vaswani 等人在 2017 年的论文《Attention Is All You Need》中首次提出。
自注意力机制是 Transformer 的核心创新。它允许模型在处理序列中的每个元素时,动态地关注序列中的所有其他元素。
多头注意力是自注意力的扩展。它将输入分割成多个"头",每个头独立计算注意力。
位置编码用于注入序列顺序信息。由于 Transformer 摒弃了循环结构,模型无法感知序列中的位置信息。
Transformer 的编码器由多个相同的层堆叠而成。每层包含多头注意力和前馈网络。
残差连接和层归一化是每个子层的标配。它们在深层网络中起到了关键作用。
""".strip()
# 测试不同 Chunk Size 配置
configs = [
{"chunk_size": 100, "chunk_overlap": 10}, # 太小:碎片化
{"chunk_size": 200, "chunk_overlap": 30}, # 偏小:精度高
{"chunk_size": 300, "chunk_overlap": 50}, # 适中:推荐
{"chunk_size": 500, "chunk_overlap": 80}, # 偏大:上下文丰富
{"chunk_size": 800, "chunk_overlap": 100}, # 大:可能稀释
{"chunk_size": 1000, "chunk_overlap": 150}, # 太大:噪声大
]
df = analyze_chunk_configs(test_text, configs) # 执行分析
print("不同 Chunk Size 配置对比:")
print(df.to_string(index=False)) # 打印表格
print("\n调优建议:")
print("- 块数过多(>10)且平均长度小 → 增加 chunk_size")
print("- 块数过少(<3)且平均长度大 → 减小 chunk_size")
print("- 黄金比例:3-7 个块,覆盖完整语义")6.3.9 常见误区
在实际项目中,分块环节有几个高频踩坑点:
误区 1:分块大小"一刀切"
很多人在整个系统中对所有文档使用同一个 chunk_size=500。但 FAQ 问答(每条几句话)和学术论文(每个段落几百字)的最佳分块大小截然不同。应该根据文档类型设置不同的分块参数。
误区 2:忽略 Overlap,设为 0
有人觉得 Overlap 是浪费存储空间,直接设为 0。结果切分边界正好把一个完整的知识点劈成两半,检索时两边都匹配不上。Overlap 的 10-20% 冗余是值得的保险。
误区 3:只用固定大小分块,不看内容结构
固定大小分块会在句子中间硬切。比如"Transformer 的核心创新是"和"自注意力机制"被分到两个块中,单独检索时都无法表达完整含义。至少应该使用递归分块,在自然边界断开。
误区 4:表格直接当文本切
表格被直接塞进文本流后按字符切分,行列结构全乱了。应该先将表格转为 Markdown 格式作为一个独立 Chunk,或者用 LLM 生成摘要再编码。
误区 5:分完块不验证
设好参数后从不做检索质量评估。建议准备一组测试问答,检查检索 Top-K 结果是否包含正确答案所在的 Chunk,持续迭代调优。
误区 6:忽视元数据
每个 Chunk 应该携带来源信息(文件名、页码、章节标题等)。检索命中时返回这些元数据,既能帮助 LLM 引用来源,也能在调试时快速定位原文。
6.3.10 本节小结
| 要点 | 说明 |
|---|---|
| 分块本质 | 在"碎片化"和"信息稀释"之间寻找平衡,把长文档剪成语义完整的小卡片 |
| 固定大小分块 | 按字符/token 数机械切分,简单但可能破坏语义,适合快速原型 |
| 递归分块 | 按分隔符优先级递归切分,最常用策略,兼顾效率和语义完整性 |
| 语义分块 | 基于 Embedding 相似度判断边界,质量最高但计算成本高 |
| Chunk Size | 中文 300-800 字符,英文 256-512 token,代码 500-1000 token |
| Overlap | 推荐 Chunk Size 的 10-20%,防止边界信息丢失 |
| 多模态文档 | PDF 用 LlamaParse 解析,表格转 Markdown 或 LLM 摘要 |
| 常见误区 | 避免"一刀切"、忽视 Overlap、不对表格特殊处理、不做检索验证 |
文档分块是 RAG 系统中影响检索质量最直接的环节。好的分块策略能让每个 Chunk 既语义完整又大小适中,为后续的向量检索奠定基础。
分块完成后,每个 Chunk 都需要经过 Embedding 编码成向量,然后存入向量数据库中。向量数据库负责高效地存储、索引和检索这些高维向量——它就是下一节 6.4 的主题。我们将在那里探讨向量数据库的索引原理(如 HNSW、IVF)、主流方案对比(Chroma、Milvus、Pinecone 等),以及如何选择最适合你场景的方案。
参考资料
- LangChain Text Splitters 文档 — LangChain 官方分块器文档,覆盖所有内置分块策略
- LlamaIndex Chunk Size Optimization Guide — LlamaIndex 官方博客,Chunk Size 优化实战
- Chunking Strategies for RAG — Pinecone 出品的分块策略深度指南
- Semantic Chunking for RAG — 5 种文本分块级别对比教程
- The Chunking Paradigm: Recursive Semantic for RAG Optimization — ACL 2025 论文,递归语义分块方法