纲要
- 文档切分在 RAG 中的核心作用
- 四种主流切分策略
- 基于长度切分:固定 token 数分割
- 基于文本架构切分:保留段落与句子边界
- 基于文档格式切分:解析 Markdown、JSON 结构
- 基于语义切分:利用模型判定语义边界
- LangChain 切分器速览
CharacterTextSplitterRecursiveCharacterTextSplitterMarkdownHeaderTextSplitterSemanticChunker(实验性)
- 完整可运行代码:使用
RecursiveCharacterTextSplitter进行本地演示
引言
在 RAG(检索增强生成)系统中,文档加载之后紧接着就是文本切分。这一步直接决定了向量检索的命中精度以及大模型能够获得的上下文质量。如果切分过粗,容易超出上下文窗口;如果切分过细,又会破坏语义完整性。LangChain 提供了一套层次化的切分工具,从最直接的按长度切割,到智能的语义边界检测,满足不同场景的需求。本文将梳理四种核心切分策略,并提供可直接运行的本地代码示例。
为什么必须切分文档?
- 模型上下文窗口有限:无论 GPT‑4 还是本地模型,都有最大 token 限制,长文档必须拆分。
- 提升检索精准度:将长文分割成语义相对独立的片段后,向量相似性搜索更容易命中实际相关的段落,而不是一整页。
- 控制嵌入成本:嵌入模型通常按 token 计费,合理的片段大小可以平衡成本与效果。
四种切分策略详解
基于长度切分
最简单的方式:指定一个固定长度(如 100 token),按此切割并允许相邻片段重叠一部分内容,防止信息被切断。CharacterTextSplitter 搭配分词器(如 tiktoken)即可实现。
python
from langchain_text_splitters import CharacterTextSplitter
splitter = CharacterTextSplitter.from_tiktoken_encoder(
encoding_name="cl100k_base",
chunk_size=100,
chunk_overlap=20
)
基于文本架构切分
RecursiveCharacterTextSplitter 是 LangChain 最推荐的通用切分器。它按优先级依次尝试用双换行、单换行、句号、逗号、空格等分隔符进行切割,尽可能维持段落和句子的完整性。这是一种折中的方案,兼顾长度限制与语义保留。
python
from langchain_text_splitters import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter(
chunk_size=100,
chunk_overlap=20,
separators=["\n\n", "\n", "。", ",", " ", ""]
)
基于文档格式切分
对于 Markdown、JSON 等结构化文档,可以利用文档本身的标记进行切割。
- Markdown :
MarkdownHeaderTextSplitter根据#、##等标题层级拆分,保留标题元数据。 - JSON :
JsonSplitter按对象或数组元素拆分,保证每个片段仍为合法 JSON。
基于语义切分(实验性)
SemanticChunker 通过计算相邻句子的嵌入向量相似度,在相似度低于阈值处划定边界。这种方式能最大程度保证片段语义独立,但目前处于实验阶段,需要额外调用嵌入模型,适合对切分质量要求极高的场景。
完整可运行代码
以下代码使用 RecursiveCharacterTextSplitter 对一段模拟产品手册进行切分,并输出每个片段的长度和内容。整个过程无需任何外部 API,可直接在本地运行。
环境准备:
bash
pip install langchain langchain-text-splitters
核心代码:
python
from langchain_core.documents import Document
from langchain_text_splitters import RecursiveCharacterTextSplitter
# 1. 构造示例文档
raw_text = """
产品名称:智能温控水杯 X-200
功能描述:
1. 实时显示水温,精确到 ±0.5°C。
2. 支持手机 App 远程设置目标温度。
3. 内置 5000mAh 电池,可续航 48 小时。
4. 采用 316 不锈钢内胆,安全健康。
5. 具有过热保护功能,超过 99°C 自动断电。
使用说明:
- 首次使用前请用清水冲洗内胆。
- 长按电源键 3 秒开机,蓝牙自动配对。
- 可通过 App 调节温度范围:40°C~100°C。
注意事项:
- 请勿将杯体浸入水中清洗。
- 充电时请使用 5V/2A 适配器。
"""
doc = Document(page_content=raw_text.strip(), metadata={"source": "product_manual"})
# 2. 创建切分器
splitter = RecursiveCharacterTextSplitter(
chunk_size=100, # 每段最大字符数
chunk_overlap=20, # 段间重叠字符数
separators=["\n\n", "\n", "。", ",", " ", ""]
)
# 3. 切分并展示结果
split_docs = splitter.split_documents([doc])
print(f"原始长度:{len(raw_text)} 字符,切分为 {len(split_docs)} 个片段:\n")
for i, d in enumerate(split_docs, 1):
print(f"片段 {i}({len(d.page_content)} 字符):")
print(d.page_content)
print("------")
运行后可以看到,文本按自然段落切分,每个片段长度基本控制在 100 字符以内,段落之间保留适当重叠,语义连续性得以保持。
切分器选择建议
| 场景 | 推荐切分器 | 说明 |
|---|---|---|
| 通用文本、快速原型 | RecursiveCharacterTextSplitter |
兼顾长度与语义,适用性最广 |
| 高度结构化文档(Markdown) | MarkdownHeaderTextSplitter |
保留标题层次,便于检索元数据 |
| JSON 数据 | JsonSplitter |
按对象/数组拆分,保持结构完整 |
| 极高质量要求 | SemanticChunker(实验性) |
利用嵌入模型判断语义边界 |
总结
文档切分是 RAG 流水线中看似简单却极为重要的一环。RecursiveCharacterTextSplitter 作为通用型切分器,能够满足大多数场景的需求;对于结构化文档,可选择专用切分器;若需极致语义完整性,再考虑实验性的 SemanticChunker。
合理设置 chunk_size 和 chunk_overlap,配合合适的 Loader 和后续的向量化步骤,将为你的 RAG 应用打下坚实基础。