LangChain 08:文本切分器

前言

长文档无法整段塞进模型上下文,检索也需要更小的语义单元。
文本切分器(Text Splitter) 把字符串或 Document 切成带重叠的块,供嵌入与 RAG 使用。
本文对齐常见教程结构:先弄清三个核心方法,再讲按字符、递归、Token、语义、HTML/Markdown/代码等策略。
整链概要见《LangChain 06:RAG 概要》;文档如何读入见《LangChain 07:文档加载器》。
切分实现在 langchain-text-splitters,不要再从旧路径 langchain.text_splitter 导入。
下文需要 Python 3.12+,依赖用 uv 管理。
各示例复制到项目根目录后执行 uv run python xxx.py 即可。

依赖

建议使用 Python 3.12 及以上。
uv 初始化工程并声明基础依赖(版本请按项目实际调整)。
Token、语义分块等额外包写在对应示例前的安装代码块中。

1
2
3
4
uv init langchain-text-splitters-demo
cd langchain-text-splitters-demo
uv venv --python 3.12
uv add langchain-text-splitters langchain-core rich

若已在空目录内初始化,也可写 uv init --name langchain-text-splitters-demo,再执行 uv venvuv add

后续运行示例脚本时,可直接 uv run python demo.py,一般不必手动 activate 虚拟环境。
若习惯激活,Windows 使用 .venv\Scripts\activate,macOS / Linux 使用 source .venv/bin/activate

rich 用于查看块数、预览与 metadata。
示例里常用 from rich import print as rprint

实现

三个核心方法

各切分器都围绕下面三个入口(递归实现细节由子类完成)。

  1. split_text(text: str) -> list[str]:切纯字符串,返回字符串列表(抽象方法,子类实现)。
  2. create_documents(texts: list[str], ...) -> list[Document]:对每个字符串调用 split_text,再封装为 Document
  3. split_documents(documents) -> list[Document]:取出各 Documentpage_content,再走 create_documents,并尽量保留原 metadata。

RAG 里最常见的是:Loader 得到 list[Document]split_documents → 向量库。

按字符切分

CharacterTextSplitter 只认 一个 separator(默认 "\n\n")。
适合分隔规则极简单、结构很规整的文本。

下面演示 separator="" 时按长度硬切(禁用分隔符优先)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from langchain_text_splitters import CharacterTextSplitter
from rich import print as rprint

text = (
"LangChain 是一个用于开发由语言模型驱动的应用程序的框架。"
"它提供了一套工具和抽象,使开发者能够更容易地构建复杂的应用程序。"
)

splitter = CharacterTextSplitter(
chunk_size=50,
chunk_overlap=5,
separator="",
)
chunks = splitter.split_text(text)

for i, chunk in enumerate(chunks):
rprint(f"块 {i + 1}: 长度 {len(chunk)}")
rprint(chunk)

指定中文句号为分隔符时,会 优先在句号处切开,再考虑 chunk_size

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from langchain_text_splitters import CharacterTextSplitter
from rich import print as rprint

text = "这是一个示例文本啊。我们将使用CharacterTextSplitter将其分割成小块。分割基于字符数。"

splitter = CharacterTextSplitter(
chunk_size=30,
chunk_overlap=5,
separator="。",
)
chunks = splitter.split_text(text)

for i, chunk in enumerate(chunks):
rprint(f"块 {i + 1}: 长度 {len(chunk)}")
rprint(chunk)

separator 优先原则可概括为:

  1. 先尽量在分隔符处断开,保持句子完整。
  2. 若单段已超过 chunk_size 且中间没有分隔符,可能得到「超长块」警告,overlap 也可能失效。
  3. chunk_overlap 主要在「合并后的相邻块」之间生效;没有可合并片段时 overlap 往往看不出来。

带重叠、按句号切的示意:

1
2
3
4
5
6
7
8
9
10
11
from langchain_text_splitters import CharacterTextSplitter
from rich import print as rprint

text = "这是第一段文本。这是第二段内容。最后一段结束。"
splitter = CharacterTextSplitter(
chunk_size=20,
chunk_overlap=8,
separator="。",
)
for i, chunk in enumerate(splitter.split_text(text)):
rprint(f"块 {i + 1}: 长度 {len(chunk)} -> {chunk}")

一般项目更推荐下一节的递归切分;只有分隔规则极简单时再用本类。

递归字符切分

RecursiveCharacterTextSplitter 是默认首选:按分隔符列表从粗到细递归下降(段落 → 行 → 句/词)。
chunk_size / chunk_overlap字符数 计。

split_text 看切块效果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from langchain_text_splitters import RecursiveCharacterTextSplitter
from rich import print as rprint

