跳转至

节点解析使用模式

Node 解析器模块

基于文件的节点解析器

有几种基于文件的节点解析器,它们是根据正在解析的内容类型(JSON、Markdown 等)创建节点。

最简单的流程是将 SimpleFileNodeParserFlatFileReader 结合起来,自动为每种类型的内容使用最佳节点解析器。然后,您可能希望将 基于文件 的节点解析器与 基于文本 的节点解析器链接起来,以考虑文本的实际长度。

简单文件节点解析器

熊哈哈注

在 Llama-index 中 SimpleFileNodeParser 用于解析文件内容,其真正的实现是 SentenceSplitter

Python
# 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"]

Python
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。

Python
from llama_index.core.node_parser import JSONNodeParser

parser = JSONNodeParser()

nodes = parser.get_nodes_from_documents(json_docs)

Markdown节点解析器

MarkdownNodeParser 解析原始 markdown 文本。

Python
from llama_index.core.node_parser import MarkdownNodeParser

parser = MarkdownNodeParser()

nodes = parser.get_nodes_from_documents(markdown_docs)

文本分割器

代码分割器

根据编写语言来分割原始代码文本。

请在此处查看支持的语言的完整列表。

Python
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 的文本分割器。

Python
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 进行切割时可以配置的参数如下:

Python
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 实际分割的过程包括分割与合并两个过程:

分割
  1. 如果整个文档 Document token 数目小于目标 token 的数目,那么不进行切分。
  2. 如果数目大于 Document 的 token 大于预设的 chunk_size 数目,那么首先会尝试以 paragraph_separator 字符串进行分段,当前的 paragraph_separator 字符串是 \n\n\n
  3. 如果分割出的段落的 token 超过 chunk_size, 那么使用 SentenceSplitter 中设置的分句函数 chunking_tokennizer_fn 进行分句。默认情况下使用 nltk 内置的分句函数进行处理,对于中文来说是不合适的,需要额外的传入分句的函数。
  4. 如果分割出的单一句子还是超过 token 數限制, 就用 secondary_chunking_regex 属性设定的规则再次切割为短的句子。当前的分隔符号为:[^,.;。?!]+[,.;。?!]?, 目前短的句子还是完整的句子。
  5. 如果短句子还是超过 chunk_size 限制, 就使用 separator 属性再次进行切割,当前的分隔符号是空格 【】。
  6. 如果短句子仍然超过 chunk_size 限制, 就以字符为单位进行切割。
  7. 最后切割出来的每一个小片段成为 split。这样切割出来的方法,可以保留完整的段落,或者是完整的句子。这样切割出来可以保留完整的语义信息,只有在不得已的情况下,才会把句子的语义切割分隔开来。
合并
  1. 每一个 split 和与下一个 split 进行合并, 直到合并并超过 chunk_size 限制,合并之后整理为一个 chunk
  2. 在第二个 chunk 的开始,会从上一个 chunk 最后一个 split 开始,往回合并 split,一直到 split 的 token 的数目超过 overlap为止。这部分的 split 会放在下一个 chunk 的开始部分,也就是除了第一个 chunk 外,每一个 chunk 都会与前一个 chunk 的尾部的 split 内容重叠。
  3. 每一个新的 chunk 除了重复前一个 chunk 的部分外,至少会把下一个 split 的加入到当前的 chunk,不论是否会超过 chunk_size 的限制。
  4. 这样合并完的结果,就是会尽可能以语义完整的位置切割出最长的片段, 而且每个片段都开头都涵盖前一个片段的尾端内容, 保留前后文的脉络。

熊哈哈注

最后切割出来的每一个小片段 chunk 成为 TextNode 对象, 在TextNode 中除了保留 文字片段 以外, 还有当前文档的一些 元数据、该文本在整个文档中的 起始点 位置信息等。

示例代码

尝试 SentenceSplitter 在尊重句子界限的同时分割文本。

Python
from llama_index.core.node_parser import SentenceSplitter

splitter = SentenceSplitter(
    chunk_size=1024,
    chunk_overlap=20,
)
nodes = splitter.get_nodes_from_documents(documents)

参考:

  1. 参考

  2. https://hackmd.io/@meebox/HJNuQ_YRa?utm_source=preview-mode&utm_medium=rec

句子窗口节点解析器

与其他节点解析器类似 SentenceWindowNodeParser,不同之处在于它将所有文档拆分为单个句子。生成的节点还包含元数据中每个节点周围的句子“窗口”。请注意,此元数据对 LLM 或嵌入模型不可见。

这对于生成具有非常特定范围的嵌入非常有用。然后,结合 MetadataReplacementNodePostProcessor,您可以在将节点发送到 LLM 之前用其周围的上下文替换句子。

下面是使用默认设置设置解析器的示例。实际上,您通常只想调整句子的窗口大小。

Python
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

语义分割器不会使用固定的块大小对文本进行分块,而是 使用嵌入相似性自适应地选择句子之间的断点。这确保了“块”包含语义上相互相关的句子。

!!! 注意事项: * 该正则表达式主要适用于英语句子 * 可能需要调整断点百分位阈值。

Python
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限制进行调整。
  • 适用场景:适合与大语言模型配合使用的场景,特别是需要高保真度的语义解析时。

Python
from llama_index.core.node_parser import TokenTextSplitter

splitter = TokenTextSplitter(
    chunk_size=1024,
    chunk_overlap=20,
    separator=" ",
)
nodes = splitter.get_nodes_from_documents(documents)

参考:

  1. https://ithelp.ithome.com.tw/articles/10326395
  2. https://developer.aliyun.com/article/1628078

基于关系的节点解析器

层次节点解析器

此节点解析器将把节点分块为分层节点。这意味着单个输入将被分块为多个块大小的层次结构,每个节点都包含对其父节点的引用。

AutoMergingRetriever 结合使用时,这使我们能够在检索到大多数子节点时自动用父节点替换检索到的节点。此过程为 LLM 提供了更完整的响应合成上下文。

Python
from llama_index.core.node_parser import HierarchicalNodeParser

node_parser = HierarchicalNodeParser.from_defaults(
    chunk_sizes=[2048, 512, 128]
)

结合 AutoMergingRetriever 可以在此处找到完整的示例。

参考

  1. https://baoyu.io/translations/rag/5-levels-of-text-splitting#google_vignette
  2. https://www.aneasystone.com/archives/2024/06/advanced-rag-notes.html