RAG分块(Chunking)没做好,耶稣来了也救不了你!!!
面试官抛出一个常见问题,有人想当然地回答:“肯定是LLM生成能力不行。”
面试官微微一笑,没有直接反驳,而是展示了一段代码——一个简单的固定长度切分方法,把一个完整的句子从中间砍断了。
“这块语义都不完整,你让LLM怎么救?”
顿时哑口无言。后来花了半个月研究资料,才恍然大悟:RAG系统翻车,70%的原因在于分块(Chunking)不当。
今天就把这5种主流分块策略掰开揉碎讲清楚,助你避开同样的坑。
一、先彻底理解RAG全流程
别急于调优模型,先搞清楚数据流的处理过程。
明白了吗?生成器只能看到你喂给它的chunk。分块切不好,后续检索和生成全是垃圾结果。
二、5种核心分块策略,别再只会固定长度切分
1. 固定分块(Fixed Chunking)—— 新手村的木剑
最简单直接的方法:按固定token数切分,添加重叠区域防止边界信息丢失。
def fixed_chunk(text, max_tokens=512, overlap=50):
tokens = tokenize(text)
chunks = []
i = 0
while i < len(tokens):
chunk = tokens[i:i+max_tokens]
chunks.append(detokenize(chunk))
i += (max_tokens - overlap)
return chunks
什么时候用?
作为baseline基准,或处理日志、纯文本等无结构内容。不要期望它能处理复杂文档。
2. 递归分块(Recursive Chunking)—— 先段落再句子
先按\n\n(段落)切分,如果仍然过长,则按\n切,再按句子切,层层剥析。
from langchain.text_splitter import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=200,
chunk_overlap=50,
separators=["\n\n", "\n", "。", " ", ""]
)
chunks = text_splitter.split_text(text)
什么时候用?
文档具有清晰的段落和章节结构。相比固定分块更智能,至少不会在半句话中间截断。
下面的流程图清晰地展示了递归分块的决策逻辑:
3. 语义分块(Semantic Chunking)—— 让模型判断分界点
将句子转换为embedding向量,相邻句子相似度骤降的位置就是语义边界。语义发生变化时,果断切分。
# 伪代码示意
sentences = split_sentences(text)
embeddings = embed(sentences)
chunks = []
current_chunk = [sentences[0]]
for i in range(1, len(sentences)):
sim = cosine_similarity(embeddings[i-1], embeddings[i])
if sim < threshold:
chunks.append(join(current_chunk))
current_chunk = [sentences[i]]
else:
current_chunk.append(sentences[i])
什么时候用?
法律文书、科研论文、技术支持文档等上下文断裂将导致严重问题的场景。
代价:计算embedding成本高,阈值需要反复调试。
4. 基于结构的分块(Structure-based Chunking)—— 直接利用文档大纲
HTML中的、标签,Markdown中的#、##标题,PDF的目录结构——这些天然就是完美的分块边界。
每个章节独立成块,若单个章节过长,则降级采用递归分块。
实现要点:
- 使用
BeautifulSoup(HTML)、markdown、pypdf等库提取结构 - 将标题层级作为根节点
- 表格、图片单独成块或提取摘要
说实话,这是生产环境中应用最稳定的策略。结合递归分块,效果甚至优于纯语义切分。
下面这张图直观对比了结构化分块与普通分块的区别:
5. 延迟分块(Late Chunking) —— 最高级的动态分块策略
颠覆传统流程:先不切分,保存整篇文档或大段落。当用户提问到来时,检索出最相关的1-2个大段落,再在这个范围内动态切分为细块。
类似于编程中的延迟计算——获取上下文信息后再做切分决策。
适用场景:
- 长篇技术报告与学术论文(跨段落上下文至关重要)
- 文档频繁更新(无需每次重算所有块)
- 法律/医疗等高精度场景(代词指代、引用不能出错)
代价:计算开销大,需要支持长token的embedding模型;查询时多一步动态切分,响应时间增加。
三、一张对比表帮你选分块策略
| 策略 | 核心逻辑 | 适合场景 | 坑 |
|---|---|---|---|
| 固定分块 | 按固定长度硬切 | 日志、baseline基准 | 语义边界被乱砍 |
| 递归分块 | 层级降维递归切分 | 有段落结构的普通文档 | 依赖分隔符质量 |
| 语义分块 | 基于embedding相似度 | 法律、科研、高精度要求 | 计算昂贵、阈值难调 |
| 结构化分块 | 解析HTML/Markdown标签 | 技术文档、wiki系统 | 需要额外解析库 |
| 延迟分块 | 查询时动态切分 | 长文、高召回需求场景 | 响应慢、成本高 |
写在最后
面试那天如果能把这些策略讲清楚,也不至于被怼得哑口无言。
分块这个环节,看似不起眼,却决定了RAG系统70%的性能上限。别再遇到问题就简单设置chunk_size=512了。
最稳妥的组合拳:
- 技术文档 → 结构化分块 + 递归降级
- 长篇报告 → 延迟分块
- 快速验证 → 固定分块作为baseline
- 法律/医疗 → 语义分块硬扛
没有万能银弹。请根据文档类型和计算资源量力而行。