text = (
"LangChain框架特性\n\n"
"多模型集成(GPT/Claude)\n"
"记忆管理功能\n"
"链式调用设计。文档分析场景示例:需要处理PDF/Word等格式。"
)

splitter = RecursiveCharacterTextSplitter(
chunk_size=20,
chunk_overlap=6,
add_start_index=True,
)
chunks = splitter.split_text(text)

for i, chunk in enumerate(chunks):
rprint(f"块 {i + 1}: 长度 {len(chunk)} -> {chunk}")

create_documents:入参是字符串列表,返回带可选 start_indexDocument

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from langchain_text_splitters import RecursiveCharacterTextSplitter
from rich import print as rprint

text = (
"LangChain框架特性\n\n"
"多模型集成(GPT/Claude)\n记忆管理功能\n"
"链式调用设计。文档分析场景示例:需要处理PDF/Word等格式。"
)
splitter = RecursiveCharacterTextSplitter(
chunk_size=20,
chunk_overlap=0,
add_start_index=True,
)
docs = splitter.create_documents([text])

for d in docs:
rprint(d)

split_documents:衔接加载器结果,保留 source / page 等 metadata。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from langchain_core.documents import Document
from langchain_text_splitters import RecursiveCharacterTextSplitter
from rich import print as rprint

docs = [
Document(
page_content=(
"文档加载器把文件变成 Document。"
"文本切分器再把 page_content 切成多块。"
"每块仍带有原始 source 等 metadata。"
),
metadata={"source": "guide.txt", "page": 0},
)
]

splitter = RecursiveCharacterTextSplitter(
chunk_size=40,
chunk_overlap=8,
add_start_index=True,
)
chunks = splitter.split_documents(docs)

for c in chunks:
rprint(c.metadata, c.page_content)

中文场景可把 。!? 放进 separators,减少句中硬切。

按 Token 切分

模型限额常以 Token 计;可用 TokenTextSplitterCharacterTextSplitter.from_tiktoken_encoder
需安装 tiktoken

1
uv add tiktoken

TokenTextSplitter 示例(编码名请按目标模型核对;cl100k_base 常见于 OpenAI 系):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from langchain_text_splitters import TokenTextSplitter
from rich import print as rprint

text = (
"人工智能是一个强大的开发框架。它支持多种语言模型和工具链。"
"人工智能是指通过计算机程序模拟人类智能的一门科学。"
"自20世纪50年代诞生以来,人工智能经历了多次起伏。"
)

splitter = TokenTextSplitter(
encoding_name="cl100k_base",
chunk_size=33,
chunk_overlap=0,
)
chunks = splitter.split_text(text)

rprint(f"共 {len(chunks)} 块")
for i, chunk in enumerate(chunks):
rprint(f"块 {i + 1}: 字符长 {len(chunk)} -> {chunk}")

也可按 Token 计量、仍用字符分隔符优先:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from langchain_text_splitters import CharacterTextSplitter
from rich import print as rprint

text = (
"人工智能是一个强大的开发框架。它支持多种语言模型和工具链。"
"今天天气很好,想出去踏青。但是又比较懒不想出去,怎么办。"
)

splitter = CharacterTextSplitter.from_tiktoken_encoder(
encoding_name="cl100k_base",
chunk_size=18,
chunk_overlap=0,
separator="。",
keep_separator=False,
)
chunks = splitter.split_text(text)

for i, chunk in enumerate(chunks):
rprint(f"块 {i + 1}: {chunk}")

方舟等厂商编码可能不同;上线前用实测 tokenizer 校准 chunk_size

语义分块

SemanticChunker 按句子嵌入相似度找断点,适合长文按「话题切换」切开。
依赖 langchain-experimental 与真实嵌入模型(演示勿用随机 Fake 嵌入)。

1
uv add langchain-experimental langchain-openai python-dotenv

下面示意对接 OpenAI 兼容嵌入端点;请换成你已开通的嵌入模型名与 Key(ark-code-latest 不能当嵌入用)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import os

from dotenv import load_dotenv
from langchain.embeddings import init_embeddings
from langchain_experimental.text_splitter import SemanticChunker
from rich import print as rprint

load_dotenv()

text = """
人工智能综述。人工智能是计算机科学的重要分支。
机器学习通过数据学习规律。深度学习在图像与语音上取得突破。
自然语言处理使计算机理解人类语言。计算机视觉处理图像与视频。
医疗与金融是常见应用领域。发展中仍面临隐私与偏见等挑战。
""".strip()

