Skip to content

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 个字符的重叠("它使用自注意力"),这是为了防止关键信息恰好被切分边界割裂。

优点:实现简单、速度极快、行为可预测 缺点:可能在句子或词语中间切断,破坏语义完整性

python
# ── 固定大小分块示例 ──
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 中最常用的分块策略! 它在保持语义完整性和实现效率之间取得了良好平衡。

python
# ── 递归分块示例 ──
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]  ← 关于位置编码

语义分块的核心步骤:

  1. 将文本按句子拆分
  2. 对每个句子计算 Embedding 向量
  3. 计算相邻句子向量之间的余弦距离(相似度)
  4. 当距离超过阈值时,在此处切分
python
# ── 语义分块示例 ──
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%。太大会导致冗余存储和检索重复,太小则无法起到桥接作用。

python
# ── 针对不同文档类型的推荐配置 ──
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 文档处理

python
# ── 使用 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
  适合:需要精确数值查询的场景
python
# ── 表格转 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 完整实战:分块策略对比实验

下面通过一个完整示例,对比不同分块策略对检索质量的影响:

python
# ── 分块策略对比实验 ──
# 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 对分块结果的影响:

python
# ── 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 等),以及如何选择最适合你场景的方案。


参考资料

  1. LangChain Text Splitters 文档 — LangChain 官方分块器文档,覆盖所有内置分块策略
  2. LlamaIndex Chunk Size Optimization Guide — LlamaIndex 官方博客,Chunk Size 优化实战
  3. Chunking Strategies for RAG — Pinecone 出品的分块策略深度指南
  4. Semantic Chunking for RAG — 5 种文本分块级别对比教程
  5. The Chunking Paradigm: Recursive Semantic for RAG Optimization — ACL 2025 论文,递归语义分块方法