Skip to content
阅读进度0%
RAG待复核

文档切分策略

RAG 系统中的文档切分技术详解 - 15种实战策略

我的学习与文章检索 →
学习记录仅保存在当前浏览器,可随时取消标记。

文档切分策略 ​

掌握文档切分的核心技术,选择合适的切分策略优化RAG效果

🎯 核心概念 ​

为什么需要文档切分? ​

RAG系统的第一步就是将长文档切分成小块,这样做的原因:

  • Token 限制:嵌入模型、重排器和生成模型各有输入上限,通常不能按生成模型的窗口来切块
  • 检索精度:小块更容易匹配用户查询的具体信息
  • 计算效率:控制单次输入成本;块越多,索引体积与召回开销也可能越大
  • 语义聚焦:避免一个chunk包含过多无关主题

切分的核心挑战 ​

关键平衡

粒度平衡:太大检索不准,太小丢失上下文
语义完整:避免在句子中间切断,保持逻辑连贯
重叠处理:边界信息的保留与冗余的权衡


🔬 文档智能预处理(切分前的关键步骤) ​

基于《超越纯文本:解锁高级RAG中复杂文档预处理的艺术》

核心洞察

数据准备决定证据能否被正确读取,模型能力决定证据能否被正确使用。 数字原生文档通常先提取文本与结构;扫描件才需要 OCR。解析错误会把错误证据传到后续所有环节。

文档智能六阶段流水线 ​

阶段技术作用关键工具
1. 布局分析LayoutLM、DocLayout-YOLO识别标题、段落、表格、图片等结构元素LayoutLMv3、GNN
2. OCR识别光学字符识别将图像中的文字转为可编辑文本PaddleOCR、Tesseract
3. 表格识别表格结构识别从视觉网格重建结构化数据TableTransformer
4. 公式识别数学/化学公式解析将公式图像转为LaTeX等格式Pix2Tex、Mathpix
5. 图像分析VLM视觉语言模型为图表生成文本描述GPT-4V、LLaVA
6. 阅读顺序逻辑流检测确定多栏文档的正确阅读顺序规则/ML混合

为什么布局分析至关重要? ​

下例展示组件职责,布局、OCR、表格和阅读顺序解析器由具体实现注入,不能直接当作可运行 SDK。每个输出元素应保留页码和边界框,便于回到原件核验。

python
# 反面案例:双栏PDF直接提取文本
# 结果:两栏句子被胡乱拼接,毫无逻辑

# 正确做法:先进行布局分析
class DocumentIntelligencePipeline:
    """文档智能处理流水线"""
    
    def process(self, document):
        # 1. 布局分析:识别结构元素
        layout = self.layout_analyzer.detect(document)
        # 输出:标题、段落、表格、图片的边界框和类型
        
        # 2. 按元素类型分别处理
        for element in layout.elements:
            if element.type == 'table':
                content = self.table_parser.extract(element)
            elif element.type == 'formula':
                content = self.formula_recognizer.parse(element)
            elif element.type == 'image':
                content = self.vlm.describe(element)
            else:
                content = self.ocr.extract(element)
            
            element.content = content
        
        # 3. 按阅读顺序重组
        ordered_content = self.reading_order_detector.sort(layout)
        return ordered_content

LayoutLM:布局感知的核心技术 ​

LayoutLM通过多模态嵌入同时建模文本和布局信息:

  1. 文本嵌入:类BERT嵌入捕捉语义
  2. 1D位置嵌入:单词在序列中的顺序
  3. 2D位置嵌入:每个token的边界框坐标(x₀,y₀,x₁,y₁) — 突破性特征
  4. 图像嵌入:字体样式、颜色等视觉特征

关键规则:视觉上相邻的词语通常在语义上相关


📋 15种分块策略详解 ​

基于《解锁RAG效能:15种分块策略秘籍》的实战总结

1️⃣ 逐行分块法(Line-by-Line Chunking) ​

核心原理:以换行符为分割点,每行独立成块

适用场景:

  • 聊天记录、对话转录文本
  • 问答式数据(客服对话、访谈实录)
  • 结构化的列表数据

实战示例:

python
def line_chunking(text):
    return text.strip().split('\n')

# 示例
dialogue = """Alice: 嗨Bob,今天下午3点有空通话吗?
Bob: 可以,想讨论项目进展吗?
Alice: 是的,还要聊聊客户会议的事
Bob: 没问题!3点见"""

chunks = line_chunking(dialogue)
# 结果:4个独立的对话块

优势:保留对话天然独立性,支持细粒度检索
注意:过短的行可能导致上下文缺失

2️⃣ 固定尺寸分块法(Fixed-Size Chunking) ​

核心原理:按固定字符数/Token数分割,无视语义边界

适用场景:

  • 杂乱无章的非结构化文本
  • OCR识别结果、网页爬取数据
  • 老旧扫描文档的数字化内容

