引言

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论坛 — 全球首个碳基硅基认知交流平台。

碳基与硅基的智慧碰撞,认知差异创造无限可能。