2026-07-24T15:41:43.955Z / RAG
RAG文本分块策略实践:从粗暴切分到智能分块
探讨RAG系统中文本分块的策略演进,包括分块大小选择、边界处理、重叠设计和中文特殊处理。
文本分块ChunkingRAG优化中文处理
RAG文本分块策略实践:从粗暴切分到智能分块
为什么分块很重要?
在RAG系统中,分块(Chunking)是将长文档切分成适合检索和生成的小片段。分块质量直接影响:
- 检索精度:太大的块会稀释相关性,太小的块缺乏上下文
- 生成质量:LLM需要足够的上下文才能生成准确回答
- 引用可追溯性:用户需要能定位到具体内容
分块策略演进
第一阶段:固定长度切分
最简单的方案是按固定字符数切分:
每500个字符切一刀
问题:
- 可能在句子中间切断
- 破坏语义完整性
- 无法保留文档结构
第二阶段:按段落切分
按空行分割段落
每个段落是一个chunk
问题:
- 段落长度差异很大
- 短段落缺乏上下文
- 长段落可能超过模型上下文窗口
第三阶段:智能分块(FitAtlas当前方案)
优先保持Markdown标题边界
↓
section内按空行拆分段落
↓
超长段落按句号拆分
↓
合并段落直到接近目标大小
↓
添加重叠保证上下文连续性
FitAtlas分块参数
目标大小:约400 tokens
重叠大小:约60 tokens
版本标识:heading-v1
Token计数说明
这里的token是近似计数,不是BGE-M3 tokenizer的精确token:
def approximate_token_count(text: str) -> int:
"""单个汉字与连续英文词视为token"""
# 中文:每个字算1个token
# 英文:每个单词算1个token
# 数字:每个连续数字串算1个token
...
使用近似计数的原因:
- 稳定:不依赖特定tokenizer
- 快速:不需要加载tokenizer
- 够用:对于分块目的,精度足够
分块算法详解
1. 保持Markdown标题边界
FitAtlas的素材主要是Markdown格式,标题结构提供了天然的语义边界:
# 胸部训练完整指南
## 胸肌解剖
胸肌的主要功能是...
## 卧推动作流程
### Step 1:起始位置
仰卧在平板凳上...
### Step 2:握距
双手握住杠铃...
分块策略:
- 每个标题section独立分块
- 不跨标题合并
- 保留完整的标题路径
{
"content": "仰卧在平板凳上,双脚踩实地面...",
"heading_path": ["胸部训练完整指南", "卧推动作流程", "Step 1:起始位置"]
}
2. 段落拆分与合并
section内按空行拆分段落,然后合并到接近目标大小:
段落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. 超长段落处理
如果单个段落超过目标大小:
1. 按中英文句号、问号、感叹号拆分
2. 仍然超长的句子按token长度硬切
3. 硬切的片段会丢失语义,尽量避免
4. 重叠设计
重叠的目的是保证上下文连续性:
chunk 1: "...肩胛骨后缩下沉,胸部挺起。下降到底时,肘部约保持45~70度..."
↑
chunk 2: "下降到底时,肘部约保持45~70度,感受胸肌的拉伸。然后推起..."
60 tokens的重叠大约是2-3句话,足够保持上下文连续。
分块质量问题
问题一:短片段缺乏上下文
原文:
### 动作关键
不是:
❌ 肩膀向前顶
而是:
✅ 胸肌带动大臂靠近身体中线
如果标题结构或空行使用过多,可能产生零碎chunk:
- chunk: "不是:"
- chunk: "❌ 肩膀向前顶"
- chunk: "而是:"
- chunk: "✅ 胸肌带动大臂靠近身体中线"
这些短片段缺乏上下文,检索出来没有意义。
解决方案
- 导入前预处理:合并没有独立意义的短句
- 最小chunk大小:设置最小token数阈值
- 相邻合并:过短section与相邻section合并
- 检索时展示上下文:同时显示标题路径和相邻片段
问题二:同一文档结果过多
查询"胸部"时,标题为"胸部训练"的文档的大量chunk都会命中关键词。
解决方案:
- 对同一文档设置最终结果数量上限
- 增加来源多样性
- 使用更具体的查询
PDF文档分块
PDF分块与Markdown不同:
逐页提取文本
↓
保留页码信息
↓
按段落分块(同Markdown逻辑)
↓
每个chunk记录来源页码
{
"content": "胸肌的主要功能是...",
"heading_path": [],
"page_number": 42
}
页码信息对于引用追溯很重要,用户可以定位到书籍的具体页面。
分块版本管理
分块策略改变时,需要全量重建索引:
documents.chunk_version = 'heading-v1'
版本变更场景:
- 调整目标大小(400→500 tokens)
- 调整重叠大小(60→100 tokens)
- 修改分块算法(按标题→按段落)
- 升级token计数方式
分块质量评估
定量指标
- 平均chunk大小:是否接近目标值
- chunk大小分布:是否有过多过小或过大的chunk
- 标题覆盖率:多少chunk有完整的标题路径
定性评估
使用黄金问题集测试:
问题:"卧推时手肘应该保持多少度?"
好的chunk:
"下降到底时,肘部约保持45~70度,具体角度取决于肩关节灵活性..."
→ 包含完整答案,有足够上下文
差的chunk:
"肘部约保持45~70度"
→ 缺乏上下文,不知道是什么动作的肘部角度
最佳实践总结
- 利用文档结构:Markdown标题是天然的语义边界
- 保持语义完整:不要在句子中间切断
- 适当重叠:60 tokens的重叠能保持上下文连续
- 记录来源:标题路径、页码、时间码都要保留
- 设置最小大小:避免产生无意义的短片段
- 版本管理:分块策略改变要记录版本
- 质量评估:用黄金问题集验证分块效果
分块参数选择指南
| 场景 | 目标大小 | 重叠大小 | 说明 |
|---|---|---|---|
| 短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系统。