本文是 refine-rag 系列教程的第六篇,我们来学习一下什么是切块,应该怎么样去切块?
本文所有代码都在:https://github.com/zonezoen/refine-rag
前言
前面花费了挺多时间去学习怎么读取数据的,那么在读取数据之后,我们要做的操作就是切块,那么什么是切块?为什么要切块呢?往下看
什么是切块?为什么要切块?
简单来说,切块就是把“长篇大论”切成“语义胶囊”。
它把成千上万字的文章,按照一定的逻辑(比如每 500 字一段,或者按段落)切分成一个个独立的小块。这样,AI 在处理信息时,就不需要每次都“读全集”,而是“精准点餐”。
那为什么要切块的?有以下几点原因:
- 大模型的上下文有 token 的长度限制
- 切块可以突出表示某个向量的特征,如果说某个大文本的向量能表示10个特征,那么这个向量表达就会很模糊,会影响检索精度
- 嵌入模型有限制,大多数主流的 Embedding 模型(如 text-embedding-3-small)通常只能处理 512 或 8192 个 Token
- 省钱,大模型 api 是按 toekn 收费的,省时间,上下文长度越长,大模型处理的时间越长
怎么切块?
目前常用的切块方法有 5 种,下面我们逐一介绍:
1. 固定切块(CharacterTextSplitter)
最简单的切块方式,按固定字符数分割文本。这种切块方式很简单,但是缺点也很明显:很容易切断语义完整性,比如:一个句子被切分成了两个块,那么这个句子的语义就会丢失。
文件名: 01-固定切块.py- from langchain_community.document_loaders import TextLoader
- from langchain_text_splitters import CharacterTextSplitter
- loader = TextLoader("../99-doc-data/黑悟空/黑悟空wiki.txt")
- data = loader.load()
- text_splitter = CharacterTextSplitter(
- chunk_size=100, # 每个块的大小
- chunk_overlap=5 # 块之间的重叠大小
- )
- chunks = text_splitter.split_documents(data)
- for chunk in chunks:
- print("====== 切块分页 ======")
- print(chunk.page_content)
复制代码 参数说明:
- chunk_size=100:每个块最多 100 个字符
- chunk_overlap=5:相邻块之间重叠 5 个字符(避免信息丢失)
优点:
缺点:
适用场景: 结构简单的文本、需要快速处理的场景
2. 递归切块(RecursiveCharacterTextSplitter)
按优先级递归尝试不同的分隔符,保持文本结构。
文件名: 02-递归切块.py- from langchain_community.document_loaders import TextLoader
- from langchain_text_splitters import RecursiveCharacterTextSplitter
- loader = TextLoader("../99-doc-data/黑悟空/黑悟空wiki.txt")
- data = loader.load()
- # 定义分割符列表,按优先级依次使用
- separators = [
- "\n\n", # 双换行符(段落分隔)
- "\n", # 单换行符(行分隔)
- "。", # 中文句号
- ".", # 英文句号
- "!", # 中文感叹号
- "!", # 英文感叹号
- "?", # 中文问号
- "?", # 英文问号
- ";", # 中文分号
- ";", # 英文分号
- ",", # 中文逗号
- ",", # 英文逗号
- " ", # 空格
- "" # 最后按字符分割
- ]
- recursive_text_splitter = RecursiveCharacterTextSplitter(
- chunk_size=100,
- chunk_overlap=5,
- separators=separators,
- length_function=len
- )
- r_chunks = recursive_text_splitter.split_documents(data)
- for chunk in r_chunks:
- print("====== 递归切块分页 ======")
- print(chunk.page_content)
复制代码 工作原理:
- 先尝试用 \n\n(段落)分割
- 如果块还是太大,用 \n(行)分割
- 如果还是太大,用 。(句号)分割
- 依此类推,直到满足 chunk_size
优点:
- 保持文本结构(段落、句子)
- 比固定切块更智能
- 适用范围广
缺点:
适用场景: 普通文章、博客、没有明确标题的文本
3. 代码切块(Language-specific Splitter)
专门为代码设计的切块器,保持代码结构完整。
文件名: 03-代码切块.py- from langchain_text_splitters import Language, RecursiveCharacterTextSplitter
- GAME_CODE = """
- class CombatSystem:
- def __init__(self):
- self.health = 100
- self.stamina = 100
-
- def update(self, delta_time):
- self._update_stats(delta_time)
- self._handle_combat()
- class InventorySystem:
- def __init__(self):
- self.items = {}
- self.capacity = 20
-
- def add_item(self, item_id, quantity):
- if item_id in self.items:
- self.items[item_id] += quantity
- else:
- self.items[item_id] = quantity
- """
- python_splitter = RecursiveCharacterTextSplitter.from_language(
- language=Language.PYTHON,
- chunk_size=1000,
- chunk_overlap=0
- )
- py_docs = python_splitter.create_documents([GAME_CODE])
- for i, chunk in enumerate(py_docs, 1):
- print(f"\n--- 第 {i} 个代码块 ---")
- print(chunk.page_content)
复制代码 支持的主流的20多种语言,工作原理: 按类、函数、方法等代码结构分割,保持代码的完整性和可读性
优点:
- 保持代码结构完整
- 不会在函数中间断开
- 支持多种编程语言
缺点:
适用场景: 代码文档、技术教程、API 文档
4. 语义切块(SemanticChunker)
最智能的切块方式,根据语义相似度分割文本。
文件名: 04-LangChain-语义分块-DeepSeek.py- from langchain_community.document_loaders import TextLoader
- from langchain_experimental.text_splitter import SemanticChunker
- from langchain_huggingface import HuggingFaceEmbeddings
- # 1. 加载文档
- loader = TextLoader("../99-doc-data/黑悟空/黑悟空wiki.txt", encoding="utf-8")
- docs = loader.load()
- # 2. 设置嵌入模型(用于计算语义相似度)
- embeddings = HuggingFaceEmbeddings(
- model_name="BAAI/bge-small-zh-v1.5",
- model_kwargs={'device': 'cpu'},
- encode_kwargs={'normalize_embeddings': True}
- )
- # 3. 创建语义分块器
- semantic_splitter = SemanticChunker(
- embeddings=embeddings,
- breakpoint_threshold_type="percentile", # 使用百分位数阈值
- breakpoint_threshold_amount=90 # 90% 不相似度时分割
- )
- # 4. 执行语义分块
- semantic_chunks = semantic_splitter.split_documents(docs)
- for i, chunk in enumerate(semantic_chunks, 1):
- print(f"\n--- 第 {i} 个语义块 ---")
- print(f"长度: {len(chunk.page_content)} 字符")
- print(f"内容: {chunk.page_content[:200]}...")
复制代码 工作原理:首先将文本按句子分割,然后计算相邻句子的语义相似度(使用 embedding),如果相似度低于阈值时,创建新的分块
示例对比:
传统分块(可能在句子中间断开):- 块1: "黑神话悟空是一款动作游戏。游戏基于西游"
- 块2: "记改编。主角是孙悟空的转世。"
复制代码 语义分块(保持语义完整):- 块1: "黑神话悟空是一款动作游戏。游戏基于西游记改编。" # 游戏介绍
- 块2: "主角是孙悟空的转世。" # 角色介绍
复制代码 参数说明:
- breakpoint_threshold_type="percentile":使用百分位数阈值(推荐)
- breakpoint_threshold_amount=90:90% 不相似度时分割
- 85:块很多(细粒度)
- 90:块适中(推荐)
- 95:块较少(粗粒度)
优点:
缺点:
- 速度慢(比传统分块慢 50 倍)
- 需要 embedding 模型
适用场景: 问答系统、长文档分析、高质量检索
5. 按段落和标题切块(最推荐)
最实用的切块方式,按文档的自然结构(标题、段落)分割。
文件名: 05-按段落标题切块.py
2.1 Markdown 标题切块
- from langchain_text_splitters import MarkdownHeaderTextSplitter
- markdown_document = """
- # 黑神话:悟空
- ## 游戏简介
- 《黑神话:悟空》是一款动作角色扮演游戏。
- ## 游戏玩法
- ### 战斗系统
- 游戏的战斗系统流畅爽快。
- ### 技能系统
- 玩家可以学习72变等经典技能。
- """
- # 定义要按哪些标题级别切块
- headers_to_split_on = [
- ("#", "一级标题"), # H1
- ("##", "二级标题"), # H2
- ("###", "三级标题"), # H3
- ]
- # 创建 Markdown 标题切块器
- markdown_splitter = MarkdownHeaderTextSplitter(
- headers_to_split_on=headers_to_split_on,
- strip_headers=False # 保留标题在内容中
- )
- # 执行切块
- md_chunks = markdown_splitter.split_text(markdown_document)
- for chunk in md_chunks:
- print(f"内容: {chunk.page_content}")
- print(f"元数据: {chunk.metadata}") # 包含标题层级信息
复制代码 输出示例:- 内容: # 黑神话:悟空
- 元数据: {'一级标题': '黑神话:悟空'}
- 内容: ## 游戏简介
- 《黑神话:悟空》是一款动作角色扮演游戏。
- 元数据: {'一级标题': '黑神话:悟空', '二级标题': '游戏简介'}
- 内容: ### 战斗系统
- 游戏的战斗系统流畅爽快。
- 元数据: {'一级标题': '黑神话:悟空', '二级标题': '游戏玩法', '三级标题': '战斗系统'}
复制代码 2.2 段落切块
- from langchain_text_splitters import RecursiveCharacterTextSplitter
- # 段落之间用双换行符分隔的文本
- paragraph_document = """
- 《黑神话:悟空》是一款动作角色扮演游戏。
- 游戏采用虚幻引擎5开发,画面表现力极强。
- 玩家在游戏中扮演孙悟空的转世。
- """
- # 优先按段落(双换行符)分割
- paragraph_splitter = RecursiveCharacterTextSplitter(
- separators=["\n\n", "\n", "。", " ", ""],
- chunk_size=500,
- chunk_overlap=50
- )
- chunks = paragraph_splitter.split_text(paragraph_document)
复制代码 2.3 混合切块(推荐)
- # 先按标题切块
- md_chunks = markdown_splitter.split_text(markdown_document)
- # 再对大块进行段落切分
- final_chunks = []
- for header_chunk in md_chunks:
- if len(header_chunk.page_content) > 200:
- # 块太大,再按段落切分
- sub_splitter = RecursiveCharacterTextSplitter(
- separators=["\n\n", "\n", "。", " ", ""],
- chunk_size=200,
- chunk_overlap=20
- )
- sub_chunks = sub_splitter.split_text(header_chunk.page_content)
-
- # 保留标题元数据
- for sub_chunk in sub_chunks:
- from langchain_core.documents import Document
- final_chunks.append(Document(
- page_content=sub_chunk,
- metadata=header_chunk.metadata
- ))
- else:
- final_chunks.append(header_chunk)
复制代码 优点:
- 保持逻辑完整性
- 每个块都有明确的主题
- 元数据包含标题信息,便于溯源
缺点:
适用场景: 说明书、论文、技术文档、Markdown 文档
切块方法对比
以下各种切块策略对比,可以根据使用场景来选择,当然了,也可以多种组合使用,比如技术文档,先基于 markdown 分段,然后再使用递归或者代码切块。
切块策略速度质量适用场景推荐度固定切块⚡⚡⚡⭐⭐简单文本、快速处理、要求较低的需求⭐⭐递归切块⚡⚡⚡⭐⭐⭐⭐普通文章、博客、无明显分段的文章⭐⭐⭐⭐代码切块⚡⚡⚡⭐⭐⭐⭐代码文档、技术教程⭐⭐⭐⭐语义切块⚡⭐⭐⭐⭐⭐问答系统、长文档⭐⭐⭐⭐段落标题切块⚡⚡⚡⭐⭐⭐⭐⭐说明书、论文、Markdown、HTML 格式文档⭐⭐⭐⭐⭐学习路径
- 简易RAG 学习
- LCEL 语法学习
- LangChain 读取数据
- LangChain 读取文本数据
- LangChain 读取图片数据
- LangChain 读取 PDF 数据
- LangChain 读取表格数据
- 文本切块
- 向量嵌入
- 向量存储
- 检索前处理
- 索引优化
- 检索后处理
- 响应生成
- 系统评估
项目地址
本文所有代码示例都在 GitHub 开源:
https://github.com/zonezoen/refine-rag
欢迎 Star 和 Fork,一起学习 RAG 技术!
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |