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)
文档可以通过数据加载器自动创建,也可以手动构建。
默认情况下,我们所有的数据加载器(包括 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()
如"文档管理"章节所述,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'}
节点(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)