LlamaIndex 第四篇 Documents 和 Nodes

简介

Document 和 Node 对象是 LlamaIndex 中的核心抽象概念。

Document(文档) 是一个通用容器,可用于封装任何数据源,例如 PDF 文件、API 输出或从数据库检索的数据。用户可以手动创建 Document,也可以通过我们的数据加载器自动生成。

  • metadata(元数据):一个可附加到文本上的注解字典。

  • relationships(关系):一个包含与其他 Document/Node 关联关系的字典。

Node(节点)是 LlamaIndex 中的核心元素。用户可以直接定义节点及其所有属性,也可以通过 NodeParser 类将源 Document"解析"为节点。默认情况下,每个从 Document 派生的节点都会继承该文档的元数据(例如:Document 中的 "file_name" 字段会自动传递到其所有子节点)。

以下是一些简单的代码片段,帮助您快速上手 Document 和 Node 的使用:

Documents

from llama_index.core import VectorStoreIndex, Document

text_list = [
    "心中若有光芒,脚下自有道路,勇往直前,无畏前行。",
    "不怕路长,只怕志短;不怕慢行,只怕停顿。勇往直前,梦想可期。",
    "生命不息,奋斗不止,以梦为马,不负韶华。",
]

documents = [Document(text=t) for t in text_list]

# 创建索引
index = VectorStoreIndex.from_documents(documents)

Nodes

from llama_index.core.node_parser import SentenceSplitter

# load documents
...

# parse nodes
parser = SentenceSplitter()
nodes = parser.get_nodes_from_documents(documents)

# build index
index = VectorStoreIndex(nodes)

定义 Document

文档可以通过数据加载器自动创建,也可以手动构建。

默认情况下,我们所有的数据加载器(包括 LlamaHub 提供的加载器)都会通过 load_data 函数返回 Document 对象。

from llama_index.core import SimpleDirectoryReader

documents = SimpleDirectoryReader("./data").load_data()

您也可以选择手动构建文档。LlamaIndex 提供了 Document 数据结构。

from llama_index.core import Document

text_list = [text1, text2, ...]
documents = [Document(text=t) for t in text_list]

文档添加元数据

1、在文档构造函数中添加

document = Document(
    text="text",
    metadata={"filename": "", "category": ""},
)

2、在文档创建完成后添加

document.metadata = {"filename": ""}

通过 SimpleDirectoryReader 的 file_metadata 钩子可自动设置文件名。该钩子会对每个文档自动执行,从而设置元数据字段:

from llama_index.core import SimpleDirectoryReader

filename_fn = lambda filename: {"file_name": filename}

# 系统会根据 filename_fn 函数自动为每个文档设置元数据。
documents = SimpleDirectoryReader(
    "./data", file_metadata=filename_fn
).load_data()

自定义文档ID

如"文档管理"章节所述,doc_id 用于实现索引中文档的高效更新。当使用 SimpleDirectoryReader 时,您可以将 doc_id 自动设置为每个文档的完整路径:

from llama_index.core import SimpleDirectoryReader

documents = SimpleDirectoryReader("./data", filename_as_id=True).load_data()
print([x.doc_id for x in documents])
['E:\\01-Python\\project\\MyLlamaIndex\\04-加载数据\\..\\data\\京东外卖-百度百科.txt', 'E:\\01-Python\\project\\MyLlamaIndex\\04-加载数据\\..\\data\\京东外卖APP平台叫什么?.txt', 'E:\\01-Python\\project\\MyLlamaIndex\\04-加载数据\\..\\data\\京东外卖新闻-1.txt']

您也可以直接为任意 Document 设置 doc_id

document.doc_id = "My new document id!"

注:ID 也可通过 Document 对象的 node_id 或 id_ 属性进行设置,该方式与 TextNode 对象的设置方法类似。

高级功能 - 元数据定制

需要重点说明的是:默认情况下,您设置的所有元数据都会参与向量嵌入生成和大语言模型(LLM)处理流程。

大语言模型(LLM)元数据文本定制

通常,一个文档可能包含多个元数据字段,但并非所有字段都需要在大语言模型(LLM)生成响应时可见。例如,在上述场景中,我们可能不希望LLM读取文档的file_name(文件名)。然而,这些文件名信息可能有助于生成更优质的向量嵌入。这种设计的关键优势在于:既能优化检索时的向量嵌入效果,又不会影响LLM最终处理的内容。

我们可以通过以下方式排除这些元数据:

document.excluded_llm_metadata_keys = ["file_name"]

接下来,我们可以通过以下方式测试 LLM 实际将处理的内容:

from llama_index.core.schema import MetadataMode

print(document.get_content(metadata_mode=MetadataMode.LLM))

Embedding元数据文本定制

与定制LLM可见元数据类似,我们也可以定制嵌入模型可见的元数据。这种情况下,您可以专门排除某些不希望影响嵌入向量的元数据字段——当特定文本可能干扰嵌入效果时,这一功能尤为重要。

document.excluded_embed_metadata_keys = ["file_name"]

可以通过以下方式测试嵌入模型实际处理的内容:

from llama_index.core.schema import MetadataMode

