节点解析使用模式
Node 解析器模块
基于文件的节点解析器
有几种基于文件的节点解析器,它们是根据正在解析的内容类型(JSON、Markdown 等)创建节点。
最简单的流程是将 SimpleFileNodeParser
与 FlatFileReader
结合起来,自动为每种类型的内容使用最佳节点解析器。然后,您可能希望将 基于文件 的节点解析器与 基于文本 的节点解析器链接起来,以考虑文本的实际长度。
简单文件节点解析器
熊哈哈注
在 Llama-index 中 SimpleFileNodeParser
用于解析文件内容,其真正的实现是 SentenceSplitter
。
# from llama_index.core.node_parser import SentenceSplitter as SimpleSentenceSplitter
from llama_index.core.node_parser import SimpleFileNodeParser
from llama_index.readers.file import FlatReader
from pathlib import Path
md_docs = FlatReader().load_data(Path("./test.md"))
parser = SimpleFileNodeParser()
md_nodes = parser.get_nodes_from_documents(md_docs)
HTML节点解析器
该节点解析器用于 beautifulsoup
解析原始 HTML
。
默认情况下,它将解析选定的 HTML 标签子集,但您可以覆盖此功能。
默认标签为:["p", "h1", "h2", "h3", "h4", "h5", "h6", "li", "b", "i", "u", "section"]
from llama_index.core.node_parser import HTMLNodeParser
parser = HTMLNodeParser(tags=["p", "h1"]) # optional list of tags
nodes = parser.get_nodes_from_documents(html_docs)
JSONNode解析器
JSONNodeParser
原始 JSON。
from llama_index.core.node_parser import JSONNodeParser
parser = JSONNodeParser()
nodes = parser.get_nodes_from_documents(json_docs)
Markdown节点解析器
MarkdownNodeParser
解析原始 markdown
文本。
from llama_index.core.node_parser import MarkdownNodeParser
parser = MarkdownNodeParser()
nodes = parser.get_nodes_from_documents(markdown_docs)
文本分割器
代码分割器
根据编写语言来分割原始代码文本。
请在此处查看支持的语言的完整列表。
from llama_index.core.node_parser import CodeSplitter
splitter = CodeSplitter(
language="python",
chunk_lines=40, # lines per chunk
chunk_lines_overlap=15, # lines overlap between chunks
max_chars=1500, # max chars per chunk
)
nodes = splitter.get_nodes_from_documents(documents)
LangchainNode解析器
您还可以使用节点解析器包装 langchain 的文本分割器。
from langchain.text_splitter import RecursiveCharacterTextSplitter
from llama_index.core.node_parser import LangchainNodeParser
parser = LangchainNodeParser(RecursiveCharacterTextSplitter())
nodes = parser.get_nodes_from_documents(documents)
句子分割器 SentenceSplitter
llama_index 默认情况下会以 SentenceSplitter
作为的分割器,该分割器以完整句子作为单位切割文档 Document
。切割的过程中, SentenceSplitter
默认以 1024 个 token 作为边界进行chunk切分, 每个chunk 开始的部分会与上一个片段 200 个 token 的部分重叠。实际处理的过程中有很多细节需要注意,以下部分针对 SentenceSplitter
的处理过程进行细节说明。
切割参数
使用 SentenceSplitter
进行切割时可以配置的参数如下:
chunk_size=1024, # 切片 token 數限制
chunk_overlap=200, # 切片開頭與前一片段尾端的重複 token 數
paragraph_separator='\n\n\n', # 段落的分界
secondary_chunking_regex='[^,.;。?!]+[,.;。?!]?' # 單一句子的樣式
separator=' ', # 最小切割的分界字元
当然也可以先构建 SentenceSplitter
的对象,然后通过 SentenceSplitter
的屬性修改上面的参数。
熊哈哈注
需要注意的是, 每一个切片都会保存元数据 metadata
,每一个切片真正的 token
数目要减去元数据 metadata
的token数,才是文本内容对应的 token
数目。可以通过优化 SentenceSplitter
的代码消除这个影响。
使用 SimpleDirectoryReader
加载器读取文件夹内的所有文档时,元数据的内容是 filepath: 文档路径
;如果直接传入文档的名称,那么元数据 metadata
的内容是 filepath: 绝对路径
。
分割步骤
SentenceSplitter
实际分割的过程包括分割与合并两个过程:
分割
- 如果整个文档
Document
token 数目小于目标token
的数目,那么不进行切分。 - 如果数目大于
Document
的 token 大于预设的chunk_size
数目,那么首先会尝试以paragraph_separator
字符串进行分段,当前的paragraph_separator
字符串是\n\n\n
。 - 如果分割出的段落的
token
超过chunk_size
, 那么使用SentenceSplitter
中设置的分句函数chunking_tokennizer_fn
进行分句。默认情况下使用nltk
内置的分句函数进行处理,对于中文来说是不合适的,需要额外的传入分句的函数。 - 如果分割出的单一句子还是超过
token
數限制, 就用secondary_chunking_regex
属性设定的规则再次切割为短的句子。当前的分隔符号为:[^,.;。?!]+[,.;。?!]?
, 目前短的句子还是完整的句子。 - 如果短句子还是超过
chunk_size
限制, 就使用separator
属性再次进行切割,当前的分隔符号是空格 【】。
- 如果短句子仍然超过
chunk_size
限制, 就以字符为单位进行切割。 - 最后切割出来的每一个小片段成为
split
。这样切割出来的方法,可以保留完整的段落,或者是完整的句子。这样切割出来可以保留完整的语义信息,只有在不得已的情况下,才会把句子的语义切割分隔开来。
合并
- 每一个
split
和与下一个split
进行合并, 直到合并并超过chunk_size
限制,合并之后整理为一个chunk
。 - 在第二个
chunk
的开始,会从上一个chunk
最后一个split
开始,往回合并split
,一直到split
的 token 的数目超过overlap
为止。这部分的split
会放在下一个chunk
的开始部分,也就是除了第一个chunk
外,每一个chunk
都会与前一个chunk
的尾部的split
内容重叠。 - 每一个新的
chunk
除了重复前一个chunk
的部分外,至少会把下一个split
的加入到当前的chunk
,不论是否会超过chunk_size
的限制。 - 这样合并完的结果,就是会尽可能以语义完整的位置切割出最长的片段, 而且每个片段都开头都涵盖前一个片段的尾端内容, 保留前后文的脉络。
熊哈哈注
最后切割出来的每一个小片段 chunk
成为 TextNode
对象, 在TextNode
中除了保留 文字片段 以外, 还有当前文档的一些 元数据、该文本在整个文档中的 起始点 位置信息等。
示例代码
尝试 SentenceSplitter
在尊重句子界限的同时分割文本。
from llama_index.core.node_parser import SentenceSplitter
splitter = SentenceSplitter(
chunk_size=1024,
chunk_overlap=20,
)
nodes = splitter.get_nodes_from_documents(documents)
参考:
-
参考
- https://hackmd.io/@meebox/HJNuQ_YRa?utm_source=preview-mode&utm_medium=rec
句子窗口节点解析器
与其他节点解析器类似 SentenceWindowNodeParser
,不同之处在于它将所有文档拆分为单个句子。生成的节点还包含元数据中每个节点周围的句子“窗口”。请注意,此元数据对 LLM 或嵌入模型不可见。
这对于生成具有非常特定范围的嵌入非常有用。然后,结合 MetadataReplacementNodePostProcessor
,您可以在将节点发送到 LLM 之前用其周围的上下文替换句子。
下面是使用默认设置设置解析器的示例。实际上,您通常只想调整句子的窗口大小。
from llama_index.core.node_parser import SentenceWindowNodeParser
node_parser = SentenceWindowNodeParser.from_defaults(
# how many sentences on either side to capture
window_size=3,
# the metadata key that holds the window of surrounding sentences
window_metadata_key="window",
# the metadata key that holds the original sentence
original_text_metadata_key="original_sentence",
)
结合 MetadataReplacementNodePostProcessor
可以在此处找到完整的示例。
语义分割节点解析器
【语义分块】是 Greg Kamradt 在他的关于 5 个embedding分块方法中提出的一个新概念,视频地址:https://youtu.be/8OJC21T2SL4?t=1933
语义分割器不会使用固定的块大小对文本进行分块,而是 使用嵌入相似性自适应地选择句子之间的断点。这确保了“块”包含语义上相互相关的句子。
!!! 注意事项: * 该正则表达式主要适用于英语句子 * 可能需要调整断点百分位阈值。
from llama_index.core.node_parser import SemanticSplitterNodeParser
from llama_index.embeddings.openai import OpenAIEmbedding
embed_model = OpenAIEmbedding()
splitter = SemanticSplitterNodeParser(
buffer_size=1, breakpoint_percentile_threshold=95, embed_model=embed_model
)
完整的示例可在我们的使用指南SemanticSplitterNodeParser中找到。
基于 token 的分割器 TokenTextSplitter
TokenTextSplitter
基于 Token 进行分割,通常与语言模型的输入结构相结合。用户可以指定每个片段的最大Token数。
在 llama-index 中与 langchain 中的 token 切分方式是不一样的。 * llama-index 中使用空格,换行符,字符粒度进行分词。 * langchain 中openai 的tiktoken 进行分词。
- 优缺点
- 优点:
- 适合大多数自然语言处理任务,可以有效保留上下文信息。
- 分割后的片段大小更符合模型的输入要求。
-
缺点:
- 对于非英语文本或特定领域文本,Token化效果可能不佳。
- 需要根据模型的Token限制进行调整。
-
适用场景:适合与大语言模型配合使用的场景,特别是需要高保真度的语义解析时。
from llama_index.core.node_parser import TokenTextSplitter
splitter = TokenTextSplitter(
chunk_size=1024,
chunk_overlap=20,
separator=" ",
)
nodes = splitter.get_nodes_from_documents(documents)
参考:
- https://ithelp.ithome.com.tw/articles/10326395
- https://developer.aliyun.com/article/1628078
基于关系的节点解析器
层次节点解析器
此节点解析器将把节点分块为分层节点。这意味着单个输入将被分块为多个块大小的层次结构,每个节点都包含对其父节点的引用。
与 AutoMergingRetriever
结合使用时,这使我们能够在检索到大多数子节点时自动用父节点替换检索到的节点。此过程为 LLM 提供了更完整的响应合成上下文。
from llama_index.core.node_parser import HierarchicalNodeParser
node_parser = HierarchicalNodeParser.from_defaults(
chunk_sizes=[2048, 512, 128]
)
结合 AutoMergingRetriever
可以在此处找到完整的示例。
参考
- https://baoyu.io/translations/rag/5-levels-of-text-splitting#google_vignette
- https://www.aneasystone.com/archives/2024/06/advanced-rag-notes.html