导读:RAG系统效果不好,80%的问题出在分块上。固定长度一刀切把答案劈成两半,语义分块又慢又贵。这篇把7种主流分块策略摊开对比——从最简单的固定长度到最新的Agent分块,每种策略的原理、代码、适用场景和踩坑点全讲清楚,帮你找到最适合自己业务的分块方案。
一、为什么分块是RAG的天花板?
先讲一个真实案例。企业内部制度问答RAG,用户问"数据留存政策是什么",系统召回了两段:一段讲"数据留存7年",另一段讲"员工留存激励"。两段都带"留存",向量相似度都很高,但第二段完全无关。模型把两段揉在一起,输出了一份"数据留存7年且员工留存激励"的离谱答案。
问题出在哪?分块把"数据留存政策"这个完整主题劈成了两半,上半截在chunk A里,下半截在chunk B里。检索时只召回了一个chunk,上下文不完整,模型就开始编。
分块策略直接决定三件事:
- 检索精度:chunk太大噪声多,太小缺上下文
- 召回率:边界切不好,答案被劈成两半就搜不全
- 生成质量:给模型的上下文是否完整、相关
二、七种分块策略全对比
| 策略 | 原理 | 速度 | 精度 | 复杂度 | 推荐度 |
|---|---|---|---|---|---|
| 固定长度分块 | 按字数/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.io或pdfplumber做预处理。
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_size | 300-800 tokens | 事实问答偏小(256),章节总结偏大(1024) |
| chunk_overlap | 50-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,成本增加。适合对准确率要求极高的场景。