Skip to content

Latest commit

 

History

History
268 lines (181 loc) · 7.27 KB

File metadata and controls

268 lines (181 loc) · 7.27 KB

5.2 文档分块策略

5.2.1 分块的重要性

分块(Chunking)是将大文档切分为小片段的过程,是 RAG 系统的关键步骤。分块质量直接影响:

  • 检索精度:能否找到最相关的内容
  • 上下文完整性:检索到的内容是否包含完整语义
  • Token 效率:是否高效利用上下文空间

5.2.2 分块的基本原则

原则一:语义完整性

每个块应该是一个语义完整的单元,能够独立表达完整的含义。

原则二:大小适中

块不能太大(占用过多上下文空间),也不能太小(缺失必要上下文)。

原则三:信息自包含

块应该包含理解其内容所需的必要背景信息。

5.2.3 常见分块方法

graph TB
    subgraph "分块方法层级"
        A["固定大小分块"]
        B["基于分隔符分块"]
        C["递归分块"]
        D["语义分块"]
        E["基于 LLM 分块"]
    end

    A --> |"简单"| F["复杂度"]
    E --> |"复杂"| F
Loading

图 5-2:分块方法层级

固定大小分块

按固定的字符数或 Token 数切分。

优点:实现简单、结果可预测
缺点:可能破坏语义完整性
适用:快速原型、对质量要求不高

参数:块大小(通常 500-2000 字符)、重叠大小(10-20%)

基于分隔符分块

按自然分隔符(段落、句子、换行)切分。

优点:保持自然边界
缺点:块大小不一致
适用:结构清晰的文档

常用分隔符:\n\n\n.

递归分块

尝试按多级分隔符切分,自动适应文档结构。

优点:平衡完整性和一致性
缺点:实现较复杂
适用:大多数文档类型

典型分隔符序列:[“\n\n”, “\n”, “ ”, “”]

语义分块

基于语义相似度切分,保持语义单元完整。

优点:语义完整性最好
缺点:计算成本高
适用:对质量要求高的场景

实现方式:计算相邻句子的嵌入相似度,在相似度低谷处切分。

基于 LLM 分块

使用语言模型理解内容结构进行切分。

优点:理解能力最强
缺点:成本高、延迟大
适用:高价值内容、复杂文档

5.2.4 分块策略对比实验(示意)

为了说明不同分块策略可能带来的差异,下面给出一个 示意性的对比图(不同数据集、检索器、嵌入模型与评测口径会导致结果差异很大):

xychart-beta
    title "不同分块策略的检索效果对比 (Recall@10)"
    x-axis ["固定大小(200)", "固定大小(500)", "语义分块", "递归分块"]
    y-axis "Recall@10 (%)" 40 --> 90
    bar [65.2, 72.5, 85.8, 81.3]
Loading

图 5-3:不同分块策略检索效果示意

注:数值仅用于说明趋势,不应被视为可复现的通用结论。

  • 语义分块 通常能获得最佳的检索效果,因为其保持了语义单元的完整性。
  • 递归分块 在效果与成本之间取得了较好的平衡。
  • 过小的固定分块 容易破坏语义,导致召回率下降。

5.2.5 分块参数调优

块大小选择

场景 推荐大小 理由
精确问答 200-500 Token 聚焦单一信息点
综合理解 500-1000 Token 更多上下文
长文档摘要 1000-2000 Token 涵盖更多内容

重叠设置

重叠可以防止信息被切断在边界处,典型设置为块大小的 10-20%。

元数据保留

为每个块附加元数据:

  • 来源文档
  • 位置信息(第几章、第几节)
  • 标题层级
  • 时间戳

5.2.6 特殊文档类型处理

代码文档

按函数、类等逻辑结构切分,而非按行数。

表格数据

保持行完整性,必要时将表头附加到每个块。

对话记录

按对话轮次切分,保持问答对完整。

技术文档

尊重标题层级结构,将标题作为块的上下文前缀。

5.2.7 分块质量评估

评估分块效果的方法:

  1. 边界检查:人工检查切分边界是否合理
  2. 检索测试:测试典型查询能否检索到相关块
  3. 下游效果:观察最终任务效果

5.2.8 最佳实践

  1. 从递归分块开始:适用于大多数场景
  2. 根据内容调整:不同类型文档使用不同策略
  3. 保留结构信息:将标题等信息附加到块中
  4. 测试和迭代:持续评估和优化分块效果

5.2.9 实战案例:三种场景的分块对比

以下是三种典型场景的分块策略对比,展示如何根据内容特点选择合适的方法。

案例一:产品规格表

场景:用户问“这款手机支持 5G 吗?”需要从产品说明书中检索答案。

错误做法

# 按 500 字符固定分块

第一块: "...4800万像素主摄像头,支持光学防抖。5G 网络..."  ← 5G信息被切断
第二块: "...支持 SA/NSA 双模。电池容量 5000mAh..."

正确做法

# 按产品规格表的属性分块

块-网络: "网络制式: 5G SA/NSA 双模,支持 n1/n28/n41/n78 频段"
块-相机: "摄像头: 4800万像素主摄,支持光学防抖"
块-电池: "电池: 5000mAh,支持 67W 快充"

踩坑经验:规格表按固定字符数分块会把相关属性切断,导致模型无法完整回答。应按语义属性分块,每个块对应一个完整的产品特性。


案例二:法律合同

场景:找出合同中的“不可抗力”免责条款。

错误做法

# 按段落分块,但切断了条款

第一块: "...第八条 不可抗力 如因战争、地震、洪水等不可..."
第二块: "...抗力事件导致合同无法履行,双方均不承担违约责任。"

正确做法

# 按条款层级分块,保持完整

块-第八条: "第八条 不可抗力
如因战争、地震、洪水等不可抗力事件导致合同无法履行,双方均不承担违约责任。
不可抗力事件发生后,受影响方应在 7 日内书面通知对方。"

踩坑经验:法律文本的完整性至关重要。曾因分块切断句子导致法律含义改变(“不承担”变成“承担”)。必须按条款结构分块,宁可块大一点也不能破坏语义完整性。


案例三:代码库

场景:开发者问“这个函数是怎么实现的?”

错误做法

# 按 50 行固定分块

第一块: "def calculate_price(items):
    total = 0
    for item in items:
        if item.discount:"  ← 函数被切断

第二块: "            price = item.price * 0.8
        else:
            price = item.price
    return total"  ← 缺少函数开头

正确做法

# 按函数/类为单位分块

块-calculate_price: "# 位置: pricing.py:45-62
def calculate_price(items):
    '''计算购物车总价,支持折扣'''
    total = 0
    for item in items:
        if item.discount:
            price = item.price * 0.8
        else:
            price = item.price
        total += price
    return total

调用方: `checkout.py:process_order()`

踩坑经验:代码按行数分块会切断函数逻辑,模型无法理解完整实现。应按 AST(抽象语法树)提取函数/类为单位,并附带调用关系元数据。