RAG分块策略深度解析|从固定长度到语义分块的实战选型指南

79 阅读16分钟

导读:RAG系统效果不好,80%的问题出在分块上。固定长度一刀切把答案劈成两半,语义分块又慢又贵。这篇把7种主流分块策略摊开对比——从最简单的固定长度到最新的Agent分块,每种策略的原理、代码、适用场景和踩坑点全讲清楚,帮你找到最适合自己业务的分块方案。


一、为什么分块是RAG的天花板?

先讲一个真实案例。企业内部制度问答RAG,用户问"数据留存政策是什么",系统召回了两段:一段讲"数据留存7年",另一段讲"员工留存激励"。两段都带"留存",向量相似度都很高,但第二段完全无关。模型把两段揉在一起,输出了一份"数据留存7年且员工留存激励"的离谱答案。

问题出在哪?分块把"数据留存政策"这个完整主题劈成了两半,上半截在chunk A里,下半截在chunk B里。检索时只召回了一个chunk,上下文不完整,模型就开始编。

分块策略直接决定三件事:

  1. 检索精度:chunk太大噪声多,太小缺上下文
  2. 召回率:边界切不好,答案被劈成两半就搜不全
  3. 生成质量:给模型的上下文是否完整、相关

二、七种分块策略全对比

策略原理速度精度复杂度推荐度
固定长度分块按字数/Token数切,设重叠极快极简⭐⭐ 原型用
递归字符分割段落→换行→句号→逗号依次切简单⭐⭐⭐⭐ 通用首选
文档结构感知按标题/章节/代码块切中等⭐⭐⭐⭐⭐ 结构化文档
语义分块Embedding检测语义边界很高中等⭐⭐⭐⭐ 叙述性文档
父子层级分块小块检索,返回大块上下文很高中等⭐⭐⭐⭐⭐ 生产推荐
Agent分块LLM判断分块边界极慢最高⭐⭐⭐ 高价值文档
Late分块先Embed整篇,再分块⭐⭐⭐⭐ 长文档

三、策略详解与代码实战

3.1 固定长度分块:最快的Baseline

最粗暴也最简单的方案,按固定字符数切一刀。

# 安装所需依赖(如果尚未安装)
pip install -q langchain langchain-text-splitters sentence-transformers pypdf numpy
from langchain_text_splitters import CharacterTextSplitter

# 示例长文本
long_text = '''RAG(Retrieval Augmented Generation)是当前大语言模型应用的主流架构。它通过检索外部知识库中的相关信息来增强模型的回答质量。

Embedding技术是RAG系统的核心组件之一。它将文本转换为高维向量,使得语义相近的文本在向量空间中距离更近。常用的中文Embedding模型包括BAAI/bge-large-zh-v1.5、text2vec-base-chinese等。

向量数据库用于存储和检索文本的向量表示。主流的向量数据库包括Milvus、FAISS、LanceDB、Chroma等。Milvus是功能最全面的生产级向量数据库,FAISS是Meta开源的高性能算法库。

在实际应用中,分块策略的选择对RAG系统的效果影响很大。推荐使用递归字符分割或父子层级分块策略。'''

# 固定长度分块
splitter = CharacterTextSplitter(
    chunk_size=150,      # 每块150字符
    chunk_overlap=30,    # 重叠30字符
    separator="\n"       # 按换行切
)

chunks = splitter.split_text(long_text)
print(f"分块数量: {len(chunks)}")

for i, chunk in enumerate(chunks, 1):
    print(f"\n--- 块 {i} ---\n{chunk}")
         
