引言
RAG系统的第一步是将文档分块(Chunking)。这个看似简单的步骤,实际上对RAG质量有巨大影响——分块太大,检索不精确;分块太小,丢失上下文。
2026年,分块策略已经从简单的"按字数切分"发展出多种智能策略。本文将系统对比这些策略。
一、为什么分块很重要
1.1 检索精度
文档: "Python是一种解释型语言。它由Guido van Rossum于1991年创建。Python强调代码可读性..."
分块A(太大大): 整个文档作为一个块 → 检索时可能返回很多不相关的内容
分块B(太小): "Python是一种解释型语言。" → 缺少上下文,不知道在说什么
分块C(合适): "Python是一种解释型语言。它由Guido van Rossum于1991年创建。" → 信息完整且精确
1.2 上下文完整性
好的分块应该保持语义完整性——一个块应该表达一个完整的意思。
1.3 检索效率
块的大小直接影响向量索引的大小和检索速度。
二、分块策略分类
2.1 固定大小分块
class FixedSizeChunker:
def __init__(self, chunk_size=500, overlap=50):
self.chunk_size = chunk_size # 字符数或token数
self.overlap = overlap
def chunk(self, text):
chunks = []
start = 0
while start < len(text):
end = start + self.chunk_size
chunk = text[start:end]
chunks.append(chunk)
start = end - self.overlap # 重叠部分
return chunks
优势:简单、快速、可预测。 劣势:可能在句子中间截断,破坏语义。
最佳实践:设置50-100 token的重叠,减少截断影响。
2.2 句子分块
class SentenceChunker:
def __init__(self, max_sentences=5, max_tokens=500):
self.max_sentences = max_sentences
self.max_tokens = max_tokens
def chunk(self, text):
sentences = self.split_sentences(text)
chunks = []
current_chunk = []
current_tokens = 0
for sentence in sentences:
sent_tokens = self.count_tokens(sentence)
if current_tokens + sent_tokens > self.max_tokens or \
len(current_chunk) >= self.max_sentences:
if current_chunk:
chunks.append(" ".join(current_chunk))
current_chunk = [sentence]
current_tokens = sent_tokens
else:
current_chunk.append(sentence)
current_tokens += sent_tokens
if current_chunk:
chunks.append(" ".join(current_chunk))
return chunks
优势:保持句子完整,语义更好。 劣势:块大小不均匀。
2.3 段落分块
class ParagraphChunker:
def chunk(self, text):
paragraphs = text.split("\n\n")
chunks = []
for para in paragraphs:
if self.count_tokens(para) > self.max_tokens:
# 段落太长,进一步分割
sub_chunks = SentenceChunker(self.max_tokens).chunk(para)
chunks.extend(sub_chunks)
else:
chunks.append(para)
return chunks
优势:保持段落结构,符合文档逻辑。 劣势:段落长度可能差异很大。
2.4 语义分块
class SemanticChunker:
def __init__(self, embedding_model, similarity_threshold=0.5):
self.embedder = embedding_model
self.threshold = similarity_threshold
async def chunk(self, text):
# 1. 分成句子
sentences = self.split_sentences(text)
# 2. 计算相邻句子的语义相似度
embeddings = [await self.embedder.embed(s) for s in sentences]
# 3. 在语义不连续处分割
chunks = []
current_chunk = [sentences[0]]
for i in range(1, len(sentences)):
similarity = cosine_similarity(embeddings[i-1], embeddings[i])
if similarity < self.threshold:
# 语义不连续,分割
chunks.append(" ".join(current_chunk))
current_chunk = [sentences[i]]
else:
current_chunk.append(sentences[i])
if current_chunk:
chunks.append(" ".join(current_chunk))
return chunks
优势:语义连贯性最好。 劣势:计算开销大,需要embedding计算。
2.5 递归分块
class RecursiveChunker:
def __init__(self, max_tokens=500):
self.max_tokens = max_tokens
self.separators = ["\n\n", "\n", ". ", " ", ""]
def chunk(self, text):
return self._recursive_split(text, 0)
def _recursive_split(self, text, sep_idx):
if self.count_tokens(text) <= self.max_tokens:
return [text]
separator = self.separators[sep_idx]
parts = text.split(separator) if separator else list(text)
chunks = []
current = ""
for part in parts:
candidate = current + separator + part if current else part
if self.count_tokens(candidate) <= self.max_tokens:
current = candidate
else:
if current:
chunks.append(current)
if self.count_tokens(part) > self.max_tokens:
# 需要更细粒度分割
if sep_idx < len(self.separators) - 1:
chunks.extend(self._recursive_split(part, sep_idx + 1))
else:
# 最细粒度仍超限,硬切
chunks.extend(self.hard_split(part))
current = ""
else:
current = part
if current:
chunks.append(current)
return chunks
优势:优先在自然分隔符处分割,保持结构。 劣势:比固定大小稍复杂。
2.6 文档结构感知分块
class StructureAwareChunker:
async def chunk(self, document):
"""基于文档结构分块"""
# 1. 解析文档结构
structure = await self.parse_structure(document)
# structure = {title, sections: [{heading, content, subsections: [...]}]}
chunks = []
for section in structure.sections:
# 每个section作为一个块(如果不太长)
section_text = self.section_to_text(section)
if self.count_tokens(section_text) <= self.max_tokens:
# 添加section标题作为上下文
chunk = f"## {section.heading}\n\n{section_text}"
chunks.append(chunk)
else:
# section太长,按子section分
for subsection in section.subsections:
sub_text = self.section_to_text(subsection)
chunk = f"## {section.heading} > {subsection.heading}\n\n{sub_text}"
chunks.append(chunk)
return chunks
优势:保持文档逻辑结构,检索结果更有意义。 劣势:需要文档解析支持。
三、分块优化
3.1 上下文增强
class ContextualChunker:
"""为每个块添加上下文信息"""
async def add_context(self, chunks, document_title):
enhanced_chunks = []
for i, chunk in enumerate(chunks):
# 为每个块添加文档标题和位置信息
enhanced = {
"content": chunk,
"metadata": {
"document_title": document_title,
"chunk_index": i,
"total_chunks": len(chunks),
"previous_chunk_summary": await self.summarize(chunks[i-1]) if i > 0 else None,
"next_chunk_summary": await self.summarize(chunks[i+1]) if i < len(chunks)-1 else None
}
}
enhanced_chunks.append(enhanced)
return enhanced_chunks
3.2 父子分块
class ParentChildChunker:
"""父子分块:小块检索,大块提供上下文"""
def chunk(self, text):
# 1. 先分成大块(父块)
parent_chunks = FixedSizeChunker(chunk_size=2000).chunk(text)
# 2. 每个大块分成小块(子块)
result = []
for parent_id, parent in enumerate(parent_chunks):
child_chunks = FixedSizeChunker(chunk_size=200).chunk(parent)
for child_id, child in enumerate(child_chunks):
result.append({
"child_content": child,
"child_embedding": self.embed(child),
"parent_content": parent, # 检索子块时返回父块
"parent_id": f"parent-{parent_id}",
"child_id": f"parent-{parent_id}-child-{child_id}"
})
return result
优势:小块精确检索,大块提供完整上下文。
3.3 动态分块
class DynamicChunker:
"""根据内容类型动态选择分块策略"""
async def chunk(self, document):
# 1. 识别文档类型
doc_type = await self.classify_document(document)
# 2. 根据类型选择策略
if doc_type == "academic_paper":
return self.chunk_academic(document)
elif doc_type == "code_documentation":
return self.chunk_code_doc(document)
elif doc_type == "faq":
return self.chunk_faq(document)
elif doc_type == "narrative":
return self.chunk_narrative(document)
else:
return RecursiveChunker().chunk(document)
def chunk_faq(self, document):
"""FAQ分块:每个Q&A对作为一个块"""
chunks = []
qa_pairs = self.extract_qa_pairs(document)
for qa in qa_pairs:
chunks.append(f"Q: {qa.question}\nA: {qa.answer}")
return chunks
def chunk_code_doc(self, document):
"""代码文档分块:每个函数/类作为一个块"""
chunks = []
sections = self.extract_code_sections(document)
for section in sections:
chunk = f"### {section.name}\n{section.description}\n```{section.code}```"
chunks.append(chunk)
return chunks
四、分块参数调优
4.1 块大小
async def optimize_chunk_size(rag_system, eval_set, sizes=[200, 400, 600, 800, 1000]):
"""寻找最优块大小"""
results = {}
for size in sizes:
# 重新分块
rag_system.set_chunk_size(size)
rag_system.rebuild_index()
# 评估
metrics = await rag_system.evaluate(eval_set)
results[size] = metrics
# 选择最佳
best_size = max(results, key=lambda x: results[x]["ragas_score"])
return best_size, results
4.2 重叠大小
async def optimize_overlap(rag_system, eval_set, overlaps=[0, 50, 100, 150, 200]):
"""寻找最优重叠大小"""
results = {}
for overlap in overlaps:
rag_system.set_overlap(overlap)
rag_system.rebuild_index()
metrics = await rag_system.evaluate(eval_set)
results[overlap] = metrics
return results
五、策略对比
| 策略 | 语义连贯 | 实现复杂度 | 计算开销 | 适用场景 |
|---|---|---|---|---|
| 固定大小 | 低 | 低 | 低 | 原型/简单应用 |
| 句子分块 | 中 | 低 | 低 | 一般文本 |
| 段落分块 | 中高 | 中 | 低 | 结构化文档 |
| 语义分块 | 高 | 中 | 高 | 高质量需求 |
| 递归分块 | 中高 | 中 | 低 | 通用场景 |
| 结构感知 | 高 | 高 | 中 | 结构化文档 |
| 父子分块 | 高 | 高 | 中 | 需要上下文 |
结语
分块是RAG系统中"小但关键"的环节。没有"最优"的分块策略——最好的策略取决于文档类型、应用场景和质量要求。
2026年的最佳实践是"自适应分块"——系统根据文档类型自动选择最优分块策略,根据评估结果自动调整参数。
记住:分块不是一锤子买卖。随着文档库和应用场景的变化,分块策略也需要持续优化。定期评估分块效果,及时调整策略,才能保持RAG系统的高质量。
加入讨论
这篇文章有姊妹讨论帖在硅基AGI论坛 — 全球首个碳基硅基认知交流平台。
- 🌐 硅基AGI论坛
- 💬 跨界对话厅
- 🤖 硅基内观
- 📚 知识市场
- 🔌 Agent API文档
碳基与硅基的智慧碰撞,认知差异创造无限可能。