RAG落地实战之文本切分4种策略全解析

🧠 一、什么是 RAG?

RAG 是一种结合了检索(Retrieval)和生成(Generation)的 AI 技术。它的流程通常包括以下几个阶段:

  • 文本切分(Text Splitting)

  • 向量化编码(Embedding)

  • 存入向量数据库(如 FAISS / Chroma / Milvus 等)

  • 检索相似文段

  • 生成回答

📦 二、为什么要进行文本切分?

大模型(如 GPT)并不能直接检索整篇文档。我们必须先把文档切分成合适大小的段落(chunk),再对每个段落进行嵌入(embedding)。切得太细,会失去上下文;切得太粗,会导致嵌入不准确或超过上下文窗口。

文本切分在 RAG 中非常关键,因为切不好可能会:

  • ✂️ 打断语义单位(比如一个句子切了一半)

  • 📉 降低召回准确率

  • 🧠 让 LLM 理解不完整,输出变差

文本切分的目标是:

  • 将大文档划分为若干小块(chunk),便于嵌入(Embedding)与后续的检索(Retrieval)。

  • 切分得合理,检索更精准。

🧩 三、文本切分策略

分块三要素:

| 要素 | 说明 | 推荐值 | | --- | --- | --- | | 块大小 | 每段文字的长度 | 200-500字 | | 块重叠 | 相邻块重复内容 | 10%-20% | | 切分依据 | 按句子/段落/语义划分 | 语义分割最优 |

分块策略对比表:

| 策略类型 | 优点 | 缺点 | 适用场景 | | --- | --- | --- | --- | | 固定大小 | 实现简单 | 可能切断完整语义 | 技术文档 | | 按段落分割 | 保持逻辑完整性 | 段落长度差异大 | 文学小说 | | 语义分割 | 确保内容完整性 | 计算资源消耗较大 | 专业领域文档 |

LlamaIndex 提供了多个内置的 TextSplitter 类,来应对不同语言与结构的文档。

常用切分器:

| TextSplitter 类型 | 适用情况 | 中文支持 | | --- | --- | --- | | SentenceSplitter | 按语句分割(适合自然语言) | ✅ 很适合中文 | | TokenTextSplitter | 按 Token 数量分块 | ✅ 精确控制 LLM 输入 | | SentenceWindowNodeParser | 句子窗口法(重叠段) | ✅ 适合上下文连续的文档 | | SemanticSplitterNodeParser

| 基于语义切分(用小模型判断) | ✅ 高级但稍慢 |

✅ 四、推荐切分策略组合

  • 简单文档:用 SentenceSplitter + 固定 chunk size

  • 上下文要求高:用 SentenceWindowNodeParser句子窗口,保持上下文连续

  • 高质量 QA:用 SemanticSplitterNodeParser,语义分块(需额外安装小模型)

🧪 五、切分器详细介绍

1、语句切分SentenceSplitter

解析文本时优先考虑完整的句子;此类会尝试将句子和段落保持在一起。

参数:

名称类型描述默认
chunk\_sizeint每个块的token大小。1024
chunk\_overlapint分割时每个块的 token 重叠。200
separatorstr分割单词的默认分隔符' '空格separator 非常关键,多语言常见句尾标点:* 中午断在 “。!?\n”
  • 英语断在 “.!?\n”
  • 西语断在 “¡¿” | | paragraph\_separator | str | 段落之间的分隔符。 | '\n\n\n' | | secondary\_chunking\_regex | str|``None | 用于拆分句子的备份正则表达式。 | '[^,.;。?!]+[,.;。?!]?|[,.;。?!]' |

安装依赖

  
pip install llama-index llama-index-embeddings-huggingface

split_text_demo.py

  
from llama_index.core import SimpleDirectoryReader  
from llama_index.core.node_parser import SentenceSplitter  
# 1. 加载文档  
documents = SimpleDirectoryReader(input_files=[r"D:\Test\LLMTrain\day20_llamaindex\data\ai.txt"]).load_data()  
#print(f"原始文档数量:{len(documents)}")  
# 2. 创建 SentenceSplitter  
sentence_splitter = SentenceSplitter(  
    chunk_size=100,  
    chunk_overlap=10,  
    separator="。!?!?.\n¡¿",  # 适配中文,英语,西班牙语三种语言的分隔符  
                )  
# 3. 分割文档  
nodes = sentence_splitter.get_nodes_from_documents(documents)  
print(f"生成节点数: {len(nodes)}")  
print("分块示例的前3部分的长度:", [len(n.text) for n in nodes[:3]])  
print("\n📌 分割结果示例:")  
for i, node in enumerate(nodes[:3]):  
    print(f"\nChunk {i + 1}:\n{node}")