embeddings = init_embeddings(
"openai:你的嵌入模型名",
api_key=os.environ["OPENAI_API_KEY"],
base_url=os.environ["OPENAI_BASE_URL"],
)

splitter = SemanticChunker(
embeddings=embeddings,
breakpoint_threshold_type="percentile",
breakpoint_threshold_amount=65.0,
sentence_split_regex=r"(?<=[。?!])\s*",
)
docs = splitter.create_documents([text])

rprint(len(docs))
for d in docs:
rprint(d.page_content)

阈值越低通常切得越碎;中文务必配置合适的 sentence_split_regex

HTML 标题切分

对 HTML 字符串可按 h1 / h2 / h3 切,并把标题写入 metadata。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
from langchain_text_splitters import HTMLHeaderTextSplitter
from rich import print as rprint

html = """
<!DOCTYPE html>
<html><body>
<div>
<h1>欢迎来到文档站</h1>
<p>这里介绍 RAG 流程。</p>
<div>
<h2>加载与切分</h2>
<p>先加载再切分再入库。</p>
<h3>本地文件</h3>
<p>支持 txt、pdf、docx 等。</p>
</div>
</div>
</body></html>
"""

splitter = HTMLHeaderTextSplitter(
headers_to_split_on=[
("h1", "标题1"),
("h2", "标题2"),
("h3", "标题3"),
]
)
docs = splitter.split_text(html)
rprint(docs)

代码切分

源码应按语言边界切,避免函数从中间断开。
RecursiveCharacterTextSplitter.from_language

先查看支持的语言枚举:

1
2
3
4
from langchain_text_splitters import Language
from rich import print as rprint

rprint([e.value for e in Language])

Python 示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from langchain_text_splitters import Language, RecursiveCharacterTextSplitter
from rich import print as rprint

code = """
def hello_world():
print("Hello, World!")

def hello_world1():
print("Hello, World1!")
"""

splitter = RecursiveCharacterTextSplitter.from_language(
language=Language.PYTHON,
chunk_size=50,
chunk_overlap=0,
)
docs = splitter.create_documents([code])
rprint(docs)

Markdown 切分

两种常见做法。

  1. MarkdownHeaderTextSplitter:按标题层级切,标题进入 metadata(推荐知识库)。
  2. MarkdownTextSplitter:按 Markdown 分隔习惯做长度切分。

标题切分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from langchain_text_splitters import MarkdownHeaderTextSplitter
from rich import print as rprint

md = """# 总览
这是总览段落。

## 安装
用 uv 安装依赖。

## 使用
先加载再切分。
"""

docs = MarkdownHeaderTextSplitter(
headers_to_split_on=[("#", "h1"), ("##", "h2")],
).split_text(md)

for d in docs:
rprint(d.metadata, d.page_content)

按长度的 Markdown 切分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from langchain_text_splitters import MarkdownTextSplitter
from rich import print as rprint

md = """# 一级标题

这是一级标题下的内容

## 二级标题

- 二级下列表项1
- 二级下列表项2
"""

splitter = MarkdownTextSplitter(chunk_size=30, chunk_overlap=0)
docs = splitter.create_documents([md])

for i, d in enumerate(docs):
rprint(f"分块 {i + 1}: {d.page_content}")

单节仍很长时,可对 MarkdownHeaderTextSplitter 的结果再套一层 RecursiveCharacterTextSplitter

参数怎么选

  1. 先对齐嵌入/模型输入上限,再设 chunk_size(字符切分要换算)。
  2. chunk_overlap 常见取 chunk_size 的 10%~20%。
  3. 中文优先保证句号等分隔符生效;超长无分隔片段会出现超长块警告。
  4. 需要「同文可复现检索」的本地演示,用确定性假嵌入;语义分块必须用真实嵌入。
  5. 用真实问答集抽查:相关句是否完整落在某一块。

验证

  1. 跑「三个核心方法」相关示例:split_text / create_documents / split_documents 返回类型符合预期。
  2. 跑「按字符切分」带 separator="。",块边界应落在句号附近。
  3. 跑「递归字符切分」的 split_documentssource 仍保留。
  4. (可选)安装 tiktoken 后跑 Token 切分;有嵌入额度时再试语义分块。

总结

  1. 先掌握 split_text / create_documents / split_documents 的分工。
  2. 默认用 RecursiveCharacterTextSplitter;单分隔符场景再用 CharacterTextSplitter
  3. 模型限额紧时用 Token 切分;话题型长文可试 SemanticChunker
  4. HTML / Markdown / 代码用结构感知切分,少在标签或函数中间切断。
  5. 切分之后进入《LangChain 09:文档嵌入》与入库、重排专文。