前言 长文档无法整段塞进模型上下文,检索也需要更小的语义单元。文本切分器(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-demouv venv --python 3.12 uv add langchain-text-splitters langchain-core rich
若已在空目录内初始化,也可写 uv init --name langchain-text-splitters-demo,再执行 uv venv 与 uv 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。
实现 三个核心方法 各切分器都围绕下面三个入口(递归实现细节由子类完成)。
split_text(text: str) -> list[str]:切纯字符串,返回字符串列表(抽象方法,子类实现)。
create_documents(texts: list[str], ...) -> list[Document]:对每个字符串调用 split_text,再封装为 Document。
split_documents(documents) -> list[Document]:取出各 Document 的 page_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 CharacterTextSplitterfrom rich import print as rprinttext = ( "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 CharacterTextSplitterfrom rich import print as rprinttext = "这是一个示例文本啊。我们将使用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 优先原则 可概括为:
先尽量在分隔符处断开,保持句子完整。
若单段已超过 chunk_size 且中间没有分隔符,可能得到「超长块」警告,overlap 也可能失效。
chunk_overlap 主要在「合并后的相邻块」之间生效;没有可合并片段时 overlap 往往看不出来。
带重叠、按句号切的示意:
1 2 3 4 5 6 7 8 9 10 11 from langchain_text_splitters import CharacterTextSplitterfrom rich import print as rprinttext = "这是第一段文本。这是第二段内容。最后一段结束。" 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 RecursiveCharacterTextSplitterfrom rich import print as rprinttext = ( "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_index 的 Document。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 from langchain_text_splitters import RecursiveCharacterTextSplitterfrom rich import print as rprinttext = ( "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 Documentfrom langchain_text_splitters import RecursiveCharacterTextSplitterfrom rich import print as rprintdocs = [ 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 计;可用 TokenTextSplitter 或 CharacterTextSplitter.from_tiktoken_encoder。 需安装 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 TokenTextSplitterfrom rich import print as rprinttext = ( "人工智能是一个强大的开发框架。它支持多种语言模型和工具链。" "人工智能是指通过计算机程序模拟人类智能的一门科学。" "自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 CharacterTextSplitterfrom rich import print as rprinttext = ( "人工智能是一个强大的开发框架。它支持多种语言模型和工具链。" "今天天气很好,想出去踏青。但是又比较懒不想出去,怎么办。" ) 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 osfrom dotenv import load_dotenvfrom langchain.embeddings import init_embeddingsfrom langchain_experimental.text_splitter import SemanticChunkerfrom rich import print as rprintload_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 HTMLHeaderTextSplitterfrom rich import print as rprinthtml = """ <!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 Languagefrom rich import print as rprintrprint([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, RecursiveCharacterTextSplitterfrom rich import print as rprintcode = """ 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 切分 两种常见做法。
MarkdownHeaderTextSplitter:按标题层级切,标题进入 metadata(推荐知识库)。
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 MarkdownHeaderTextSplitterfrom rich import print as rprintmd = """# 总览 这是总览段落。 ## 安装 用 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 MarkdownTextSplitterfrom rich import print as rprintmd = """# 一级标题 这是一级标题下的内容 ## 二级标题 - 二级下列表项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。
参数怎么选
先对齐嵌入/模型输入上限,再设 chunk_size(字符切分要换算)。
chunk_overlap 常见取 chunk_size 的 10%~20%。
中文优先保证句号等分隔符生效;超长无分隔片段会出现超长块警告。
需要「同文可复现检索」的本地演示,用确定性假嵌入;语义分块必须用真实嵌入。
用真实问答集抽查:相关句是否完整落在某一块。
验证
跑「三个核心方法」相关示例:split_text / create_documents / split_documents 返回类型符合预期。
跑「按字符切分」带 separator="。",块边界应落在句号附近。
跑「递归字符切分」的 split_documents,source 仍保留。
(可选)安装 tiktoken 后跑 Token 切分;有嵌入额度时再试语义分块。
总结
先掌握 split_text / create_documents / split_documents 的分工。
默认用 RecursiveCharacterTextSplitter;单分隔符场景再用 CharacterTextSplitter。
模型限额紧时用 Token 切分;话题型长文可试 SemanticChunker。
HTML / Markdown / 代码用结构感知切分,少在标签或函数中间切断。
切分之后进入《LangChain 09:文档嵌入》与入库、重排专文。