# 输出:
# 分块数量: 3
# 
# --- 块 1 ---
# RAG(Retrieval Augmented Generation)是当前大语言模型应用的主流架构。它通过检索外部知识库中的相关信息来增强模型的回答质量。
# 
# --- 块 2 ---
# Embedding技术是RAG系统的核心组件之一。它将文本转换为高维向量,使得语义相近的文本在向量空间中距离更近。常用的中文Embedding模型包括BAAI/bge-large-zh-v1.5、text2vec-base-chinese等。
# 
# --- 块 3 ---
# 向量数据库用于存储和检索文本的向量表示。主流的向量数据库包括Milvus、FAISS、LanceDB、Chroma等。Milvus是功能最全面的生产级向量数据库,FAISS是Meta开源的高性能算法库。
# 在实际应用中,分块策略的选择对RAG系统的效果影响很大。推荐使用递归字符分割或父子层级分块策略。

优点:零计算开销,速度最快,代码最简单

缺点:可能从句子中间切断,"90天后删除"脱离了上下文根本不知道删的是什么

⚠️ 踩坑实录:我第一次做RAG Demo就用的这个,结果一份API文档被切成两半,上半段讲"请求参数",下半段讲"响应格式"。用户问"这个接口返回什么",只召回上半段,模型就开始瞎编返回格式。血泪教训:chunk_overlap千万别设0,至少设50-100字符的重叠区。


3.2 递归字符分割:LangChain默认方案

按优先级依次尝试分隔符:段落(\n\n) → 换行(\n) → 句号 → 逗号 → 空格。

from langchain_text_splitters import RecursiveCharacterTextSplitter

splitter = RecursiveCharacterTextSplitter(
    chunk_size=150,
    chunk_overlap=30,
    separators=["\n\n", "\n", "。", "!", "?", ".", " ", ""]
)

chunks = splitter.split_text(long_text)
print(f"分块数量: {len(chunks)}")

for i, chunk in enumerate(chunks, 1):
    print(f"\n--- 块 {i} ---\n{chunk}")
    
# 输出:
# 分块数量: 4
# 
# --- 块 1 ---
# RAG(Retrieval Augmented Generation)是当前大语言模型应用的主流架构。它通过检索外部知识库中的相关信息来增强模型的回答质量。
# 
# --- 块 2 ---
# Embedding技术是RAG系统的核心组件之一。它将文本转换为高维向量,使得语义相近的文本在向量空间中距离更近。常用的中文Embedding模型包括BAAI/bge-large-zh-v1.5、text2vec-base-chinese等。
# 
# --- 块 3 ---
# 向量数据库用于存储和检索文本的向量表示。主流的向量数据库包括Milvus、FAISS、LanceDB、Chroma等。Milvus是功能最全面的生产级向量数据库,FAISS是Meta开源的高性能算法库。
# 
# --- 块 4 ---
# 在实际应用中,分块策略的选择对RAG系统的效果影响很大。推荐使用递归字符分割或父子层级分块策略。

为什么推荐? 它优先在语义边界切分,不会从句子中间劈开。LangChain和LlamaIndex都把它作为默认方案,80%的场景用它就够了

💡 chunk_size选多少? 几个经验值:

  • 128-256 Token:FAQ、短问答,检索精度高
  • 512 Token:通用推荐起点(我大部分项目用这个)
  • 1024+ Token:长文档、需要丰富上下文的场景

3.3 文档结构感知:结构化文档的救星

按文档结构切:Markdown标题、PDF章节、代码函数、表格整块。

from langchain_text_splitters import MarkdownHeaderTextSplitter

# Markdown示例文本
markdown_text = '''# RAG系统简介

RAG(Retrieval Augmented Generation)是当前大语言模型应用的主流架构。

## 核心组件

### Embedding层
将文本转换为高维向量。

### 向量数据库
存储和检索文本的向量表示。

## 应用场景

知识问答、文档检索、客服系统。'''

# Markdown按标题分块
headers_to_split_on = [
    ("#", "Header 1"),
    ("##", "Header 2"),
    ("###", "Header 3"),
]

splitter = MarkdownHeaderTextSplitter(headers_to_split_on)
chunks = splitter.split_text(markdown_text)

# 每个chunk保留标题层级信息
for i, chunk in enumerate(chunks, 1):
    print(f"\n--- 块 {i} ---")
    print(f"元数据: {chunk.metadata}")
    print(f"内容: {chunk.page_content}")
    