关于chunk_overlap

chunk_overlap 表示相邻两个文本块之间“重叠”的字符数或 token 数。

它的作用是:

  • 保留上下文连续性(避免重要信息断裂)
  • 提升检索和生成质量(比如多轮问答)

有时即便设置了值,但分隔的语句并没有重叠部分。因为是优先按照“语句边界”来切分。

  • 它不是严格按字符数定长分块的

  • 所以 chunk_overlap 是“尽量重叠语句”,而不是精确控制字符重叠

2、固定分块切分

TokenTextSplitter 根据固定的Token数量切分,目前使用场景较少。

参数:

姓名类型描述默认
chunk\_sizeint每个块的token块大小。1024
chunk\_overlapint分割时每个块的 token 重叠。20
separatorstr分割单词的默认分隔符' '
backup\_separatorsList用于分割的附加分隔符。<dynamic>
keep\_whitespacesbool是否保留块中的前导/尾随空格。False

token_text_splitter_demo.py

  
#使用固定节点切分  
from llama_index.core import SimpleDirectoryReader  
from llama_index.core.node_parser import TokenTextSplitter  
  
documents = SimpleDirectoryReader(input_files=[r"D:\Test\LLMTrain\day20_llamaindex\data\ai.txt"]).load_data()  
  
fixed_splitter = TokenTextSplitter(chunk_size=256, chunk_overlap=20)  
fixed_nodes = fixed_splitter.get_nodes_from_documents(documents)  
print("固定分块示例:", [len(n.text) for n in fixed_nodes[:3]])  
print(print("首个节点内容:\n", fixed_nodes[0].text))  
print("===========")  
print(print("第二个节点内容:\n", fixed_nodes[1].text))

3、句子窗口切分

SentenceWindowNodeParser 是 LlamaIndex 提供的一个高级文本解析器,专为 RAG(Retrieval-Augmented Generation)场景设计。它的核心作用是将文档按句子拆分,并为每个句子节点附加其前后若干句子的上下文信息,从而在检索和生成阶段提供更丰富的语义背景。

核心功能

  • 句子级切分:将文档拆分为独立的句子,每个句子作为一个节点(Node)。

  • 上下文窗口:为每个句子节点附加其前后若干句子的内容,形成一个“句子窗口”,以提供上下文信息。

  • 元数据存储:上下文窗口信息存储在节点的元数据中,便于后续检索和生成阶段使用。

    参数:

| 姓名 | 类型 | 描述 | 默认 | | --- | --- | --- | --- | | sentence\_splitter | Optional[Callable] | 将文本拆分成句子 | <function split\_by\_sentence\_tokenizer.<locals>.<lambda> at 0x7b5051030a40> | | include\_metadata | bool | 是否在节点中包含元数据 | 必需的 | | include\_prev\_next\_rel | bool | 是否包含上一个/下一个关系 | 必需的 | | window\_size | int | 指定每个句子节点前后包含的句子数量 | 3 | | window\_metadata\_key | str | 存储上下文窗口信息的元数据键名 | 'window' | | original\_text\_metadata\_key | str | 用于存储原始句子的元数据键。 | 'original\_text' |

  
from llama_index.core  import Document  
from llama_index.core import SimpleDirectoryReader  
from llama_index.core.node_parser import SentenceWindowNodeParser  
  
text = "hello. how are you? I am fine! aaa;ee. bb,cc"  
  
# 定义句子解析器  
node_parser = SentenceWindowNodeParser.from_defaults(  
    window_size=3,  
    window_metadata_key="window",  
    original_text_metadata_key="original_text",  
)  
#print(node_parser)  
#documents = SimpleDirectoryReader(input_files=[r"D:\Test\LLMTrain\day20_llamaindex\data\ai.txt"]).load_data()  
  
nodes = node_parser.get_nodes_from_documents([Document(text=text)])  
  
print([x.text for x in nodes])  
  
print("-"*20)  
  
print("第1个节点的元数据",nodes[0].metadata)  
  
print("-"*20)  
print("最后1个节点的元数据",nodes[4].metadata)

picture.image

