找回密码
 立即注册
首页 业界区 业界 006:RAG 入门-面试官问你,RAG 为什么要切块? ...

006:RAG 入门-面试官问你,RAG 为什么要切块?

梦霉 4 小时前
本文是 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
  1. from langchain_community.document_loaders import TextLoader
  2. from langchain_text_splitters import CharacterTextSplitter
  3. loader = TextLoader("../99-doc-data/黑悟空/黑悟空wiki.txt")
  4. data = loader.load()
  5. text_splitter = CharacterTextSplitter(
  6.     chunk_size=100,      # 每个块的大小
  7.     chunk_overlap=5      # 块之间的重叠大小
  8. )
  9. chunks = text_splitter.split_documents(data)
  10. for chunk in chunks:
  11.     print("====== 切块分页 ======")
  12.     print(chunk.page_content)
复制代码
参数说明:

  • chunk_size=100:每个块最多 100 个字符
  • chunk_overlap=5:相邻块之间重叠 5 个字符(避免信息丢失)
优点:

  • 实现简单,速度快
  • 块大小可控
缺点:

  • 容易在句子中间断开
  • 破坏语义完整性
适用场景: 结构简单的文本、需要快速处理的场景
2. 递归切块(RecursiveCharacterTextSplitter)

按优先级递归尝试不同的分隔符,保持文本结构。
文件名: 02-递归切块.py
  1. from langchain_community.document_loaders import TextLoader
  2. from langchain_text_splitters import RecursiveCharacterTextSplitter
  3. loader = TextLoader("../99-doc-data/黑悟空/黑悟空wiki.txt")
  4. data = loader.load()
  5. # 定义分割符列表,按优先级依次使用
  6. separators = [
  7.     "\n\n",  # 双换行符(段落分隔)
  8.     "\n",    # 单换行符(行分隔)
  9.     "。",    # 中文句号
  10.     ".",     # 英文句号
  11.     "!",    # 中文感叹号
  12.     "!",     # 英文感叹号
  13.     "?",    # 中文问号
  14.     "?",     # 英文问号
  15.     ";",    # 中文分号
  16.     ";",     # 英文分号
  17.     ",",    # 中文逗号
  18.     ",",     # 英文逗号
  19.     " ",     # 空格
  20.     ""       # 最后按字符分割
  21. ]
  22. recursive_text_splitter = RecursiveCharacterTextSplitter(
  23.     chunk_size=100,
  24.     chunk_overlap=5,
  25.     separators=separators,
  26.     length_function=len
  27. )
  28. r_chunks = recursive_text_splitter.split_documents(data)
  29. for chunk in r_chunks:
  30.     print("====== 递归切块分页 ======")
  31.     print(chunk.page_content)
复制代码
工作原理:

  • 先尝试用 \n\n(段落)分割
  • 如果块还是太大,用 \n(行)分割
  • 如果还是太大,用 。(句号)分割
  • 依此类推,直到满足 chunk_size
优点:

  • 保持文本结构(段落、句子)
  • 比固定切块更智能
  • 适用范围广
缺点:

  • 仍可能在不合适的地方断开
适用场景: 普通文章、博客、没有明确标题的文本
3. 代码切块(Language-specific Splitter)

专门为代码设计的切块器,保持代码结构完整。
文件名: 03-代码切块.py
  1. from langchain_text_splitters import Language, RecursiveCharacterTextSplitter
  2. GAME_CODE = """
  3. class CombatSystem:
  4.    def __init__(self):
  5.        self.health = 100
  6.        self.stamina = 100
  7.    
  8.    def update(self, delta_time):
  9.        self._update_stats(delta_time)
  10.        self._handle_combat()
  11. class InventorySystem:
  12.    def __init__(self):
  13.        self.items = {}
  14.        self.capacity = 20
  15.    
  16.    def add_item(self, item_id, quantity):
  17.        if item_id in self.items:
  18.            self.items[item_id] += quantity
  19.        else:
  20.            self.items[item_id] = quantity
  21. """
  22. python_splitter = RecursiveCharacterTextSplitter.from_language(
  23.     language=Language.PYTHON,
  24.     chunk_size=1000,
  25.     chunk_overlap=0
  26. )
  27. py_docs = python_splitter.create_documents([GAME_CODE])
  28. for i, chunk in enumerate(py_docs, 1):
  29.     print(f"\n--- 第 {i} 个代码块 ---")
  30.     print(chunk.page_content)
复制代码
支持的主流的20多种语言,工作原理: 按类、函数、方法等代码结构分割,保持代码的完整性和可读性
优点:

  • 保持代码结构完整
  • 不会在函数中间断开
  • 支持多种编程语言
缺点:

  • 仅适用于代码
适用场景: 代码文档、技术教程、API 文档
4. 语义切块(SemanticChunker)

最智能的切块方式,根据语义相似度分割文本。
文件名: 04-LangChain-语义分块-DeepSeek.py
  1. from langchain_community.document_loaders import TextLoader
  2. from langchain_experimental.text_splitter import SemanticChunker
  3. from langchain_huggingface import HuggingFaceEmbeddings
  4. # 1. 加载文档
  5. loader = TextLoader("../99-doc-data/黑悟空/黑悟空wiki.txt", encoding="utf-8")
  6. docs = loader.load()
  7. # 2. 设置嵌入模型(用于计算语义相似度)
  8. embeddings = HuggingFaceEmbeddings(
  9.     model_name="BAAI/bge-small-zh-v1.5",
  10.     model_kwargs={'device': 'cpu'},
  11.     encode_kwargs={'normalize_embeddings': True}
  12. )
  13. # 3. 创建语义分块器
  14. semantic_splitter = SemanticChunker(
  15.     embeddings=embeddings,
  16.     breakpoint_threshold_type="percentile",  # 使用百分位数阈值
  17.     breakpoint_threshold_amount=90           # 90% 不相似度时分割
  18. )
  19. # 4. 执行语义分块
  20. semantic_chunks = semantic_splitter.split_documents(docs)
  21. for i, chunk in enumerate(semantic_chunks, 1):
  22.     print(f"\n--- 第 {i} 个语义块 ---")
  23.     print(f"长度: {len(chunk.page_content)} 字符")
  24.     print(f"内容: {chunk.page_content[:200]}...")