# 输出:
# --- 块 1 ---
# 元数据: {'Header 1': 'RAG系统简介'}
# 内容: RAG(Retrieval Augmented Generation)是当前大语言模型应用的主流架构。
# 
# --- 块 2 ---
# 元数据: {'Header 1': 'RAG系统简介', 'Header 2': '核心组件', 'Header 3': 'Embedding层'}
# 内容: 将文本转换为高维向量。
# 
# --- 块 3 ---
# 元数据: {'Header 1': 'RAG系统简介', 'Header 2': '核心组件', 'Header 3': '向量数据库'}
# 内容: 存储和检索文本的向量表示。
# 
# --- 块 4 ---
# 元数据: {'Header 1': 'RAG系统简介', 'Header 2': '应用场景'}
# 内容: 知识问答、文档检索、客服系统。

适用场景:技术文档、API文档、法律合同、产品手册。表格不会被拆散,代码块保持完整。

⚠️ 踩坑提醒:需要好的文档解析器。PDF扫描件结构识别不准,切出来的chunk可能把表格头和数据行分开。推荐用Unstructured.iopdfplumber做预处理。


3.4 语义分块:边界最精准

用Embedding检测语义边界——相邻句子语义相似度骤降时切分。

# 方法一:使用 ModelScope 下载(推荐国内用户)
pip install modelscope

python -c "
from modelscope import snapshot_download
import os
# 下载 Embedding 模型
snapshot_download('Xorbits/bge-large-zh-v1.5', cache_dir='./models')
import re
import numpy as np
from sentence_transformers import SentenceTransformer

# 加载轻量级Embedding模型
model = SentenceTransformer(r'E:\LLM Project\Local Knowledge Base Q&A System\models\Xorbits\bge-large-zh-v1.5')

def split_by_semantics(text, threshold=0.7):
    """语义分块实现"""
    # 1. 先切成句子
    sentences = re.split(r'(?<=[。!?.!?])\s*', text)
    sentences = [s.strip() for s in sentences if s.strip()]
    
    if len(sentences) <= 1:
        return [text]
    
    # 2. 句子向量化
    embeddings = model.encode(sentences, normalize_embeddings=True)
    
    # 3. 计算相邻句子相似度,找边界
    chunks = []
    current_chunk = [sentences[0]]
    
    for i in range(1, len(sentences)):
        similarity = np.dot(embeddings[i], embeddings[i-1])
        
        if similarity < threshold:
            # 语义突变,切分
            chunks.append(''.join(current_chunk))
            current_chunk = [sentences[i]]
        else:
            current_chunk.append(sentences[i])
    
    chunks.append(''.join(current_chunk))
    return chunks

# 测试
semantic_chunks = split_by_semantics(long_text, threshold=0.7)
print(f"语义分块数量: {len(semantic_chunks)}")

for i, chunk in enumerate(semantic_chunks, 1):
    print(f"\n--- 块 {i} ---\n{chunk}")

# 输出:
# 语义分块数量: 10
#
# --- 块 1 ---
# RAG(Retrieval Augmented Generation)是当前大语言模型应用的主流架构。
#
# --- 块 2 ---
# 它通过检索外部知识库中的相关信息来增强模型的回答质量。
#
# --- 块 3 ---
# Embedding技术是RAG系统的核心组件之一。
#
# --- 块 4 ---
# 它将文本转换为高维向量,使得语义相近的文本在向量空间中距离更近。
#
# --- 块 5 ---
# 常用的中文Embedding模型包括BAAI/bge-large-zh-v1.
#
# --- 块 6 ---
# 5、text2vec-base-chinese等。
#
# --- 块 7 ---
# 向量数据库用于存储和检索文本的向量表示。
#
# --- 块 8 ---
# 主流的向量数据库包括Milvus、FAISS、LanceDB、Chroma等。Milvus是功能最全面的生产级向量数据库,FAISS是Meta开源的高性能算法库。
#
# --- 块 9 ---
# 在实际应用中,分块策略的选择对RAG系统的效果影响很大。
#
# --- 块 10 ---
# 推荐使用递归字符分割或父子层级分块策略。