print(document.get_content(metadata_mode=MetadataMode.EMBED))

元数据格式定制

如您目前所了解的,当向大语言模型(LLM)或嵌入模型传输数据时,元数据会被注入到每个文档/节点的实际文本中。默认情况下,该元数据的格式由以下三个属性控制:

❤️1、Document.metadata_seperator -> default = "\n"

该属性用于控制元数据中所有键值对拼接时的分隔符。

2、Document.metadata_template -> default = "{key}: {value}"

此属性用于控制元数据中每个键值对的格式化方式,必须包含 key(键)- value(值)两个字符串变量

3、Document.text_template -> default = {metadata_str}\n\n{content}

当使用 metadata_separator 和 metadata_template 将元数据转换为字符串后,此模板用于控制元数据与文档/节点文本内容拼接时的最终呈现格式。该模板必须包含以下两个字符串变量:

  • metadata(元数据)

  • content(文本内容)

⚽总结:

元数据定制完整示例

from llama_index import Document
from llama_index.schema import MetadataMode

# 1. 创建带元数据的文档
doc = Document(
    text="这是核心文本内容",
    metadata={
        "author": "张伟", 
        "department": "算法部",
        "file_name": "重要报告.pdf",
        "publish_date": "2023-11-30"
    },
    metadata_separator=" | ",  # 键值对分隔符
    metadata_template="{key}: {value}",  # 单字段模板
    text_template="【元数据】{metadata}\n【正文】{content}"  # 全文模板
)

# 2. 验证不同模式的输出
print("--- LLM可见内容 ---")
print(doc.get_content(MetadataMode.LLM))  # 默认排除file_name

print("\n--- 嵌入模型可见内容 ---") 
print(doc.get_content(MetadataMode.EMBED))  # 包含全部元数据

print("\n--- 原始元数据 ---")
print(doc.metadata)

输出结果示例

--- LLM可见内容 ---
【元数据】author: 张伟 | department: 算法部 | publish_date: 2023-11-30  
【正文】这是核心文本内容

--- 嵌入模型可见内容 ---
【元数据】author: 张伟 | department: 算法部 | file_name: 重要报告.pdf | publish_date: 2023-11-30  
【正文】这是核心文本内容

--- 原始元数据 ---
{'author': '张伟', 'department': '算法部', 'file_name': '重要报告.pdf', 'publish_date': '2023-11-30'}

定义Nodes

节点(Node)是源文档的"片段化"表示形式,可以是文本块、图像或其他类型的数据单元。每个节点不仅包含元数据,还存储了与其他节点及索引结构的关联信息。

在LlamaIndex中,节点(Node)是一等公民。您可以选择直接定义节点及其所有属性,也可以通过NodeParser类将源文档"解析"为节点。

例如,您可以通过以下方式操作节点:

from llama_index.core.node_parser import SentenceSplitter

parser = SentenceSplitter()

# 将文档解析为节点列表
nodes = parser.get_nodes_from_documents(documents)

您也可以选择手动构建节点对象,并跳过第一部分。例如,

from llama_index.core.schema import TextNode, NodeRelationship, RelatedNodeInfo

node1 = TextNode(text="", id_="")
node2 = TextNode(text="", id_="")
# set relationships
node1.relationships[NodeRelationship.NEXT] = RelatedNodeInfo(
    node_id=node2.node_id
)
node2.relationships[NodeRelationship.PREVIOUS] = RelatedNodeInfo(
    node_id=node1.node_id
)
nodes = [node1, node2]

如果需要,RelatedNodeInfo 类还可以存储额外的元数据:

node2.relationships[NodeRelationship.PARENT] = RelatedNodeInfo(
    node_id=node1.node_id, metadata={"key": "val"}
)

添加Node节点ID

每个节点都有一个 node_id 属性,若未手动指定则会自动生成。该 ID 可用于多种用途,例如:更新存储中的节点、通过 IndexNode 定义节点间关系等。

您也可以直接获取或设置任意 TextNode 的 node_id。

print(node.node_id)
node.node_id = "My new node_id!"

将文档拆分为节点(Node)

处理文档的关键步骤是将其分割为文本块(Chunks)或节点(Node)对象,其核心目的是将原始数据处理成适合大语言模型(LLM)检索和处理的标准化单元。

LlamaIndex 支持多种文本分割器,覆盖从基础的段落/句子/词元(token)分割器到针对特定文件格式(如 HTML、JSON)的专用分割器。

文本分割器(text splitters) : Node Parser Modules - LlamaIndex

这些分割器既可单独使用,也能作为数据接入管道(ingestion pipeline)的组成部分。

ingestion pipelin:Node Parser Usage Pattern - LlamaIndex

from llama_index.core import SimpleDirectoryReader
from llama_index.core.ingestion import IngestionPipeline
from llama_index.core.node_parser import TokenTextSplitter

documents = SimpleDirectoryReader("./data").load_data()

pipeline = IngestionPipeline(transformations=[TokenTextSplitter(), ...])

nodes = pipeline.run(documents=documents)

你可能感兴趣的:(LlamaIndex,人工智能,python)