在将文档存入向量数据库之前,必须将其切分成更小的块。这不仅是为了适配模型的上下文限制,更是为了让检索到的内容更具"语义相关性"。
1. 为什么切分很困难?
简单的按字符数切分往往会破坏信息的完整性。
• 问题 A:一个句子可能在中间被切断,导致语义丢失。
• 问题 B:段落的上下文关系被剥离。
核心目标:保持具有相似语义的文本块在一起。
2. 切分的核心参数
在 LangChain 的所有切分器中,有两个关键参数:
1. Chunk Size (块大小):每个文本块包含的字符数(或 Token 数)。
2. Chunk Overlap (块重叠):相邻两个块之间共有的字符数。
* 作用:通过重叠来保持上下文的连续性,防止重要信息在切分点丢失。
3. 常见的切分器类型
A. CharacterTextSplitter (字符切分器)
最基础的切分器,按指定的字符(如 \\n\\n)进行切分。
缺点:非常生硬,容易忽略文本的层级结构。
B. RecursiveCharacterTextSplitter (递归字符切分器) —— 推荐使用
这是最常用的通用切分器。它尝试按一组字符(如 ["\\n\\n", "\\n", " ", ""])递归地切分。
• 逻辑:先尝试按段落切,如果块太大,再按句子切,最后按单词切。
• 优势:尽可能地保持段落和句子的完整。
C. TokenTextSplitter (Token 切分器)
按照模型实际处理的 Token 数量进行切分。
适用场景:当你需要严格控制发送给 LLM 的 Token 成本和限制时。
D. MarkdownHeaderTextSplitter (Markdown 标题切分器)
根据 Markdown 的层级标题(#,##,###)来切分。
优势:它能将标题信息自动加入到每个块的 Metadata 中,让模型知道这个块属于哪个章节。
4. 代码实现示例
以下展示了递归切分器的基本用法:
from langchain.text_splitter import RecursiveCharacterTextSplitter
# 1. 准备长文本
text = "..."
# 2. 初始化切分器
splitter = RecursiveCharacterTextSplitter(
chunk_size=1000, # 每个块 1000 字符
chunk_overlap=150, # 前后重叠 150 字符
separators=["\\n\\n", "\\n", "(?<=\\. )", " ", ""] # 优先级排序 )
# 3. 执行切分
chunks = splitter.split_text(text)
print(f"切分后的块数量: {len(chunks)}")
5. 关键点总结
特性
描述
语义维持
优先在自然换行处切分,比硬性数字切分更智能。
重叠的价值
就像滑窗一样,确保第一块结尾和第二块开头的信息是连贯的。
Metadata 传递
切分器通常会保留原始文档的 Metadata,并为每个块生成新的位置信息。
6. 进阶建议
1. 根据内容选工具:如果是代码,使用 Language.PYTHON 对应的切分器;如果是文档,使用递归切分器。
2. 可视化检查:在正式存入数据库前,打印几个块看看切分点是否合理(是否切断了核心论点)。
3. 结合 Metadata:使用 MarkdownHeaderTextSplitter 可以在检索时帮助模型区分"背景介绍"和"核心结论"。
注:文档切分是 RAG 系统优化的"杠杆点"。合适的切分策略能显著减少模型的幻觉。
夜雨聆风