复制代码
工作原理:首先将文本按句子分割,然后计算相邻句子的语义相似度(使用 embedding),如果相似度低于阈值时,创建新的分块
示例对比:
传统分块(可能在句子中间断开):
  1. 块1: "黑神话悟空是一款动作游戏。游戏基于西游"
  2. 块2: "记改编。主角是孙悟空的转世。"
复制代码
语义分块(保持语义完整):
  1. 块1: "黑神话悟空是一款动作游戏。游戏基于西游记改编。"  # 游戏介绍
  2. 块2: "主角是孙悟空的转世。"  # 角色介绍
复制代码
参数说明:

  • breakpoint_threshold_type="percentile":使用百分位数阈值(推荐)
  • breakpoint_threshold_amount=90:90% 不相似度时分割

    • 85:块很多(细粒度)
    • 90:块适中(推荐)
    • 95:块较少(粗粒度)

优点:

  • 保持语义连贯性
  • 自动识别主题边界
  • 检索质量最高
缺点:

  • 速度慢(比传统分块慢 50 倍)
  • 需要 embedding 模型
适用场景: 问答系统、长文档分析、高质量检索
5. 按段落和标题切块(最推荐)

最实用的切块方式,按文档的自然结构(标题、段落)分割。
文件名: 05-按段落标题切块.py
2.1 Markdown 标题切块
  1. from langchain_text_splitters import MarkdownHeaderTextSplitter
  2. markdown_document = """
  3. # 黑神话:悟空
  4. ## 游戏简介
  5. 《黑神话:悟空》是一款动作角色扮演游戏。
  6. ## 游戏玩法
  7. ### 战斗系统
  8. 游戏的战斗系统流畅爽快。
  9. ### 技能系统
  10. 玩家可以学习72变等经典技能。
  11. """
  12. # 定义要按哪些标题级别切块
  13. headers_to_split_on = [
  14.     ("#", "一级标题"),      # H1
  15.     ("##", "二级标题"),     # H2
  16.     ("###", "三级标题"),    # H3
  17. ]
  18. # 创建 Markdown 标题切块器
  19. markdown_splitter = MarkdownHeaderTextSplitter(
  20.     headers_to_split_on=headers_to_split_on,
  21.     strip_headers=False  # 保留标题在内容中
  22. )
  23. # 执行切块
  24. md_chunks = markdown_splitter.split_text(markdown_document)
  25. for chunk in md_chunks:
  26.     print(f"内容: {chunk.page_content}")
  27.     print(f"元数据: {chunk.metadata}")  # 包含标题层级信息
复制代码
输出示例:
  1. 内容: # 黑神话:悟空
  2. 元数据: {'一级标题': '黑神话:悟空'}
  3. 内容: ## 游戏简介
  4. 《黑神话:悟空》是一款动作角色扮演游戏。
  5. 元数据: {'一级标题': '黑神话:悟空', '二级标题': '游戏简介'}
  6. 内容: ### 战斗系统
  7. 游戏的战斗系统流畅爽快。
  8. 元数据: {'一级标题': '黑神话:悟空', '二级标题': '游戏玩法', '三级标题': '战斗系统'}
复制代码
2.2 段落切块
  1. from langchain_text_splitters import RecursiveCharacterTextSplitter
  2. # 段落之间用双换行符分隔的文本
  3. paragraph_document = """
  4. 《黑神话:悟空》是一款动作角色扮演游戏。
  5. 游戏采用虚幻引擎5开发,画面表现力极强。
  6. 玩家在游戏中扮演孙悟空的转世。
  7. """
  8. # 优先按段落(双换行符)分割
  9. paragraph_splitter = RecursiveCharacterTextSplitter(
  10.     separators=["\n\n", "\n", "。", " ", ""],
  11.     chunk_size=500,
  12.     chunk_overlap=50
  13. )
  14. chunks = paragraph_splitter.split_text(paragraph_document)
复制代码
2.3 混合切块(推荐)
  1. # 先按标题切块
  2. md_chunks = markdown_splitter.split_text(markdown_document)
  3. # 再对大块进行段落切分
  4. final_chunks = []
  5. for header_chunk in md_chunks:
  6.     if len(header_chunk.page_content) > 200:
  7.         # 块太大,再按段落切分
  8.         sub_splitter = RecursiveCharacterTextSplitter(
  9.             separators=["\n\n", "\n", "。", " ", ""],
  10.             chunk_size=200,
  11.             chunk_overlap=20
  12.         )
  13.         sub_chunks = sub_splitter.split_text(header_chunk.page_content)
  14.         
  15.         # 保留标题元数据
  16.         for sub_chunk in sub_chunks:
  17.             from langchain_core.documents import Document
  18.             final_chunks.append(Document(
  19.                 page_content=sub_chunk,
  20.                 metadata=header_chunk.metadata
  21.             ))
  22.     else:
  23.         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 技术!

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册