2026-07-24T15:41:43.955Z / RAG

RAG文本分块策略实践:从粗暴切分到智能分块

探讨RAG系统中文本分块的策略演进,包括分块大小选择、边界处理、重叠设计和中文特殊处理。

文本分块ChunkingRAG优化中文处理

RAG文本分块策略实践:从粗暴切分到智能分块

为什么分块很重要?

在RAG系统中,分块(Chunking)是将长文档切分成适合检索和生成的小片段。分块质量直接影响:

  • 检索精度:太大的块会稀释相关性,太小的块缺乏上下文
  • 生成质量:LLM需要足够的上下文才能生成准确回答
  • 引用可追溯性:用户需要能定位到具体内容

分块策略演进

第一阶段:固定长度切分

最简单的方案是按固定字符数切分:

text
每500个字符切一刀

问题

  • 可能在句子中间切断
  • 破坏语义完整性
  • 无法保留文档结构

第二阶段:按段落切分

text
按空行分割段落
每个段落是一个chunk

问题

  • 段落长度差异很大
  • 短段落缺乏上下文
  • 长段落可能超过模型上下文窗口

第三阶段:智能分块(FitAtlas当前方案)

text
优先保持Markdown标题边界
↓
section内按空行拆分段落
↓
超长段落按句号拆分
↓
合并段落直到接近目标大小
↓
添加重叠保证上下文连续性

FitAtlas分块参数

text
目标大小:约400 tokens
重叠大小:约60 tokens
版本标识:heading-v1

Token计数说明

这里的token是近似计数,不是BGE-M3 tokenizer的精确token:

python
def approximate_token_count(text: str) -> int:
    """单个汉字与连续英文词视为token"""
    # 中文:每个字算1个token
    # 英文:每个单词算1个token
    # 数字:每个连续数字串算1个token
    ...

使用近似计数的原因:

  • 稳定:不依赖特定tokenizer
  • 快速:不需要加载tokenizer
  • 够用:对于分块目的,精度足够

分块算法详解

1. 保持Markdown标题边界

FitAtlas的素材主要是Markdown格式,标题结构提供了天然的语义边界:

markdown
# 胸部训练完整指南

## 胸肌解剖

胸肌的主要功能是...

## 卧推动作流程

### Step 1:起始位置

仰卧在平板凳上...

### Step 2:握距

双手握住杠铃...

分块策略:

  • 每个标题section独立分块
  • 不跨标题合并
  • 保留完整的标题路径
json
{
  "content": "仰卧在平板凳上,双脚踩实地面...",
  "heading_path": ["胸部训练完整指南", "卧推动作流程", "Step 1:起始位置"]
}

2. 段落拆分与合并

section内按空行拆分段落,然后合并到接近目标大小:

text
段落A: 80 tokens
段落B: 120 tokens
段落C: 150 tokens
段落D: 100 tokens

合并后:
chunk 1: A + B = 200 tokens
chunk 2: B尾部 + C = 210 tokens (60 tokens重叠)
chunk 3: C尾部 + D = 250 tokens (60 tokens重叠)

3. 超长段落处理

如果单个段落超过目标大小:

text
1. 按中英文句号、问号、感叹号拆分
2. 仍然超长的句子按token长度硬切
3. 硬切的片段会丢失语义,尽量避免

4. 重叠设计

重叠的目的是保证上下文连续性:

text
chunk 1: "...肩胛骨后缩下沉,胸部挺起。下降到底时,肘部约保持45~70度..."
                                                    ↑
chunk 2: "下降到底时,肘部约保持45~70度,感受胸肌的拉伸。然后推起..."

60 tokens的重叠大约是2-3句话,足够保持上下文连续。

分块质量问题

问题一:短片段缺乏上下文

原文:

markdown
### 动作关键

不是:

❌ 肩膀向前顶

而是:

✅ 胸肌带动大臂靠近身体中线

如果标题结构或空行使用过多,可能产生零碎chunk:

  • chunk: "不是:"
  • chunk: "❌ 肩膀向前顶"
  • chunk: "而是:"
  • chunk: "✅ 胸肌带动大臂靠近身体中线"

这些短片段缺乏上下文,检索出来没有意义。

解决方案

  1. 导入前预处理:合并没有独立意义的短句
  2. 最小chunk大小:设置最小token数阈值
  3. 相邻合并:过短section与相邻section合并
  4. 检索时展示上下文:同时显示标题路径和相邻片段

问题二:同一文档结果过多

查询"胸部"时,标题为"胸部训练"的文档的大量chunk都会命中关键词。

解决方案:

  • 对同一文档设置最终结果数量上限
  • 增加来源多样性
  • 使用更具体的查询

PDF文档分块

PDF分块与Markdown不同:

text
逐页提取文本
↓
保留页码信息
↓
按段落分块(同Markdown逻辑)
↓
每个chunk记录来源页码
json
{
  "content": "胸肌的主要功能是...",
  "heading_path": [],
  "page_number": 42
}

页码信息对于引用追溯很重要,用户可以定位到书籍的具体页面。

分块版本管理

分块策略改变时,需要全量重建索引:

text
documents.chunk_version = 'heading-v1'

版本变更场景:

  • 调整目标大小(400→500 tokens)
  • 调整重叠大小(60→100 tokens)
  • 修改分块算法(按标题→按段落)
  • 升级token计数方式

分块质量评估

定量指标

  • 平均chunk大小:是否接近目标值
  • chunk大小分布:是否有过多过小或过大的chunk
  • 标题覆盖率:多少chunk有完整的标题路径

定性评估

使用黄金问题集测试:

text
问题:"卧推时手肘应该保持多少度?"

好的chunk:
"下降到底时,肘部约保持45~70度,具体角度取决于肩关节灵活性..."
→ 包含完整答案,有足够上下文

差的chunk:
"肘部约保持45~70度"
→ 缺乏上下文,不知道是什么动作的肘部角度

最佳实践总结

  1. 利用文档结构:Markdown标题是天然的语义边界
  2. 保持语义完整:不要在句子中间切断
  3. 适当重叠:60 tokens的重叠能保持上下文连续
  4. 记录来源:标题路径、页码、时间码都要保留
  5. 设置最小大小:避免产生无意义的短片段
  6. 版本管理:分块策略改变要记录版本
  7. 质量评估:用黄金问题集验证分块效果

分块参数选择指南

场景 目标大小 重叠大小 说明
短FAQ 200 tokens 30 tokens FAQ通常较短
技术文档 400 tokens 60 tokens 平衡精度和上下文
长篇报告 500 tokens 100 tokens 需要更多上下文
对话记录 300 tokens 50 tokens 保持对话连续性

注意:以上只是参考,实际参数需要根据具体场景和检索效果调优。

与其他优化的关系

分块优化需要与其他环节配合:

  • Embedding模型:模型的上下文窗口限制了chunk的最大大小
  • 检索策略:chunk大小影响向量检索和关键词检索的效果
  • LLM生成:chunk大小影响送入LLM的上下文量
  • 引用追溯:chunk需要保留足够的来源信息

分块是RAG系统的基础,基础不牢,上层优化效果有限。


本文基于FitAtlas健身知识库的实践经验。FitAtlas使用BGE-M3 + PostgreSQL pgvector构建可追溯的RAG系统。

RAG文本分块策略实践:从粗暴切分到智能分块 | Remi Resume