文档处理与分块策略
问题
RAG 中如何进行文档解析和分块?有哪些分块策略?分块大小如何选择?
答案
文档处理是 RAG 的第一步也是最关键的一步——分块质量直接决定检索质量。
一、文档解析
不同格式的文档需要不同的解析工具:
| 格式 | 解析工具 | 注意事项 |
|---|---|---|
| PyPDF2、pdfplumber、Unstructured | 扫描件需 OCR | |
| Word | python-docx、Unstructured | 注意表格和图片 |
| Markdown | 直接按标题分割 | 保留层级结构 |
| HTML | BeautifulSoup、Unstructured | 去除导航/广告噪声 |
| 代码 | 按函数/类/文件分块 | 保留 import 上下文 |
| 表格 | 转为自然语言或 Markdown 表格 | 列-值对比纯文本更有效 |
二、分块策略
1. 固定大小分块(Fixed Size)
from langchain.text_splitter import CharacterTextSplitter
splitter = CharacterTextSplitter(
chunk_size=500,
chunk_overlap=50,
separator="\n"
)
- 最简单,按固定字符/Token 数切割
- 可能在句子中间断开,破坏语义
2. 递归分块(Recursive)
from langchain.text_splitter import RecursiveCharacterTextSplitter
# 按优先级尝试不同分隔符
splitter = RecursiveCharacterTextSplitter(
chunk_size=512,
chunk_overlap=50,
separators=["\n\n", "\n", "。", ";", ",", " ", ""]
)
- 最常用的策略——先尝试段落分割,段落太大再按句子,依次递减
- 较好地保留语义完整性
3. 语义分块(Semantic)
from langchain_experimental.text_splitter import SemanticChunker
from langchain_openai import OpenAIEmbeddings
# 基于 Embedding 相似度判断分割点
splitter = SemanticChunker(
OpenAIEmbeddings(),
breakpoint_threshold_type="percentile", # 相似度突变处分开
)
- 用 Embedding 计算相邻句子的语义相似度,在语义突变处分割
- 质量最高,但成本也最高(需要调用 Embedding API)
4. 按文档结构分块
from langchain.text_splitter import MarkdownHeaderTextSplitter
# 按 Markdown 标题层级分块
splitter = MarkdownHeaderTextSplitter(
headers_to_split_on=[
("#", "h1"),
("##", "h2"),
("###", "h3"),
]
)
- 适用于结构化文档(Markdown、HTML、代码)
- 天然保留文档结构和上下文
三、分块大小选择
| 分块大小 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 小块(128-256 Token) | 检索精确 | 上下文不足 | 事实性问答 |
| 中块(256-512 Token) | 平衡精度和上下文 | 通用默认选择 | 大多数场景 |
| 大块(512-1024 Token) | 上下文丰富 | 可能引入噪声 | 摘要、分析 |
分块大小与 Embedding 模型匹配
不同 Embedding 模型有最佳输入长度。例如 text-embedding-3-small 最大支持 8191 Token,但实际在 256-512 Token 效果最好。过长的输入会稀释语义。
四、分块优化技巧
重叠(Overlap)
块 1: [A B C D E F G H]
块 2: [F G H I J K L M] ← 重叠部分保证跨块信息不丢失
- 一般设置 chunk_overlap = chunk_size × 10%-20%
元数据附加
# 为每个块附加元数据,提升检索和引用能力
for chunk in chunks:
chunk.metadata.update({
"source": "产品手册v3.2",
"page": 15,
"section": "退款政策",
"created_at": "2024-01-15"
})
Parent-Child 策略
- 检索时用小块(精准匹配),生成时返回大块(上下文丰富)
- LlamaIndex 的
SentenceWindowNodeParser实现了这个策略
常见面试问题
Q1: 表格数据如何分块?
答案:
- 转为自然语言:将每行转为 "产品A的价格是199元,库存100件"
- 保持表格格式:用 Markdown 表格格式保留结构
- 列-值对:每行拆为独立块,并附加表头作为上下文
Q2: 如何处理跨页/跨段落的信息?
答案:
- Overlap:相邻块重叠 50-100 Token
- Parent-Child:小块检索,大块返回
- 前置上下文:每个块前面附加章节标题
Q3: 分块太大或太小分别会导致什么问题?
答案:
- 太小:上下文不足,LLM 无法理解完整语义,导致回答片面
- 太大:包含无关信息(噪声),稀释关键内容,且 Embedding 质量下降