Langchain文本切割器在RAG中的使用

Langchain文本切割器在RAG中的使用

文本切割器使用原理与api

用户:

为什么要进行父子块切分

元宝:

父子块切分的核心目的是平衡检索的精确度与上下文的完整性

为什么需要这种策略?

传统切分方式要么切得太粗(一个大块包含很多内容,检索时容易带进噪声),要么切得太细(小块太碎片化,丢失上下文语义,LLM难以理解)。

父子块切分通过两层结构来解决:

层级

大小

用途

父块

较大(如 512~1024 tokens)

保存完整段落或章节的语义,作为最终喂给 LLM 的上下文

子块

较小(如 128~256 tokens)

用于向量检索,粒度更细,更容易命中用户问题的精准片段

工作流程

  1. 文档先切成若干父块,每个父块再进一步切成多个子块
  2. 建立索引时,只对子块做向量化存储,同时记录每个子块所属的父块 ID。
  3. 检索时,用用户 query 去匹配最相关的子块,然后找到对应的父块,把整个父块作为上下文交给 LLM。

带来的好处

  • 检索更准:小粒度的子块能更精确地定位到答案所在的那句话或那个段落,避免被大块中的无关内容干扰。
  • 上下文更全:LLM 拿到的不是孤立的子句,而是完整的父块(段落/小节),包含了必要的背景信息、逻辑衔接,回答质量更高。
  • 节省 token:不需要把整个大文档都塞进去,只取命中的父块即可。

举个例子

假设有一篇论文的摘要部分:

“我们提出了一种新方法。该方法基于注意力机制。实验表明效果提升了15%。”

如果只切一个父块(整段),检索“效果提升多少”时,子块可能是“实验表明效果提升了15%”,命中了,但返回给 LLM 的是整个父块(包括前面两句),LLM 就能理解“什么方法提升了15%”。

如果只用单层细切,检索到“实验表明效果提升了15%”后,只给 LLM 这一句,它不知道是哪个方法,就会产生幻觉或答非所问。

这就是父子块切分的价值所在——让检索找得准,让模型看得懂

用户:

递归切分器的作用,为什么要使用递归切分器

元宝:

递归切分器的作用

递归切分器(RecursiveCharacterTextSplitter)是一种分层级、自适应的文本分割策略。它的核心思想是:

从最大粒度的分隔符开始尝试切分,如果切出来的块仍然超过指定大小,就换下一个更小的分隔符继续递归切分,直到所有块都满足长度限制。

