
基于文本结构分块 - 文本分块(Text Splitting),RAG不可缺失的重要环节
在 RAG 的核心步骤中,有一个至关重要的步骤:“文本分块(Text Splitting)”。
它的主要作用就是把一大段文本切分成更小、更合理的片段,这样模型才能更好地理解、处理或者存储这些内容。
如果一整篇文章不拆开,那 embedding 的颗粒度太粗,问答的时候很容易不准。所以切得好不好,直接影响最后答案的相关性和准确性。
最基本的分块方法是根据文档的长度进行拆分。这种简单而有效的方法确保每个块不会超过指定的大小限制。
基于长度拆分的主要好处:简单明了的实现、一致的块大小、易于适应不同模型的要求。缺点就是: 过于死板,忽视文本结构
1. 基于文本结构分块
一般的文本会自然地组织成层次单位,如段落、句子和词。
我们可以利用这种固有结构来指导我们的拆分策略,创建保持自然语言流畅、保持拆分内的语义连贯,并适应不同文本粒度水平的拆分。
LangChain 的 RecursiveCharacterTextSplitter
实现了这个概念:
-
RecursiveCharacterTextSplitter
尝试保持较大单位(例如段落)的完整性。 - 如果一个单位超出了块大小,它将移到下一个层级(例如句子)。
- 如果必要,这个过程将继续到单词级别。
2. RecursiveCharacterTextSplitter的实现思路
2.1 挑选分隔符
- 从提供的分隔符列表中找到第一个在文本中存在的分隔符
- 如果找到合适的分隔符,将其后的所有分隔符保存起来,用于后续可能的递归分割
- 如果找不到任何分隔符,就使用最后一个分隔符(通常是空字符串)
举个例子:
假设分隔符列表是 ["\n\n", "\n", " ", ""],对于文本 "Hello\nWorld" :
- 首先检查 "\n\n" ,文本中不存在
- 然后检查 "\n" ,文本中存在
- 选择 "\n" 作为分隔符
- 保存 [" ", ""] 作为 new_separators ,供后续使用
separator = separators[-1]
new_separators = []
for i, _s in enumerate(separators):
_separator = _s if self._is_separator_regex else re.escape(_s)
if _s == "":
separator = _s
break
if re.search(_separator, text):
separator = _s
new_separators = separators[i + 1 :]
break
_separator = separator if self._is_separator_regex else re.escape(separator)
2.2 按分隔符分割文本
splits = _split_text_with_regex(text, _separator, self._keep_separator)
2.3 整理分割好的块
- 对每个分割后的文本块进行处理:
- 如果文本块小于指定大小,添加到临时列表
- 如果文本块大于指定大小,且还有其他分隔符可用,则递归分割
- 如果文本块大于指定大小,但没有其他分隔符,则直接添加
- 合并所有符合大小要求的文本块
- 返回最终的分割结果
for s in splits:
if self._length_function(s) < self._chunk_size:
_good_splits.append(s)
else:
if _good_splits:
merged_text = self._merge_splits(_good_splits, _separator)
final_chunks.extend(merged_text)
_good_splits = []
ifnot new_separators:
final_chunks.append(s)
else:
other_info = self._split_text(s, new_separators)
final_chunks.extend(other_info)
if _good_splits:
merged_text = self._merge_splits(_good_splits, _separator)
final_chunks.extend(merged_text)
return final_chunks
3. 代码实现
from langchain.text_splitter import RecursiveCharacterTextSplitter
text = """
《诛仙》
作者:萧鼎
第一集
序章
时间:不明,应该在很早很早以前。
地点:神州浩土。
自太古以来,人类眼见周遭世界,诸般奇异之事,电闪雷鸣,狂风暴雨,又有天灾人祸,伤亡无数,哀鸿遍野,绝非人力所能为,所能抵挡。遂以为九天之上,有诸般神灵,九幽之下,亦是阴魂归处,阎罗殿堂。
于是神仙之说,流传于世。无数人类子民,诚心叩拜,向着自己臆想创造出的各种神明顶礼膜拜,祈福诉苦,香火鼎盛。
自古以来,凡人无不有一死。但世人皆恶死爱生,更有地府阎罗之说,平添了几分苦惧,在此之下,遂有长生不死之说。
相较其它生灵物种,人类或在体质上处于劣势,但万物灵长,却是绝无虚言。在追求长生的原动力下,一代代聪明才智之士,前赴后继,投入毕生精力,苦苦钻研。
至今为止,虽然真正意义上的长生不死仍未找到,却有一些修真炼道之士参透些许天地造化,以凡人之身,掌握强横力量,借助各般秘宝法器之力,竟可震撼天地,有雷霆之威。
而一些得道高深的前辈,更传说已活上千年之久而不死。世上之人以为得道成仙,便有更多人投入修真炼道之路。
神州浩土,广瀚无边。唯有中原大地,最是丰美肥沃,天下人口十之八九聚居于此。而东南西北边荒之地,山险水恶,多凶兽猛禽,多恶瘴毒物,亦多蛮族夷民,茹毛饮血,是以人迹罕至。而人间自古相传,有洪荒遗种,残存人世,藏于深山密谷,寿逾万年,却是无人得见。
时至今日,人间修真炼道之人,多如过江之鲫,数不胜数。又以神州浩土之广阔,人间奇人异士之多,故修炼之法道林林总总,俱不相同。长生之法还未找到,彼此间却逐渐有了门派之分,正邪之别。由之而起的门户之见,勾心斗角乃至争伐杀戮,在所多有。
当长生不死看起来那般遥远而不可捉摸,修炼中所带来的力量,便逐渐成了许多人的目标。
方今之世,正道大昌,邪魔退避。中原大地山灵水秀,人气鼎盛,物产丰富,为正派诸家牢牢占据。其中尤以「青云门」、「天音寺」和「焚香谷」为三大支柱,是为领袖。
这个故事,便是从「青云门」开始的。
"""
text_splitter = RecursiveCharacterTextSplitter(chunk_size=150)
docs = text_splitter.create_documents([text])
for doc in docs:
print('-' * 50)
print(doc)
4. 拆分结果
通过观察文本的分块结果,可以看出 RecursiveCharacterTextSplitter 在 chunk_size=150 的设置下,将整个文本分成了7个完整的块。
分割时优先考虑了段落间的自然分隔(\n\n),使每个块都保持了相对独立的主题。
这种分块方式既保证了每块内容的语义连贯性,又控制了文本长度在合理范围内,为后续的文本处理和分析提供了良好的基础。
--------------------------------------------------
page_cnotallow='《诛仙》
作者:萧鼎
第一集
序章
时间:不明,应该在很早很早以前。
地点:神州浩土。
自太古以来,人类眼见周遭世界,诸般奇异之事,电闪雷鸣,狂风暴雨,又有天灾人祸,伤亡无数,哀鸿遍野,绝非人力所能为,所能抵挡。遂以为九天之上,有诸般神灵,九幽之下,亦是阴魂归处,阎罗殿堂。'
--------------------------------------------------
page_cnotallow='于是神仙之说,流传于世。无数人类子民,诚心叩拜,向着自己臆想创造出的各种神明顶礼膜拜,祈福诉苦,香火鼎盛。
自古以来,凡人无不有一死。但世人皆恶死爱生,更有地府阎罗之说,平添了几分苦惧,在此之下,遂有长生不死之说。'
--------------------------------------------------
page_cnotallow='相较其它生灵物种,人类或在体质上处于劣势,但万物灵长,却是绝无虚言。在追求长生的原动力下,一代代聪明才智之士,前赴后继,投入毕生精力,苦苦钻研。'
--------------------------------------------------
page_cnotallow='至今为止,虽然真正意义上的长生不死仍未找到,却有一些修真炼道之士参透些许天地造化,以凡人之身,掌握强横力量,借助各般秘宝法器之力,竟可震撼天地,有雷霆之威。
而一些得道高深的前辈,更传说已活上千年之久而不死。世上之人以为得道成仙,便有更多人投入修真炼道之路。'
--------------------------------------------------
page_cnotallow='神州浩土,广瀚无边。唯有中原大地,最是丰美肥沃,天下人口十之八九聚居于此。而东南西北边荒之地,山险水恶,多凶兽猛禽,多恶瘴毒物,亦多蛮族夷民,茹毛饮血,是以人迹罕至。而人间自古相传,有洪荒遗种,残存人世,藏于深山密谷,寿逾万年,却是无人得见。'
--------------------------------------------------
page_cnotallow='时至今日,人间修真炼道之人,多如过江之鲫,数不胜数。又以神州浩土之广阔,人间奇人异士之多,故修炼之法道林林总总,俱不相同。长生之法还未找到,彼此间却逐渐有了门派之分,正邪之别。由之而起的门户之见,勾心斗角乃至争伐杀戮,在所多有。'
--------------------------------------------------
page_cnotallow='当长生不死看起来那般遥远而不可捉摸,修炼中所带来的力量,便逐渐成了许多人的目标。
方今之世,正道大昌,邪魔退避。中原大地山灵水秀,人气鼎盛,物产丰富,为正派诸家牢牢占据。其中尤以「青云门」、「天音寺」和「焚香谷」为三大支柱,是为领袖。
这个故事,便是从「青云门」开始的。'
5. 图形化显示分块
通过www.chunkviz.com可以以图形化的方式看到分块结果
总结
文本分块不仅是技术实现的问题,更是影响 RAG 系统最终效果的核心策略。
简单分块虽易上手但效果有限,结构化递归分块则在保留语义、提升相关性方面表现更优。
想要构建高质量问答系统,分块方式绝不能随便选,而是要结合文本特点和应用场景精细设计。
本文转载自AI取经路,作者:AI取经路