优点:边界最精准,chunk内语义一致性最高 缺点:需要额外Embedding计算,索引速度慢3-10倍

💡 适用场景:长叙述性文档(论文、博客、会议纪要),主题变化不规则的文本。


3.5 父子层级分块:生产环境首选

小块用于精准检索,大块用于给模型提供上下文。

import re
import numpy as np
from sentence_transformers import SentenceTransformer

# 加载轻量级Embedding模型
model = SentenceTransformer(r'E:\LLM Project\Local Knowledge Base Q&A System\models\Xorbits\bge-large-zh-v1.5')

def split_by_semantics(text, threshold=0.7):
    """语义分块实现"""
    # 1. 先切成句子
    sentences = re.split(r'(?<=[。!?.!?])\s*', text)
    sentences = [s.strip() for s in sentences if s.strip()]
    
    if len(sentences) <= 1:
        return [text]
    
    # 2. 句子向量化
    embeddings = model.encode(sentences, normalize_embeddings=True)
    
    # 3. 计算相邻句子相似度,找边界
    chunks = []
    current_chunk = [sentences[0]]
    
    for i in range(1, len(sentences)):
        similarity = np.dot(embeddings[i], embeddings[i-1])
        
        if similarity < threshold:
            # 语义突变,切分
            chunks.append(''.join(current_chunk))
            current_chunk = [sentences[i]]
        else:
            current_chunk.append(sentences[i])
    
    chunks.append(''.join(current_chunk))
    return chunks

# 测试
semantic_chunks = split_by_semantics(long_text, threshold=0.7)
print(f"语义分块数量: {len(semantic_chunks)}")

for i, chunk in enumerate(semantic_chunks, 1):
    print(f"\n--- 块 {i} ---\n{chunk}")

# 输出:
# 语义分块数量: 10
#
# --- 块 1 ---
# RAG(Retrieval Augmented Generation)是当前大语言模型应用的主流架构。
#
# --- 块 2 ---
# 它通过检索外部知识库中的相关信息来增强模型的回答质量。
#
# --- 块 3 ---
# Embedding技术是RAG系统的核心组件之一。
#
# --- 块 4 ---
# 它将文本转换为高维向量,使得语义相近的文本在向量空间中距离更近。
#
# --- 块 5 ---
# 常用的中文Embedding模型包括BAAI/bge-large-zh-v1.
#
# --- 块 6 ---
# 5、text2vec-base-chinese等。
#
# --- 块 7 ---
# 向量数据库用于存储和检索文本的向量表示。
#
# --- 块 8 ---
# 主流的向量数据库包括Milvus、FAISS、LanceDB、Chroma等。Milvus是功能最全面的生产级向量数据库,FAISS是Meta开源的高性能算法库。
#
# --- 块 9 ---
# 在实际应用中,分块策略的选择对RAG系统的效果影响很大。
#
# --- 块 10 ---
# 推荐使用递归字符分割或父子层级分块策略。

为什么这是生产推荐? 它解决了RAG的核心矛盾:

  • 检索要精准 → 小块匹配度高
  • 生成要上下文 → 大块信息完整

⚠️ 实现注意:需要维护父子映射关系。检索到子块后,根据parent_id去取父块内容。存储时子块入向量库,父块另存一份(比如放关系数据库或对象存储)。


3.6 Agent分块:质量天花板

让LLM读完整篇文档,自己判断哪里该切分。

# Agent分块:让LLM来切分文本
import os
from openai import OpenAI

# 配置LLM API(支持兼容OpenAI接口的服务)
client = OpenAI(
    api_key=os.getenv("OPENAI_API_KEY", "your-api-key"),  # 或直接填写
    base_url=os.getenv("OPENAI_BASE_URL", "https://api.deepseek.com")  # 可换国内代理
)

