rag系统在构建向量库时,调用embedding模型之前需要考虑如何将原始文本做分块,好的分块策略可以帮助我们优化从向量库被召回的内容的准确性。
分块的方法:
固定块大小
我们只需决定块中的tokens的数量,以及它们之间是否应该有任何重叠。一般来说,我们会在块之间保持一些重叠,以确保语义上下文不会在块之间丢失。在大多数情况下,固定大小的分块将是最直接经济的方法。与其他形式的分块相比,固定大小的分块在计算上更加经济且易于使用,因为它在分块过程中不需要使用任何NLP库。缺点是可能导致相关联的信息切割开,使单独的块难以理解。
基于langchain的示例代码
text = "..." # your text
from langchain.text_splitter import CharacterTextSplitter
text_splitter = CharacterTextSplitter(
separator = "\n\n",
chunk_size = 256,
chunk_overlap = 20
)
docs = text_splitter.create_documents([text])
特定格式分块:
特定格式分块是针对具有特定结构或语法特征的文本文件进行分块的一种方法,如 Markdown、LaTeX、Python 代码等。这种分块方式依据各自格式的特定字符或结构标记来实现,以保证分块后的内容在结构上的完整性和逻辑上的连贯性。比如 Markdown 文本可以使用标题(#
)、列表(-
)、引用(>
)等来进行分块。
针对特定格式的分块,langchain 提供了相应的方法,如:
-
• MarkdownTextSplitter:根据 markdown 的标题、列表或引用等规则来分割文本
-
• LatexTextSplitter:根据 latex 的 chapter、section 或 subsection 等规则来分割文本
-
• HTMLHeaderTextSplitter:根据 html 特定字符串分割文本,如 h1、h2、h3 等
-
• PythonCodeTextSplitter:根据 python 特定的字符串分割文本,如 class、def 等,总共有 15 种不同的语言可供选择
递归分块
递归分块以一组分隔符为参数,以递归的方式将文本分成更小的块。如果在第一次分割时无法得到所需长度的块,它将递归地继续尝试。每次递归都会尝试更细粒度的分割符号,直到块的长度满足要求。这样可以确保即使初始块很大,最终也能得到较为合适的小块。例如,可以先尝试按照句子结束符来分割(如句号或问号),如果这样分割出的文本块太长,就会依次尝试其他的标记,例如逗号或者空格。通过这种方式,我们可以找到比较合适的分割点,同时尽量避免破坏文本的语义结构。
下面是一个使用 Langchain 的 RecursiveCharacterTextSplitter 进行递归分块的示例:
from langchain_text_splitters import RecursiveCharacterTextSplitter
def test_iterative_text_splitter() -> None:
"""Test iterative text splitter."""
text = """Hi.\n\nI'm Harrison.\n\nHow? Are? You?\nOkay then f f f f.
This is a weird text to write, but gotta test the splittingggg some how.
Bye!\n\n-H."""
# 分隔符列表是["\n\n", "\n", " ", ""]
splitter = RecursiveCharacterTextSplitter(
separators=["\n\n", "\n", " ", ""],
chunk_size=10,
chunk_overlap=1)
output = splitter.split_text(text)
expected_output = [
"Hi.",
"I'm",
"Harrison.",
"How? Are?",
"You?",
"Okay then",
"f f f f.",
"This is a",
"weird",
"text to",
"write,",
"but gotta",
"test the",
"splitting",
"gggg",
"some how.",
"Bye!",
"-H.",
]
assert output == expected_output
内容感知分块:
语义分块
首先在句子之间进行分割,句子通常是一个语义单位,它包含关于一个主题的单一想法;然后使用embedding表征句子,最后将相似的句子组合在一起形成块。同时保持句子的顺序。
命题分块
类似语义分块,基于LLM构建:
- 先从基于段落的句法分块迭代开始
- 对于每个段落,使用LLM生成独立的命题
- 移除冗余的命题
- 索引并存储生成的命题
- 查询时,从命题语料库中检索,而不是原始的文档语料