🎯 课程主题
深入掌握 RecursiveCharacterTextSplitter —— LangChain 中最常用的文档切分器,理解其递归多级分隔符机制、拆分+回溯合并两阶段流程。
📝 核心知识点
RecursiveCharacterTextSplitter 概述
- 继承自 TextSplitter,是当前开发中最常用的切分器
- 与 CharacterTextSplitter 的核心区别:支持多级分隔符递归切分
默认分隔符优先级(从高到低)
separators = [
"\n\n", # 1. 段落分隔(优先级最高)
"\n", # 2. 换行
" ", # 3. 空格
"" # 4. 字符级(兜底,保证不超过 chunk_size)
]
递归切分流程
原始文本
│
▼
按 "\n\n"(段落)切分
│
├─ 片段 ≤ chunk_size → 保留不动 ✓
│
└─ 片段 > chunk_size → 按 "\n"(换行)再切
│
├─ 片段 ≤ chunk_size → 保留不动 ✓
│
└─ 片段 > chunk_size → 按 " "(空格)再切
│
├─ 片段 ≤ chunk_size → 保留不动 ✓
│
└─ 片段 > chunk_size → 按字符硬切(兜底)
核心参数
| 参数 | 默认值 | 说明 |
|---|---|---|
chunk_size | 4000 | chunk 的最大字符数 |
chunk_overlap | 200 | chunk 之间的重叠字符数 |
separators | ["\n\n", "\n", " ", ""] | 多级分隔符列表(可自定义) |
length_function | len | 计算文本长度的函数 |
add_start_index | False | 是否在 metadata 中添加 chunk 起始索引 |
拆分 + 回溯合并两阶段
第一阶段:递归拆分(Split)
└── 按 separators 优先级递归切分,得到初步 fragments
第二阶段:回溯合并(Merge Back)
└── 将相邻 fragment 尝试合并:
├─ 合并后 ≤ chunk_size → 合并
├─ 合并后 > chunk_size → 不合并,将当前已合并的作为一个独立 chunk
│ → 在末尾补 chunk_overlap 的内容
│ → 新的 fragment 从剩余部分开始
└─ 最终得到 final_chunks
关键价值
- 语义完整性优先:优先按段落、换行等自然分隔符切分
- 长度兜底控制:最终保证所有 chunk 不超过
chunk_size - 双重保险:兼顾语义 + 长度,不会产生超大或超小 chunk
🏗️ 架构与工作流
递归切分示意(具体案例)
原始文本:
"LangChain框架特性\n包括LLM调用\n和Chain编排\n\n企业级应用\n特征:可扩展性、可靠性"
假设 chunk_size = 10
第一轮(按 \n\n 切):
Part1: "LangChain框架特性\n包括LLM调用\n和Chain编排" (长度 > 10 → 继续切)
Part2: "企业级应用\n特征:可扩展性、可靠性" (长度 > 10 → 继续切)
第二轮(Part1 按 \n 切):
"LangChain框架特性" (11字符 > 10 → 继续切)
"包括LLM调用" (< 10 ✓)
"和Chain编排" (< 10 ✓)
第三轮(按字符硬切 "LangChain框架特性"):
"LangChain框" (10字符)
"架特性" (3字符)
最终 Part1 chunks: ["LangChain框", "架特性", "包括LLM调用", "和Chain编排"]
Part2 同理递归...
经过回溯合并:
"LangChain框" + "架特性" = 13 > 10 → 不合并
"LangChain框" 独立为 chunk1
"架特性" + "包括LLM调用"(+overlap) → 进入合并逻辑...
💻 代码实战
示例一:基础用法(split_text)
from langchain.text_splitter import RecursiveCharacterTextSplitter
text = "LangChain框架特性\n包括LLM调用\n和Chain编排\n\n企业级应用\n特征:可扩展性、可靠性"
splitter = RecursiveCharacterTextSplitter(
chunk_size=10,
chunk_overlap=0,
add_start_index=True
)
chunks = splitter.split_text(text) # 返回 List[str]
for i, chunk in enumerate(chunks):
print(f"--- Chunk {i+1} (len={len(chunk)}) ---")
print(chunk)
示例二:字符串列表输入(create_documents)
texts = [
"第一段示例文本内容",
"第二段示例文本内容"
]
splitter = RecursiveCharacterTextSplitter(
chunk_size=10,
chunk_overlap=0
)
docs = splitter.create_documents(texts) # 返回 List[Document]
for doc in docs:
print(doc.page_content)
示例三:结合文件加载(split_documents,最常用)
from langchain_community.document_loaders import TextLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
loader = TextLoader("./document.txt")
documents = loader.load()
splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=50,
separators=["\n\n", "\n", "。", "!", "?", " "]
)
split_docs = splitter.split_documents(documents)
print(f"共切分为 {len(split_docs)} 个 chunk")
示例四:从磁盘文件直接读取并切分
with open("./骆驼祥子.txt", "r", encoding="utf-8") as f:
text = f.read()
splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=50
)
# 方法一:传入字符串
chunks = splitter.split_text(text)
# 方法二:传入字符串列表
docs = splitter.create_documents([text])
示例五:自定义分隔符
splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=50,
separators=[
"\n\n", # 段落
"\n", # 换行
"。", # 中文句号
"!", # 感叹号
"?", # 问号
",", # 逗号
" ", # 空格
"" # 字符级兜底
]
)
# 如果不传 separators,则使用默认值
# 如果传了 separators,则完全使用自定义值(不合并默认值)
⚠️ 常见问题与避坑指南
separators 是复数形式:注意是
separators(列表)而非separator(单个字符串)。这是与 CharacterTextSplitter 的关键区别。自定义 separators 会完全覆盖默认值:如果手动指定了
separators,LangChain 不会将你的列表与默认列表合并,而是完全使用你的列表。需要在自定义列表中保留兜底的""(字符级切割)。回溯合并阶段的理解:拆分完成后不是立即返回,而是进入合并阶段。合并的目的是:避免出现过多碎片化的小 chunk,在不超过
chunk_size的前提下尽量合并,提高信息密度。chunk_overlap 的生成时机:在回溯合并阶段,当两个 fragment 合并后会超出
chunk_size时,当前已合并的部分会补充chunk_overlap长度的内容(从下一个 fragment 的开头截取),保证上下文连续性。选择 separators 的建议:
- 中文文本:加入
"。"、"!"、"?"、","等中文标点 - 代码文件:考虑
"\n"、" "(缩进)等 - 西文文本:保留默认的
"\n\n"、"\n"、" "即可
- 中文文本:加入
chunk_size 设置建议:
- 嵌入模型限制:要考虑 embedding 模型的 token 上限
- 一般建议 500~1000 字符,中文约等于 250~500 个汉字
- 英文文本可适当调大,因英文字符数与 token 数接近 1:1
💡 个人总结与延伸
RecursiveCharacterTextSplitter 是最佳选择:在 LangChain 生态中,它是最常用、最通用的切分器。递归多级分隔符 + 回溯合并的设计使其能够适应各种文本格式(文章、代码、文档等),兼顾语义完整性和长度控制。
三方法调用链的实际意义:理解
split_documents→create_documents→split_text的调用链后,实际开发中只需记住"Loader 产出的 Document 列表直接传split_documents"这一条路径即可,但在调试验证时可以用split_text快速测试单个字符串的切分效果。与 CharacterTextSplitter 的对比:
| 维度 | CharacterTextSplitter | RecursiveCharacterTextSplitter |
|---|---|---|
| 分隔符 | 单个 separator | 多个 separators(列表) |
| 切割方式 | 按分隔符一次切分 + 合并 | 按分隔符优先级递归切分 + 合并 |
| 超限处理 | 不处理(保留超限片段) | 逐级递归,最终字符级兜底 |
| 适用场景 | 简单文本 | 通用场景(推荐) |
- 进阶方向:对于特定格式的文本(Markdown、HTML、代码),LangChain 还提供了专门的 Splitter(如
MarkdownHeaderTextSplitter、HTMLHeaderTextSplitter、Language::RecursiveCharacterTextSplitter),可以按标题层级或代码语法结构进行切分,效果更佳。