def llm_chunking(text, model="deepseek-chat"):
    """让LLM根据语义边界切分文本"""
    prompt = f"""请将以下文本按语义主题切分成多个chunk。
每个chunk应是一个独立的语义单元,不要从句子中间切开。
用 JSON 数组格式输出,每个元素是一个chunk的文本:

["chunk1内容", "chunk2内容", ...]

文本:
{text}"""
    
    response = client.chat.completions.create(
        model=model,
        messages=[
            {"role": "system", "content": "你是一个专业的文本分块助手,擅长识别文本的语义边界。请只返回JSON数组,不要其他内容。"},
            {"role": "user", "content": prompt}
        ],
        temperature=0.0
    )
    
    result = response.choices[0].message.content
    # 解析返回的JSON
    import json
    chunks = json.loads(result)
    return chunks

# 运行Agent分块
agent_chunks = llm_chunking(long_text)
print(f"Agent分块数量: {len(agent_chunks)}")

for i, chunk in enumerate(agent_chunks, 1):
    print(f"\n--- 块 {i} ---\n{chunk}")
    
# 输出:
# Agent分块数量: 4
# 
# --- 块 1 ---
# RAG(Retrieval Augmented Generation)是当前大语言模型应用的主流架构。它通过检索外部知识库中的相关信息来增强模型的回答质量。
# 
# --- 块 2 ---
# Embedding技术是RAG系统的核心组件之一。它将文本转换为高维向量,使得语义相近的文本在向量空间中距离更近。常用的中文Embedding模型包括BAAI/bge-large-zh-v1.5、text2vec-base-chinese等。
# 
# --- 块 3 ---
# 向量数据库用于存储和检索文本的向量表示。主流的向量数据库包括Milvus、FAISS、LanceDB、Chroma等。Milvus是功能最全面的生产级向量数据库,FAISS是Meta开源的高性能算法库。
# 
# --- 块 4 ---
# 在实际应用中,分块策略的选择对RAG系统的效果影响很大。推荐使用递归字符分割或父子层级分块策略。

优点:质量最高,边界最合理 缺点:10-50倍成本,索引一篇文档可能要几毛钱

💡 适用场景:法律合同、医疗指南、金融监管文件——准确比成本重要的场景。


3.7 Late分块:长文档新思路

先Embed整篇文档(用长上下文模型),再在Embedding层面分块。

# Late分块核心思想
# 1. 用长上下文模型(如GPT-4 128k)Embed整篇文档
# 2. 得到每个token的embedding(带上下文)
# 3. 按token位置分组,形成chunk

# 目前主流框架支持有限,概念性代码:
from transformers import AutoModel

model = AutoModel.from_pretrained("jinaai/jina-embeddings-v2-base-zh", trust_remote_code=True)

# 整篇文档输入,获取token-level embeddings
tokens_embeddings = model.encode(text, output_tokens=True)

# 按窗口分组(每512个token一组)
chunk_size = 512
chunks = []
for i in range(0, len(tokens_embeddings), chunk_size):
    chunk_emb = tokens_embeddings[i:i+chunk_size]
    # 池化得到chunk-level embedding
    chunks.append(chunk_emb.mean(axis=0))

优点:每个chunk的embedding都包含了整篇文档的上下文信息 缺点:需要长上下文Embedding模型,计算成本高


四、分块策略选型决策树

开始
  │
  ├─ 文档类型?
  │    ├─ Markdown/结构化文档 → 文档结构感知分块
  │    ├─ 代码/API文档 → 代码感知分块(按函数/类切)
  │    ├─ 法律/医疗/金融 → Agent分块(质量优先)
  │    └─ 普通文本/混合内容 → 继续
  │
  ├─ 数据规模?
  │    ├─ <1万条 → 语义分块(质量优先)
  │    └─ >1万条 → 继续
  │
  ├─ 是否需要上线生产?
  │    ├─ 是 → 父子层级分块(推荐)
  │    └─ 否 → 递归字符分割(够用)
  │
  └─ 预算/延迟敏感?
       ├─ 敏感 → 递归字符分割 + 调overlap
       └─ 不敏感 → 语义分块或Late分块

