# 文档转换器与字符分割器组件的使用
文档加载器读取到 Document 后,通常还不能直接写入向量库。原始文档可能存在内容过长、格式不统一、语言不一致、噪声过多、metadata 不完整等问题。
因此,在 RAG 数据入库前,经常需要执行文档转换。文档转换的输入是 Document 列表,输出仍然是 Document 列表,只是内容、结构或 metadata 被处理过。

常见转换动作包括:
- 文档切分。
- 文档合并。
- 文档过滤。
- 文档翻译。
- HTML 转文本。
- 内容压缩。
- metadata 标记。
- 关键信息抽取。
其中,文本分割器是最常见的一类文档转换器。
# DocumentTransformer
LangChain 中的文档转换器基类是 BaseDocumentTransformer。它的核心方法是:
| 方法 | 作用 |
|---|---|
transform_documents() | 同步转换文档列表 |
atransform_documents() | 异步转换文档列表 |
从职责上看,DocumentTransformer 不关心文档从哪里来,也不关心最终写入哪个向量库。它只负责把传入的 Document 列表转换成新的 Document 列表。
这让 RAG 入库链路可以拆成:
Loader -> Document -> Transformer -> Splitter -> Embedding -> VectorStore

# TextSplitter
TextSplitter 可以看作一类特殊的 DocumentTransformer。它负责把长文本拆成更小的 chunk,让后续向量检索更加稳定。
如果不做切分,长文档直接入库会带来几个问题:
- 文档太长,向量表达会被大量无关内容稀释。
- 检索命中后,放进 Prompt 的内容太大,浪费 token。
- 长文档可能超过模型上下文限制。
- 答案所在位置被大量上下文包围,模型难以定位。
如果切得太碎,也会有问题:
- 每个 chunk 信息不足。
- 原本完整的语义被切断。
- 相邻 chunk 缺少上下文。
所以分割器的核心目标不是“切得越小越好”,而是让 chunk 足够短、足够完整、足够适合召回。
# 字符分割器示例
字符分割器可以根据指定分隔符切分文本,再按照 chunk 大小和重叠长度合并成文档片段。
示例代码:
from langchain_community.document_loaders import UnstructuredMarkdownLoader
from langchain_text_splitters import CharacterTextSplitter
# 1. 加载对应的文档
loader = UnstructuredMarkdownLoader("./项目API文档.md")
documents = loader.load()
# 2. 构建文本分割器
text_splitter = CharacterTextSplitter(
separator="\n\n",
chunk_size=500,
chunk_overlap=50,
add_start_index=True,
)
# 3. 分割文本
chunks = text_splitter.split_documents(documents)
for chunk in chunks:
print(f"块大小:{len(chunk.page_content)}, 元数据:{chunk.metadata}")
print(len(chunks))
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
这段代码中:
| 参数 | 作用 |
|---|---|
separator | 优先使用的文本分隔符 |
chunk_size | 每个 chunk 的目标大小 |
chunk_overlap | 相邻 chunk 的重叠长度 |
add_start_index | 是否在 metadata 中记录起始位置 |
add_start_index=True 很重要。它会在 metadata 中记录 chunk 在原文中的起始位置,后续做引用、回溯和问题排查时很有用。
# 切分后的 metadata
分割器处理 Document 时,通常会继承原始 Document 的 metadata,并追加切分相关字段。例如原始文档中有 source,切分后每个 chunk 仍然应该保留 source。
如果启用 add_start_index=True,metadata 中还会出现 start_index,用于表示该 chunk 在原文中的起始字符位置。
示例:
{
"source": "./项目API文档.md",
"start_index": 6579,
}
2
3
4
这类 metadata 对生产排查非常关键。比如用户反馈答案不对,可以根据 source 和 start_index 回到原始文档定位上下文。
# 最新版 LangChain 用法提示
最新版 LangChain 中,文本分割器已经拆到 langchain_text_splitters 包中。通用文本场景下,官方更推荐 RecursiveCharacterTextSplitter,因为它会优先保留段落、句子、词等自然结构。
字符分割器仍然可以使用,但更适合格式非常稳定、明确知道分隔符的文本。例如日志块、固定格式段落、双换行分段的 Markdown。
更推荐的通用写法:
from langchain_text_splitters import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200,
add_start_index=True,
)
chunks = text_splitter.split_documents(documents)
2
3
4
5
6
7
8
9
如果明确要按双换行切分,可以继续使用 CharacterTextSplitter:
from langchain_text_splitters import CharacterTextSplitter
text_splitter = CharacterTextSplitter(
separator="\n\n",
chunk_size=500,
chunk_overlap=50,
add_start_index=True,
)
2
3
4
5
6
7
8
最新版写法中,重点不是“必须换掉 CharacterTextSplitter”,而是要根据文本结构选择分割器:通用文本优先递归字符分割器,结构明确时可以用字符分割器。
# 拓展
文档转换器不只有切分。实际 RAG 入库前还可以加入多种转换:
- 清洗转换:去掉空白、页眉页脚、导航噪声。
- 过滤转换:删除过短、无意义或重复文档。
- 翻译转换:把多语言内容统一成目标语言。
- 摘要转换:为长文档生成摘要字段。
- 问答转换:把陈述型内容转换成问答形式。
- metadata 增强:增加分类、标签、权限字段。
这些转换可以串联起来,但要控制复杂度。每加一个转换步骤,都应该能解释它解决什么问题,并能回放转换前后的效果。
# 常见问题
# chunk_size 应该设置多大?
没有固定答案。常见做法是从 500 到 1200 字符或相应 token 范围开始试,结合文档类型、Embedding 模型、LLM 上下文长度和评估集调整。
# chunk_overlap 越大越好吗?
不是。重叠可以保留上下文,但过大会导致重复内容变多、索引膨胀、检索结果重复。一般先设置为 chunk_size 的 10% 到 20%,再根据效果调整。
# 为什么切分后检索结果重复?
可能是 overlap 太大,也可能是原文重复、chunk 太小、top_k 过高。可以降低 overlap、去重、使用 MMR 或增加重排序。
# 面试题
# DocumentTransformer 和 TextSplitter 的关系是什么?
DocumentTransformer 是文档转换器的抽象,TextSplitter 可以看作一种特殊的文档转换器,用于把长 Document 切分成多个小 Document。
# CharacterTextSplitter 和 RecursiveCharacterTextSplitter 有什么区别?
CharacterTextSplitter 主要按指定分隔符切分;RecursiveCharacterTextSplitter 会按分隔符优先级递归切分,尽量保留段落、句子等自然结构。
# 为什么 RAG 入库前必须切分文档?
因为长文档直接入库会降低向量表达质量,也会让检索结果过长、噪声过多。合理切分能提高召回质量和上下文利用效率。
# 生产问题排查
| 问题 | 常见原因 | 处理方式 |
|---|---|---|
| 检索不到答案 | chunk 太大或切断关键语义 | 调整 chunk_size,增加 overlap,换递归分割器 |
| 检索结果重复 | overlap 太大、原文重复、top_k 过高 | 降低 overlap,去重,使用 MMR |
| 答案缺少上下文 | chunk 太小或没有重叠 | 增大 chunk_size 或 chunk_overlap |
| 无法回溯原文 | metadata 缺少 source/start_index | 开启 add_start_index,保留 source |
| token 成本过高 | chunk 太大、top_k 太高 | 控制 chunk_size 和 top_k |
| 表格内容被切乱 | 普通文本分割器不理解表格 | 对表格单独解析或使用结构化切分策略 |