Text Splitters 文本分割器
文本分割器(Text Splitters)用于将长文档分割成更小的块(chunks)。这是构建 RAG(检索增强生成)系统的关键步骤——合适的块大小和分割策略直接影响检索质量和生成效果。
为什么需要文本分割
在 LangChain 的工作流中,文档加载后通常需要分割成小块,原因如下:
- 嵌入限制:嵌入模型有最大输入长度限制(如 512 或 8192 tokens)
- 检索精度:较小的块更容易与用户查询精确匹配
- 上下文窗口:LLM 的上下文窗口有限,需要选择合适的块大小
- 语义完整性:好的分割策略能保持语义单元的完整性
核心概念
文本分割器的工作方式:
文档 → 分割器(根据策略切割)→ 文档块列表所有分割器都有一个共同的基础接口:
python
texts = text_splitter.split_text(text) # 分割字符串
docs = text_splitter.split_documents(documents) # 分割 Document 对象常用分割器
RecursiveCharacterTextSplitter(推荐)
这是最常用的分割器。它递归地尝试按不同分隔符分割文本,从大到小依次尝试,直到块大小满足要求。
python
from langchain_text_splitters import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500, # 每个块的最大字符数
chunk_overlap=50, # 块之间的重叠字符数
length_function=len, # 长度计算函数
separators=[ # 分隔符优先级(从高到低)
"\n\n",
"\n",
"。",
".",
" ",
"",
],
keep_separator=True # 保留分隔符
)
text = """LangChain 是一个强大的框架。
它帮助开发者构建基于 LLM 的应用程序。
文档加载、文本分割、向量存储等功能一应俱全。
在这个示例中,我们将演示如何分割文本。
注意保持语义单元的完整性。"""
chunks = text_splitter.split_text(text)
for i, chunk in enumerate(chunks):
print(f"块 {i+1} ({len(chunk)} 字符): {chunk[:50]}...")TokenTextSplitter
基于 token 数量而非字符数进行分割,更精确地控制 LLM 的输入大小。
python
from langchain_text_splitters import TokenTextSplitter
text_splitter = TokenTextSplitter(
chunk_size=100, # 每个块的最大 token 数
chunk_overlap=10, # 块之间的重叠 token 数
)
docs = text_splitter.create_documents([text])MarkdownHeaderTextSplitter
按 Markdown 标题结构分割,保持文档的结构层次。
python
from langchain_text_splitters import MarkdownHeaderTextSplitter
headers_to_split_on = [
("#", "H1"),
("##", "H2"),
("###", "H3"),
]
splitter = MarkdownHeaderTextSplitter(
headers_to_split_on=headers_to_split_on
)
markdown_text = """
# LangChain 入门指南
## 安装
使用 pip 安装 LangChain。
## 快速开始
### 基本用法
创建一个简单的链。
### 高级用法
使用 AgentExecutor 执行任务。
## 常见问题
FAQ 部分。
"""
docs = splitter.split_text(markdown_text)
for doc in docs:
print(f"标题: {doc.metadata}, 内容预览: {doc.page_content[:40]}...")RecursiveJsonSplitter
用于分割 JSON 数据,保持 JSON 结构的完整性。
python
from langchain_text_splitters import RecursiveJsonSplitter
splitter = RecursiveJsonSplitter(max_chunk_size=300)
json_data = {
"title": "技术文档",
"sections": [
{"heading": "介绍", "content": "...很长的内容..."},
{"heading": "方法", "content": "...很长的内容..."},
]
}
docs = splitter.split_json(json_data, convert_lists=True)CharacterTextSplitter
按字符数简单分割。
python
from langchain_text_splitters import CharacterTextSplitter
text_splitter = CharacterTextSplitter(
separator="\n",
chunk_size=200,
chunk_overlap=20,
)SentenceTransformersTokenTextSplitter
基于 Sentence Transformers 模型的分词器进行分割,确保每个块不超过模型的最大 token 限制。
python
from langchain_text_splitters import SentenceTransformersTokenTextSplitter
splitter = SentenceTransformersTokenTextSplitter(
chunk_overlap=0,
model_name="sentence-transformers/all-mpnet-base-v2",
tokens_per_chunk=256
)
docs = splitter.split_text(text)分割策略选择
| 分割器 | 适用场景 | 优势 |
|---|---|---|
| RecursiveCharacterTextSplitter | 通用文本 | 灵活性高,效果稳定 |
| MarkdownHeaderTextSplitter | Markdown 文档 | 保持文档结构 |
| RecursiveJsonSplitter | JSON 数据 | 保持数据层次 |
| TokenTextSplitter | 精确控制 token 数 | 与 LLM token 计数一致 |
| CharacterTextSplitter | 简单文本 | 实现简单 |
块重叠(Chunk Overlap)
块重叠确保分割边界上下文的连贯性:
python
# 无重叠:可能导致信息丢失
splitter_no_overlap = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=0, # 无重叠
)
# 有重叠:保持上下文连贯
splitter_with_overlap = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=100, # 保留部分上下文
)重叠建议:
- 一般文本:
chunk_overlap = chunk_size * 10% ~ 20% - 技术文档:
chunk_overlap = chunk_size * 15% ~ 25% - 需要强连贯性:
chunk_overlap = chunk_size * 30%
完整示例:RAG 预处理流程
python
from langchain_community.document_loaders import DirectoryLoader, TextLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
# 1. 加载文档
loader = DirectoryLoader(
"./data/",
glob="**/*.txt",
loader_cls=TextLoader,
show_progress=True
)
documents = loader.load()
print(f"加载了 {len(documents)} 个文档")
# 2. 分割文档
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=50,
separators=["\n\n", "\n", "。", ".", " ", ""],
keep_separator=True
)
chunks = text_splitter.split_documents(documents)
print(f"分割为 {len(chunks)} 个块")
# 3. 查看分割结果
for i, chunk in enumerate(chunks[:3]):
print(f"\n--- 块 {i+1} ---")
print(f"来源: {chunk.metadata.get('source', 'unknown')}")
print(f"内容: {chunk.page_content[:100]}...")最佳实践
- 选择合适的块大小:根据嵌入模型和 LLM 的 token 限制选择,通常 500-1000 字符
- 保持语义完整性:优先使用 RecursiveCharacterTextSplitter 以自然分隔符切割
- 合理设置重叠:重叠能保持上下文连贯,但不宜过大(不超过 chunk_size 的 20%)
- 结构化文档优先:Markdown、代码等结构化文档使用专门的分割器
- 测试不同参数:在不同数据集上测试 chunk_size 和 overlap 的效果