实战示例:

python
def fixed_size_chunking(text, chunk_size=500, overlap=50):
    if chunk_size <= 0 or not 0 <= overlap < chunk_size:
        raise ValueError("要求 chunk_size > 0 且 0 <= overlap < chunk_size")
    chunks = []
    start = 0
    while start < len(text):
        end = min(start + chunk_size, len(text))
        chunk = text[start:end]
        chunks.append(chunk)
        if end == len(text):
            break  # 尾块必须终止,避免 overlap 让循环永不结束
        start = end - overlap
    return chunks

优势:实现数据块标准化,便于统一处理
风险:可能割裂完整语义单元,建议仅在万不得已时使用

3️⃣ 滑动窗口分块法(Sliding Window Chunking) ​

核心原理:固定窗口大小,按步长滑动切分,保留重叠区域

适用场景:

  • 连续性强的技术文档
  • 小说、散文等文学作品
  • 需要保持上下文连贯的长文本

关键参数:

  • window_size:窗口大小(如512 tokens)
  • step_size:滑动步长(如400 tokens,重叠112 tokens)

4️⃣ 句子级分块法(Sentence-Based Chunking) ​

核心原理:以句号、问号、感叹号等句子边界为切分点

优势:保持语义完整性,适合问答系统
实现:结合NLP库(如spaCy、NLTK)进行句子分割

5️⃣ 段落分块法(Paragraph Chunking) ​

核心原理:以段落(双换行符\n\n)为切分单位

适用场景:

  • 结构化文档(论文、报告)
  • 博客文章、新闻稿
  • 技术文档

6️⃣ 递归分块法(Recursive Chunking) ​

核心原理:从大单元开始,逐层分割过大的分块,直至符合尺寸要求

算法流程:

  1. 先按段落分割
  2. 段落过大 → 按句子分割
  3. 句子仍过大 → 按固定长度分割

实战示例:

python
def recursive_chunking(text, max_size=500):
    # 第1层:段落分割
    paragraphs = text.split('\n\n')
    
    chunks = []
    for para in paragraphs:
        if len(para) <= max_size:
            chunks.append(para)
        else:
            # 第2层:句子分割
            sentences = para.split('。')
            for sent in sentences:
                if len(sent) <= max_size:
                    chunks.append(sent)
                else:
                    # 第3层:固定长度分割
                    chunks.extend(fixed_size_chunking(sent, max_size, overlap=0))
    return chunks

7️⃣ 表格分块法(Table Chunking) ​

核心原理:将表格作为独立分块(整体或逐行分割)

适用场景:

  • 财务报告、销售数据
  • 科学论文的实验结果
  • 包含结构化数据的混合文档

处理策略:

  • 整表保留:小表格整体作为一个chunk
  • 逐行分割:大表格按行切分,保留表头
  • 语义分组:按业务逻辑将相关行组合

8️⃣ 章节/标题分块法(Section-Based Chunking) ​

核心原理:按文档的层级结构(H1、H2、H3标题)进行切分

优势:保留文档逻辑结构,便于理解上下文关系

9️⃣ 语义分块法(Semantic Chunking) ​

核心原理:基于语义相似度,将相关内容聚合为一个chunk

实现方式:

  • 计算句子间的embedding相似度
  • 设定相似度阈值进行聚类
  • 动态调整chunk边界

计算开销:句子级向量化与边界判断增加成本,不保证优于结构化递归切分。语义连续也不意味着证据完整,尤其要保留否定、条件与表格说明。

🔟 Token分块法(Token-Based Chunking) ​

核心原理:按模型的Token数量进行精确切分

重要性:直接对应模型输入限制,是最精确的切分方式

python
import tiktoken

def token_chunking(text, model="gpt-3.5-turbo", max_tokens=500):
    encoding = tiktoken.encoding_for_model(model)
    tokens = encoding.encode(text)
    
    chunks = []
    for i in range(0, len(tokens), max_tokens):
        chunk_tokens = tokens[i:i + max_tokens]
        chunk_text = encoding.decode(chunk_tokens)
        chunks.append(chunk_text)
    return chunks

11️⃣ 父子分块(Parent–Child Chunking) ​

小块负责匹配,命中后取回所属父段落或章节作为证据。它适合问题简短、回答需要上下文的场景;父块回填必须去重并受 token 预算约束,否则几个小块可能反复带回同一大段。

12️⃣ 句子窗口(Sentence Window) ​

以句子建立检索粒度,命中后扩展相邻若干句。扩展时不能跨租户、文档版本或不相关章节;检查窗口是否包含代词指代和例外条件,而不只是固定取前后 N 句。

13️⃣ 代码结构分块(AST-aware Chunking) ​

