文档与节点 #
概述 #
Document 和 Node 是 LlamaIndex 中数据的基本单位。Document 代表原始文档,Node 是文档分割后的子块,是索引和检索的基本单位。
text
┌─────────────────────────────────────────────────────────────┐
│ 文档与节点关系 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Document (原始文档) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 完整的文档内容,可能很长... │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ │ NodeParser │
│ ▼ │
│ Nodes (分割后的节点) │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Node 1 │ │ Node 2 │ │ Node 3 │ │ Node 4 │ │
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
│ │
│ 每个 Node 包含: │
│ - 文本内容 │
│ - 元数据 │
│ - 与其他节点的关系 │
│ │
└─────────────────────────────────────────────────────────────┘
Document 详解 #
创建 Document #
python
from llama_index.core import Document
doc = Document(text="这是文档的文本内容")
doc = Document(
text="文档内容",
metadata={
"source": "example.txt",
"author": "张三",
"date": "2024-01-01",
},
doc_id="unique_doc_id",
)
doc = Document(
text="文档内容",
metadata={"filename": "report.pdf"},
excluded_embed_metadata_keys=["filename"],
excluded_llm_metadata_keys=["filename"],
)
Document 属性 #
python
from llama_index.core import Document
doc = Document(
text="示例文档",
metadata={"source": "test", "category": "demo"},
)
print(doc.text)
print(doc.metadata)
print(doc.doc_id)
print(doc.hash)
doc.text = "更新后的内容"
从文件创建 Document #
python
from llama_index.core import SimpleDirectoryReader
documents = SimpleDirectoryReader("./data").load_data()
for doc in documents:
print(f"ID: {doc.doc_id}")
print(f"来源: {doc.metadata.get('file_name')}")
print(f"长度: {len(doc.text)}")
Node 详解 #
创建 Node #
python
from llama_index.core import TextNode
node = TextNode(text="这是一个节点")
node = TextNode(
text="节点内容",
metadata={"source": "example.pdf", "page": 1},
node_id="node_001",
)
node = TextNode(
text="节点内容",
embedding=[0.1, 0.2, 0.3, ...],
)
Node 类型 #
text
┌─────────────────────────────────────────────────────────────┐
│ Node 类型 │
├─────────────────────────────────────────────────────────────┤
│ │
│ TextNode │
│ ├── 最常用的节点类型 │
│ ├── 包含文本内容 │
│ └── 用于文本检索 │
│ │
│ ImageNode │
│ ├── 图像节点 │
│ ├── 包含图像路径或 URL │
│ └── 用于多模态检索 │
│ │
│ IndexNode │
│ ├── 索引节点 │
│ ├── 引用其他节点或索引 │
│ └── 用于递归检索 │
│ │
└─────────────────────────────────────────────────────────────┘
ImageNode #
python
from llama_index.core.schema import ImageNode
image_node = ImageNode(
image_path="./images/photo.jpg",
text="图像描述",
)
image_node = ImageNode(
image_url="https://example.com/image.jpg",
text="网络图像描述",
)
IndexNode #
python
from llama_index.core.schema import IndexNode
index_node = IndexNode(
text="摘要文本",
index_id="target_node_id",
)
Node 关系 #
python
from llama_index.core import Document
from llama_index.core.node_parser import SentenceSplitter
doc = Document(text="第一句。第二句。第三句。第四句。")
splitter = SentenceSplitter(chunk_size=20, chunk_overlap=5)
nodes = splitter.get_nodes_from_documents([doc])
for i, node in enumerate(nodes):
print(f"\n节点 {i+1}: {node.text[:30]}...")
if node.prev_node:
print(f" 前节点: {node.prev_node.node_id[:8]}...")
if node.next_node:
print(f" 后节点: {node.next_node.node_id[:8]}...")
print(f" 父文档: {node.ref_doc_id[:8] if node.ref_doc_id else 'None'}...")
NodeParser(节点解析器) #
概述 #
NodeParser 负责将 Document 分割成 Node。选择合适的分割策略对检索质量至关重要。
text
┌─────────────────────────────────────────────────────────────┐
│ NodeParser 类型 │
├─────────────────────────────────────────────────────────────┤
│ │
│ SentenceSplitter │
│ ├── 按句子分割 │
│ ├── 支持中英文 │
│ └── 最常用的分割器 │
│ │
│ TokenTextSplitter │
│ ├── 按 Token 数量分割 │
│ ├── 精确控制大小 │
│ └── 适合控制 LLM 输入 │
│ │
│ MarkdownNodeParser │
│ ├── 按 Markdown 结构分割 │
│ ├── 保留标题层级 │
│ └── 适合 Markdown 文档 │
│ │
│ CodeSplitter │
│ ├── 按代码结构分割 │
│ ├── 支持多种语言 │
│ └── 适合代码文档 │
│ │
│ SemanticSplitter │
│ ├── 语义分割 │
│ ├── 基于嵌入相似度 │
│ └── 智能分割 │
│ │
└─────────────────────────────────────────────────────────────┘
SentenceSplitter #
python
from llama_index.core import Document
from llama_index.core.node_parser import SentenceSplitter
doc = Document(text="这是一个很长的文档..." * 100)
splitter = SentenceSplitter(
chunk_size=512,
chunk_overlap=50,
paragraph_separator="\n\n",
secondary_chunking_regex="[^,.;。?!]+[,.;。?!]?",
)
nodes = splitter.get_nodes_from_documents([doc])
print(f"分割成 {len(nodes)} 个节点")
for node in nodes[:3]:
print(f"\n节点: {node.text[:100]}...")
TokenTextSplitter #
python
from llama_index.core.node_parser import TokenTextSplitter
splitter = TokenTextSplitter(
chunk_size=256,
chunk_overlap=20,
separator=" ",
)
nodes = splitter.get_nodes_from_documents(documents)
MarkdownNodeParser #
python
from llama_index.core import Document
from llama_index.core.node_parser import MarkdownNodeParser
doc = Document(text="""
# 主标题
这是主标题下的内容。
## 子标题一
这是子标题一的内容。
## 子标题二
这是子标题二的内容。
""")
parser = MarkdownNodeParser()
nodes = parser.get_nodes_from_documents([doc])
for node in nodes:
print(f"标题: {node.metadata.get('Header 1', 'N/A')}")
print(f"内容: {node.text[:50]}...")
print("---")
CodeSplitter #
python
from llama_index.core import Document
from llama_index.core.node_parser import CodeSplitter
code = '''
def hello_world():
"""打印 Hello World"""
print("Hello, World!")
def add(a, b):
"""加法函数"""
return a + b
class Calculator:
"""计算器类"""
def __init__(self):
self.result = 0
def add(self, x):
self.result += x
return self
'''
doc = Document(text=code)
splitter = CodeSplitter(
language="python",
chunk_lines=10,
chunk_lines_overlap=3,
max_chars=500,
)
nodes = splitter.get_nodes_from_documents([doc])
for i, node in enumerate(nodes):
print(f"\n=== 节点 {i+1} ===")
print(node.text)
SemanticSplitter #
bash
pip install llama-index-node-parser-semantic-splitter
python
from llama_index.core import Document
from llama_index.core.node_parser import SemanticSplitterNodeParser
from llama_index.embeddings.openai import OpenAIEmbedding
doc = Document(text="很长的文档内容...")
embed_model = OpenAIEmbedding()
splitter = SemanticSplitterNodeParser(
buffer_size=1,
breakpoint_percentile_threshold=95,
embed_model=embed_model,
)
nodes = splitter.get_nodes_from_documents([doc])
HierarchicalNodeParser #
python
from llama_index.core.node_parser import HierarchicalNodeParser
parser = HierarchicalNodeParser.from_defaults(
chunk_sizes=[2048, 512, 128],
)
nodes = parser.get_nodes_from_documents(documents)
for node in nodes:
print(f"层级: {node.metadata.get('level', 'unknown')}")
print(f"大小: {len(node.text)}")
分割策略选择 #
text
┌─────────────────────────────────────────────────────────────┐
│ 分割策略选择指南 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 场景 推荐分割器 │
│ ───────────────────────────────────────────────────────── │
│ 通用文档 SentenceSplitter │
│ 精确控制大小 TokenTextSplitter │
│ Markdown 文档 MarkdownNodeParser │
│ 代码文件 CodeSplitter │
│ 语义相关内容 SemanticSplitter │
│ 大型文档 HierarchicalNodeParser │
│ │
└─────────────────────────────────────────────────────────────┘
参数调优 #
python
from llama_index.core.node_parser import SentenceSplitter
splitter = SentenceSplitter(
chunk_size=512,
chunk_overlap=50,
)
splitter = SentenceSplitter(
chunk_size=1024,
chunk_overlap=100,
)
splitter = SentenceSplitter(
chunk_size=256,
chunk_overlap=25,
)
元数据处理 #
继承父文档元数据 #
python
from llama_index.core import Document
from llama_index.core.node_parser import SentenceSplitter
doc = Document(
text="文档内容...",
metadata={
"source": "report.pdf",
"author": "张三",
"department": "技术部",
},
)
splitter = SentenceSplitter()
nodes = splitter.get_nodes_from_documents([doc])
for node in nodes:
print(node.metadata)
添加节点级元数据 #
python
from llama_index.core import Document, TextNode
from llama_index.core.node_parser import SentenceSplitter
def add_metadata(nodes):
for i, node in enumerate(nodes):
node.metadata["chunk_index"] = i
node.metadata["total_chunks"] = len(nodes)
node.metadata["word_count"] = len(node.text.split())
return nodes
doc = Document(text="文档内容...")
splitter = SentenceSplitter()
nodes = splitter.get_nodes_from_documents([doc])
nodes = add_metadata(nodes)
排除元数据 #
python
from llama_index.core import Document
doc = Document(
text="文档内容",
metadata={
"public": "公开信息",
"private": "私有信息",
},
excluded_embed_metadata_keys=["private"],
excluded_llm_metadata_keys=["private"],
)
节点转换 #
使用 Transformations #
python
from llama_index.core import Document
from llama_index.core.node_parser import SentenceSplitter
from llama_index.core.ingestion import IngestionPipeline
from llama_index.embeddings.openai import OpenAIEmbedding
pipeline = IngestionPipeline(
transformations=[
SentenceSplitter(chunk_size=512, chunk_overlap=50),
OpenAIEmbedding(),
],
)
nodes = pipeline.run(documents=documents)
自定义转换 #
python
from llama_index.core.schema import TextNode
from llama_index.core.transformations.base import BaseTransformation
class CustomTransformer(BaseTransformation):
def __call__(self, nodes, **kwargs):
for node in nodes:
node.text = node.text.upper()
return nodes
transformer = CustomTransformer()
nodes = transformer(nodes)
节点过滤 #
python
from llama_index.core import Document
from llama_index.core.node_parser import SentenceSplitter
doc = Document(text="文档内容...")
nodes = SentenceSplitter().get_nodes_from_documents([doc])
filtered_nodes = [
node for node in nodes
if len(node.text) > 100
]
filtered_nodes = [
node for node in nodes
if node.metadata.get("category") == "tech"
]
完整示例 #
python
from llama_index.core import SimpleDirectoryReader, VectorStoreIndex
from llama_index.core.node_parser import SentenceSplitter
from llama_index.core.ingestion import IngestionPipeline
from llama_index.embeddings.openai import OpenAIEmbedding
documents = SimpleDirectoryReader("./data").load_data()
print(f"加载了 {len(documents)} 个文档")
splitter = SentenceSplitter(
chunk_size=512,
chunk_overlap=50,
)
pipeline = IngestionPipeline(
transformations=[
splitter,
OpenAIEmbedding(),
],
)
nodes = pipeline.run(documents=documents)
print(f"生成了 {len(nodes)} 个节点")
for i, node in enumerate(nodes[:3]):
print(f"\n=== 节点 {i+1} ===")
print(f"文本长度: {len(node.text)}")
print(f"来源: {node.metadata.get('file_name')}")
print(f"内容预览: {node.text[:100]}...")
index = VectorStoreIndex(nodes)
query_engine = index.as_query_engine()
response = query_engine.query("文档的主要内容是什么?")
print(f"\n回答: {response}")
下一步 #
掌握文档和节点处理后,接下来学习 索引类型 了解如何构建高效的索引!
最后更新:2026-03-30