五、分块调优实战参数表

参数建议值调优方向
chunk_size300-800 tokens事实问答偏小(256),章节总结偏大(1024)
chunk_overlap50-150 tokens解决上下文断裂,过大会增加重复
语义分块阈值0.7越高chunk越大,越低越碎
父子分块-子块128-256 tokens检索精度优先
父子分块-父块512-1024 tokens上下文完整优先

六、高级技巧:元数据增强分块

分块时带上元数据,检索和生成时都能用上:

from datetime import datetime

# 分块时提取元数据
doc_name = "rag_intro.txt"
section_title = "RAG系统介绍"
page_num = 1

splitter = RecursiveCharacterTextSplitter(chunk_size=200, chunk_overlap=30)
chunks = splitter.split_documents([Document(page_content=long_text, metadata={})])

# 增强元数据
for i, chunk in enumerate(chunks):
    chunk.metadata.update({
        "source": doc_name,           # 来源文档
        "section": section_title,     # 所属章节
        "page": page_num,             # 页码
        "doc_type": "policy",         # 文档类型
        "created_at": datetime.now().strftime("%Y-%m-%d"),  # 创建时间
        "chunk_index": i,             # 块序号
    })

# 查看结果
print("元数据增强后的分块:")
for i, chunk in enumerate(chunks[:2]):
    print(f"\n块 {i} 元数据: {chunk.metadata}")
    print(f"块 {i} 内容: {chunk.page_content[:80]}...")

# 输出:
# 元数据增强后的分块:
#
# 块 0 元数据: {'source': 'rag_intro.txt', 'section': 'RAG系统介绍', 'page': 1, 'doc_type': 'policy', 'created_at': '2026-06-07', 'chunk_index': 0}
# 块 0 内容: RAG(Retrieval Augmented Generation)是当前大语言模型应用的主流架构。它通过检索外部知识库中的相关信息来增强模型的回答质量。...
#
# 块 1 元数据: {'source': 'rag_intro.txt', 'section': 'RAG系统介绍', 'page': 1, 'doc_type': 'policy', 'created_at': '2026-06-07', 'chunk_index': 1}
# 块 1 内容: Embedding技术是RAG系统的核心组件之一。它将文本转换为高维向量,使得语义相近的文本在向量空间中距离更近。常用的中文Embedding模型包括BAAI/...

元数据的妙用

  • 过滤检索where("doc_type = 'policy' AND created_at > '2024-01-01'")
  • 溯源标注:生成答案时标注"引用自《数据留存政策》第3页"
  • 时间排序:优先返回最新版本的文档

七、ChunkRAG:基于LLM的块级过滤新思路

最近看到一篇有意思的论文ChunkRAG,思路是在检索后、生成前,用LLM对每个召回的chunk做相关性评分,过滤掉不相关的块。

# ChunkRAG核心流程
# 1. 语义分块 → 2. 向量检索Top-20 → 3. LLM评分过滤 → 4. 生成

def chunk_filter(query, chunks, threshold=0.7):
    """用LLM评估每个chunk与查询的相关性"""
    scores = []
    for chunk in chunks:
        prompt = f"""评估以下文档片段与用户问题的相关性。
问题:{query}
片段:{chunk}

请输出0-1的相关性分数(1表示完全相关,0表示无关)。只输出数字。"""
        
        response = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[{"role": "user", "content": prompt}]
        )
        score = float(response.choices[0].message.content.strip())
        scores.append(score)
    
    # 过滤低分chunk
    filtered = [c for c, s in zip(chunks, scores) if s >= threshold]
    return filtered

# 使用
top_chunks = vector_search(query, k=20)
filtered_chunks = chunk_filter(query, top_chunks)
answer = generate(query, filtered_chunks)

论文数据显示,ChunkRAG在PopQA数据集上准确率64.9%,比标准RAG提升10个百分点。代价是每个查询要多调几次LLM,成本增加。适合对准确率要求极高的场景。