按函数、类或语法树节点切分,保留文件路径、符号名、签名、注释和必要导入。超长函数需要继续切分并记录所属符号。调用关系问题还可能需要邻接符号或图检索,单个函数块不足以回答。

14️⃣ 问答对分块(QA-pair Chunking) ​

FAQ 将问题与回答作为同一单元,并保存业务条件、时间与来源。多轮客服对话按问题解决过程分组,不能让“可以”“不支持”等回答脱离前文;合成问题只做检索辅助,不替换原始事实。

15️⃣ 上下文补充分块(Context-enriched Chunking) ​

为片段加入标题路径、文档摘要或模型生成的局部背景,提高独立检索时的可理解性。生成补充信息要与原文分字段保存并核验;引用仍指向原文,避免把补充摘要中的推测升级为事实。

⚙️ 分块策略选择指南 ​

按文档类型选择 ​

文档类型推荐策略理由
对话记录逐行分块保持问答独立性
技术文档章节分块 + 递归结合结构与大小控制
学术论文段落分块逻辑单元完整
小说散文滑动窗口保持情节连贯
表格数据表格专用保持结构化信息
OCR文本固定尺寸结构混乱时的兜底方案

按应用场景选择 ​

应用场景策略组合参数建议
问答系统句子级 + Token控制每chunk 1-3个完整句子
文档摘要段落级 + 重叠窗口chunk_size=800, overlap=100
代码检索函数级 + 递归按函数/类边界切分
多语言文档语义分块考虑语言特征

🛠️ 实战最佳实践 ​

LangChain 实现 ​

安装 langchain-text-splitters。下例的默认长度函数是 len,所以 1000/200 是字符数,不是 token 数;token 限额需要指定计数器或使用对应 tokenizer。见 LangChain 递归切分官方文档。

python
from langchain_text_splitters import RecursiveCharacterTextSplitter

# 中文优化的递归分块
splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000,
    chunk_overlap=200,
    separators=["\n\n", "\n", "。", "!", "?", ";", " ", ""],
    keep_separator=True  # 保留分隔符
)

document = "# 年假规定\n\n员工满足连续工作条件后,按规定申请年假。"
chunks = splitter.split_text(document)

混合策略实现 ​

下面是接口示意,section_based_chunking 需由 Markdown/HTML 等结构解析器实现;不是仅按字符串匹配标题就能处理所有文档。

python
def hybrid_chunking(text, doc_type="general"):
    if doc_type == "dialogue":
        return line_chunking(text)
    elif doc_type == "structured":
        return section_based_chunking(text)
    else:
        # 默认递归策略
        return recursive_chunking(text, max_size=800)

元数据保留 ​

python
def chunking_with_metadata(text, source_file, page_num):
    chunks = recursive_chunking(text)
    
    metadata_chunks = []
    for i, chunk in enumerate(chunks):
        metadata_chunks.append({
            "content": chunk,
            "source": source_file,
            "page": page_num,
            "chunk_id": f"{source_file}_{page_num}_{i}",
            "chunk_size": len(chunk)
        })
    
    return metadata_chunks

⚠️ 常见问题与解决 ​

问题1:上下文丢失 ​

现象:切分后的chunk缺乏完整信息
解决:

  • 在相同上下文 token 预算下比较不同 overlap;不默认采用 20–30%,先检查重复证据占比
  • 使用父子分段:小chunk检索,大chunk生成
  • 保留关键元数据(标题、章节信息)

问题2:检索不精准 ​

现象:返回的chunk与查询不匹配
解决:

  • 将标准证据映射回原文,确认是块太长引入噪声,还是块太短切断条件,再调整大小
  • 优化分隔符选择
  • 结合重排序(Rerank)

问题3:计算开销大 ​

现象:语义切分速度慢
解决:

  • 预计算文档embedding
  • 使用缓存机制
  • 混合策略:重要文档用语义分块,其他用递归分块

切分方案怎么验收 ​

为每个问题标出原文中的最小证据区间,再观察切分后是否仍能完整覆盖。固定嵌入模型、候选 K、重排器及最终上下文 token 预算,对比切分方案;只固定 K 会让大块方案获得更多可见信息,比较并不公平。

至少记录:证据完整率、Recall@K、引用定位正确率、上下文重复率、索引大小、解析失败率与入库耗时。重点抽查跨页表格、页眉页脚、脚注、代码块和“除非/不适用”等限定语。可检索块需要稳定文档 ID、版本、页码/字符区间、标题路径和权限标签;单纯顺序编号不足以支持更新追溯。

Token 切分示例仅展示计数思路:按 token 数硬切可能拆开字符对应的字节序列,也可能割断完整句子。生产中优先沿原文边界分段,再用 tokenizer 检查上限;截断、解码与重新编码后的长度都要验证。

🔗 相关阅读 ​

相关文章:

外部资源:

基于 VitePress 构建