结果分析:

  • 可看到英文字符串 "hello. how are you? I am fine! aaa;ee. bb,cc" 被拆分成了5个句子,SentenceWindowNodeParser根据句尾的标点符号句号(.), 问候(?),感叹号(!)来识别和切割句子。

  • 当文档被切割以后,窗口数据和文档数据都会被存储在节点的元数据中并以自定义的window_metadata_key和original_text_metadata_key来表示。

  • 我们查看第一个文档的元数据,第一个文档也就是原始文档的第一个句子,因此窗口数据中只包含了当前句子和后续两条句子共3个句子。

  • 节点的最后一共文档,因为是最后一共文档因此它的窗口数据中只包含了当前句子的前三条句子和当前句子一共4个句子。

注意:

LlamaIndex中的SentenceWindowNodeParser只能识别半角的英文标点符号,这将导致无法切割中文的文档。

解决方法:将中文文档中的全角符号(句号。、问号?、感叹号!)全部替换成对应的半角标点符号。且后面再多加一空格,这样就可以切割中文了。

  
from llama_index.core  import Document  
from llama_index.core import SimpleDirectoryReader  
from llama_index.core.node_parser import SentenceWindowNodeParser  
  
text = "你好,很高兴认识你。已经10点了,可我还不想起床!下雪啦!你的作业完成了吗?"  
text = text.replace('。', '. ')  
text = text.replace('!', '! ')  
text = text.replace('?', '? ')  
  
# 定义句子解析器  
node_parser = SentenceWindowNodeParser.from_defaults(  
    window_size=3,  
    window_metadata_key="window",  
    original_text_metadata_key="original_text",  
)  
#print(node_parser)  
#documents = SimpleDirectoryReader(input_files=[r"D:\Test\LLMTrain\day20_llamaindex\data\ai.txt"]).load_data()  
  
nodes = node_parser.get_nodes_from_documents([Document(text=text)])  
  
print([x.text for x in nodes])  
  
print("-"*20)  
  
print("第1个节点的元数据",nodes[0].metadata)  
  
print("-"*20)  
print("最后第2个节点的元数据",nodes[2].metadata)

picture.image

4、语义切分Semantic splitter

参数说明

| 名称 | 类型 | 描述 | 默认 | | --- | --- | --- | --- | | buffer_size | int | 模型每次考虑的句子数量 | 1 | | embed_model | BaseEmbedding | (BaseEmbedding):要使用的嵌入模型
| 必需的 | | sentence_splitter | Optional[Callable] | 将文本拆分成句子
| <function split_by_sentence_tokenizer.. at 0x7b5051032660> | | include_metadata | bool | 是否在节点中包含元数据
| 必需的 | | include_prev_next_rel | bool | 是否包含上一个/下一个关系
| 必需的 | | breakpoint_percentile_threshold | int | 一组句子与下一组句子之间余弦差异的百分位数,该百分位数必须超过该百分位数才能形成节点。该数字越小,生成的节点越多 | 95 |

buffer_size的作用:

SemanticSplitterNodeParser 的主要功能是根据语义相似性将文档划分为多个节点(Nodes),每个节点包含一组语义相关的句子。在此过程中,buffer_size 决定了在计算语义相似性时,模型每次考虑的句子数量。

例如,设置 buffer_size=3 表示模型每次将连续的 3 个句子作为一个单元进行语义相似性评估。这有助于确定是否应在这些句子之间插入断点,从而形成新的节点。

参数设置建议

  • 较小的 buffer_size(如 1 或 2):适用于内容变化频繁或结构松散的文档,有助于更精细地捕捉语义变化。
  • 较大的 buffer_size(如 5 或 10):适用于结构紧凑、语义连贯的文档,有助于减少不必要的分割。
  
from llama_index.core.node_parser import SemanticSplitterNodeParser  
from llama_index.embeddings.huggingface import HuggingFaceEmbedding  
from llama_index.core import SimpleDirectoryReader  
  
embed_model = HuggingFaceEmbedding(model_name=r"D:\Test\LLMTrain\testllm\llm\BAAI\bge-m3")  
  
documents = SimpleDirectoryReader(input_files=[r"D:\Test\LLMTrain\day20_llamaindex\data\ai.txt"]).load_data()  
  
  
parser = SemanticSplitterNodeParser(  
    embed_model=embed_model,  
    buffer_size=2  
)  
  
nodes = parser.get_nodes_from_documents(documents)  
  
print(f"🔍 共生成 {len(nodes)} 个语义块")  
print(f"\n示例块:\n{nodes[0].get_content()}")

📌 六、总结建议

| 目标 | 推荐 TextSplitter | | --- | --- | | 兼容中英文、快速好用 | SentenceSplitter ✅ | | 需要上下文连续性 | FixedWindowSplitter ✅ | | 高质量问答 + 多语义融合 | SemanticSplitterNodeParser(高级) ✅ |

0
0
0
0
评论
未登录
暂无评论