常见的分隔符优先级(由大到小):

  1. 段落分隔符(\n\n
  2. 行分隔符(\n
  3. 句子结束符(.!?
  4. 单词分隔符(空格)
  5. 字符级别(兜底)

为什么要使用递归切分器?

相比最简单的固定字符数切分(例如每 500 个字符一刀切),递归切分器有以下几个关键优势:

1. 尽可能保留语义完整性

固定长度切分很容易把一句话拦腰截断,导致一个 chunk 末尾是半句话,下一个 chunk 开头是后半句。递归切分器优先在段落、句子等自然边界处切分,最大程度保证每个 chunk 的语义相对独立

2. 自适应不同文档的结构

有的文档用双换行分段,有的用单换行,有的用句号结尾。递归切分器不假定一种固定的分隔符,而是从最自然的边界(段落)逐步降级到最细的边界(字符),对各种格式的文档都有较好的适应性

3. 避免产生过小或过大的碎片

固定长度切分可能因为文档中存在超长段落而产出超出限制的 chunk,或者因为大量短行而产出很多极小的 chunk。递归切分器通过递归调整,使得每个 chunk 都在目标大小附近,且边界合理

4. 配合向量检索更有效

RAG 系统中,chunk 的质量直接影响检索精度。语义完整的 chunk 能让 embedding 更好地捕捉其含义,查询时也更容易命中正确的内容。递归切分器产出的 chunk 边界干净,减少了“跨 chunk 的语义依赖”。

对比示例

假设有一段文本:

第一章 引言 本研究旨在探索…… 首先,我们回顾了相关工作。 然后,提出了新方法。 最后,给出了实验结果。
  • 固定长度切分(chunk_****size=50):可能会在第2行中间切断,得到“本研究旨在探索……首先,我们回”和“顾了相关工作。然后,提出了新方法。”——两个 chunk 都包含不完整的信息。
  • 递归切分器:优先用\n\n切出“第一章 引言”作为一个 chunk;再用\n切剩下的段落;若仍超长,会在句号处切分。结果每个 chunk 都是完整的段落或句子。

什么时候不适合用递归切分器?

  • 文档本身已经是结构化良好的小单元(如 Markdown 标题下的短段落),直接用MarkdownHeaderTextSplitter更合适。
  • 需要对代码或特定格式做语法感知切分(如 Python 函数级别),应使用专门的PythonCodeTextSplitter等。
  • 文档非常短,无需切分。

但在大多数通用文本场景下,递归切分器是 LangChain 推荐的默认分割器,也是平衡效率与质量的最佳选择。

用户:

1:TextLoader/UnstructuredLoad - 加载文本文件

2:CharacterTextSplitter - 按照字符分隔符切分文件内容(测试)

3:RecursiveCharacterTextSplitter - 递归降级切分器(项目中使用)

4:SemanticChunker - 语义切分器

5:MarkdownHeaderTextSplitter - Markdown切分器

告诉我在rag的实际项目中,这些方法各自适合在什么场景下使用,并举例给出使用前和使用后的用例变化

元宝:

在 RAG 实际项目中,文档加载和切分是决定检索质量的关键环节。你列出的五个组件分别面向不同的文档形态和业务需求,下面逐一说明它们最适合的场景,并给出使用前后的直观变化。


1. TextLoader / UnstructuredLoader —— 加载文本文件

适用场景

  • TextLoader:纯文本文件(.txt),编码简单,无特殊格式。
  • UnstructuredLoader:多格式文件(PDF、DOCX、HTML、PPTX、图片 OCR 等),需要自动提取文本、表格、元数据。

使用前:原始磁盘文件(如一份 PDF 合同)。

使用后:LangChain 的Document对象列表,每个对象包含page_content(文本内容)和metadata(文件名、页码、修改时间等)。

示例

  • 使用前:contract.pdf(二进制文件,不可直接处理)。
  • 使用后:
[ Document(page_content="第一条 双方权利义务...", metadata={"source": "contract.pdf", "page": 1}), Document(page_content="第二条 违约责任...", metadata={"source": "contract.pdf", "page": 2}) ]

在 RAG 中,这一步是所有后续操作的前提,UnstructuredLoader 因其强大的格式兼容性成为首选。


2. CharacterTextSplitter —— 按字符分隔符切分(测试)

适用场景

  • 快速原型验证、单元测试。
  • 文档结构极其简单(如纯段落用\n\n分隔),且段落长度均匀。
  • 不适合生产:因为它只按一个固定分隔符或固定字符数硬切,极易破坏语义边界。

使用前:一段连续文本。

使用后:按指定分隔符(如\n\n)或固定长度(如chunk_size=200)切成的片段。

示例

  • 使用前:
今天天气很好。我们去公园散步。晚上回家做饭。
  • 使用后(separator="。",chunk_size=50):
["今天天气很好。", "我们去公园散步。", "晚上回家做饭。"]

但如果句子长度不均,会出现截断:“今天天气很好。我们去公”——后半句丢失。

一般只在调试阶段临时使用,正式项目不建议。


3. RecursiveCharacterTextSplitter —— 递归降级切分器(项目主力)

适用场景

  • 绝大多数通用文档:新闻、报告、小说、邮件、聊天记录等。
  • 文档结构不统一,既有段落又有短句,没有固定分隔符。
  • 生产环境首选:它从["\n\n", "\n", " ", ""]依次尝试,优先在段落、句子、单词边界处切分,最大限度保留语义完整性。

使用前:一整段混合结构的文本。

使用后:每个 chunk 都在目标长度(如 500 tokens)附近,且边界落在自然停顿处。

示例

  • 使用前(一段产品说明书):
产品概述 本设备采用铝合金外壳,坚固耐用。 主要功能包括:温度调节、定时开关、智能联动。 请勿在潮湿环境下使用。
  • 使用后(chunk_size=100,chunk_overlap=20):
Chunk 1: "产品概述\n\n本设备采用铝合金外壳,坚固耐用。" Chunk 2: "本设备采用铝合金外壳,坚固耐用。\n主要功能包括:温度调节、定时开关、智能联动。" Chunk 3: "主要功能包括:温度调节、定时开关、智能联动。\n请勿在潮湿环境下使用。"

注意:重叠保证了上下文连续性,且没有在句子中间硬切。

在 LangChain 官方推荐和多数 RAG 教程中,RecursiveCharacterTextSplitter 是默认切分器。


4. SemanticChunker —— 语义切分器

适用场景

  • 文档缺乏明显分隔符,但内容存在主题或语义转折(如学术论文的方法部分、技术博客的连续叙述)。
  • 需要按语义边界而非字符长度切分,使每个 chunk 代表一个独立的语义单元。
  • 对检索精度要求极高,愿意付出额外计算成本(需要调用 embedding 模型判断断点)。

使用前:一段连续、无明显空行的技术论述。

使用后:根据句子嵌入向量的余弦距离,在语义变化较大的位置断开。

示例

  • 使用前:
梯度下降是一种迭代优化算法。它通过沿负梯度方向更新参数来最小化损失函数。学习率控制每一步的步长。如果学习率过大,可能导致震荡甚至发散。另一种常用优化器是Adam,它结合了动量和自适应学习率。
  • 使用后(语义断点可能在“学习率控制...”之前或“另一种常用优化器...”之前):
Chunk 1: "梯度下降是一种迭代优化算法。它通过沿负梯度方向更新参数来最小化损失函数。" Chunk 2: "学习率控制每一步的步长。如果学习率过大,可能导致震荡甚至发散。" Chunk 3: "另一种常用优化器是Adam,它结合了动量和自适应学习率。"

每个 chunk 聚焦一个子话题,检索“Adam”时只会命中第三个 chunk,不会引入无关的梯度下降细节。

适用于知识密集、需要高精度检索的场景(如法律条文、医学文献)。


5. MarkdownHeaderTextSplitter —— Markdown 切分器

适用场景

  • 文档是Markdown 格式(README、Wiki、API 文档、Notion 导出等)。
  • 需要保留标题层级,使每个 chunk 知道自己的上下文(如属于哪个一级标题、二级标题)。
  • 检索时可以按标题过滤或显示层级路径。

使用前:完整的.md文件。

使用后:按标题层级(######)切分,每个 chunk 的metadata中记录标题链。

示例

  • 使用前(一个 API 文档片段):
# 快速开始 ## 安装 运行 `pip install mylib` ## 配置 设置环境变量 `API_KEY`
  • 使用后(headers_to_split_on=[("#", "h1"), ("##", "h2")]):
Chunk 1: content: "运行 `pip install mylib`" metadata: {"h1": "快速开始", "h2": "安装"} Chunk 2: content: "设置环境变量 `API_KEY`" metadata: {"h1": "快速开始", "h2": "配置"}

检索“如何安装”时,不仅能拿到正确内容,还能知道它属于“快速开始 > 安装”,方便 LLM 组织回答。

如果你的知识库本身就是 Markdown(如 GitBook、Obsidian),MarkdownHeaderTextSplitter 是最佳选择。


在 RAG 项目中如何搭配

步骤

推荐工具

原因

加载

UnstructuredLoader

支持 PDF/Office/HTML/图片等,通用性强

切分(通用)

RecursiveCharacterTextSplitter

平衡语义完整性与灵活性,生产首选

切分(Markdown)

MarkdownHeaderTextSplitter

保留标题结构,提升检索上下文

切分(高精度)

SemanticChunker

按语义转折切分,适合专业领域

切分(测试)

CharacterTextSplitter

快速验证,不上生产

实际项目中往往组合使用:先用 UnstructuredLoader 加载,再根据文档类型选择 RecursiveCharacterTextSplitter 或 MarkdownHeaderTextSplitter,必要时叠加 SemanticChunker 做二次精切。