文档与节点 #

概述 #

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