LlamaIndex - 01 DataLoader (文档加载)

LlamaIndex 文档加载完全指南

文档加载是 LlamaIndex 构建 RAG 应用的第一步,也是最基础的一步。本文将全面介绍 LlamaIndex 中文档加载的各种方式、核心概念以及高级自定义技巧。


一、核心概念:Document

在 LlamaIndex 中,Document(文档)是对原始数据源的标准化封装,是整个 RAG 流程的数据入口。它不仅仅指传统的文本文档,而是涵盖所有可被加载的结构化/非结构化数据的抽象载体。

1. Document 的核心属性

每个 Document 对象包含以下关键属性:

属性类型说明
textstr核心属性,存储文档的原始文本内容
metadatadict元数据字典,记录文档的上下文信息(文件名、路径、创建时间等)
id_ / doc_idstr唯一标识符,用于区分不同 Document

2. Document 的关键特性

  • 多源适配性:LlamaIndex 提供 100+ 内置 Data Loader,可将不同数据源统一为 Document 对象
  • 不可变性:原始 Document 加载后内容不会被修改(分割操作会生成新的 Node)
  • 编码保障:加载中文文档时需通过 encoding="utf-8" 指定编码

二、文档加载的三种方式

方式一:使用 SimpleDirectoryReader(最常用)

SimpleDirectoryReader 是最简单易用的加载器,可以将指定目录中的文件批量加载为 Document 对象:

from llama_index.core import SimpleDirectoryReader

# 基本用法:加载目录下所有文件
documents = SimpleDirectoryReader("./data").load_data()

# 指定编码(处理中文文档)
documents = SimpleDirectoryReader("./data", encoding="utf-8").load_data()

# 查看加载结果
print(f"加载文档数量:{len(documents)}")
print(f"第一个文档内容长度:{len(documents[0].text)}字符")
print(f"文档元数据:{documents[0].metadata}")

支持的文件格式:Markdown、PDF、Word 文档、PowerPoint 演示文稿、图像、音频和视频等。

方式二:使用 LlamaHub 的数据加载器

LlamaHub 是 LlamaIndex 的数据连接器注册表,提供了数百种连接器,支持从各种数据源加载数据:

from llama_index.core import download_loader
from llama_index.readers.database import DatabaseReader

# 示例:从 SQL 数据库加载数据
reader = DatabaseReader(
    scheme="postgresql",
    host="localhost",
    port=5432,
    user="myuser",
    password="mypassword",
    dbname="mydb",
)

query = "SELECT * FROM users"
documents = reader.load_data(query=query)

方式三:手动构造 Document

你也可以手动创建 Document 对象:

from llama_index.core import Document

# 从文本列表创建
text_list = ["文本1内容", "文本2内容", "文本3内容"]
documents = [Document(text=t) for t in text_list]

# 创建单个文档
document = Document(text="这是文档的原始文本内容")

# 快速创建示例文档(用于原型开发)
example_doc = Document.example()

三、元数据(Metadata)管理

元数据是 Document 的重要组成部分,用于记录文档的上下文信息,对回答溯源和多文档管理至关重要。

1. 设置元数据

方式一:在构造函数中设置

document = Document(
    text="文档内容",
    metadata={
        "filename": "important_doc.pdf",
        "category": "技术文档",
        "author": "张三",
        "creation_date": "2024-01-15"
    }
)

方式二:创建后设置

document = Document(text="文档内容")
document.metadata = {"filename": "doc.txt", "category": "指南"}

方式三:使用 SimpleDirectoryReader 自动设置

from llama_index.core import SimpleDirectoryReader

# 定义元数据生成函数
filename_fn = lambda filename: {"file_name": filename, "source": "local"}

documents = SimpleDirectoryReader(
    "./data", 
    file_metadata=filename_fn
).load_data()

2. 自定义文档 ID

文档 ID 用于在索引中高效管理和刷新文档:

# 自动将文件路径作为 ID
documents = SimpleDirectoryReader("./data", filename_as_id=True).load_data()

# 手动设置 ID
document.doc_id = "my_custom_document_id"

3. 高级元数据自定义

默认情况下,元数据会被同时注入到 Embedding 模型和 LLM 中。但你可以分别控制:

from llama_index.core import Document
from llama_index.core.schema import MetadataMode

document = Document(
    text="这是一个需要处理的文档",
    metadata={
        "file_name": "secret_document.txt",  # 文件名可能包含敏感信息
        "category": "finance",
        "author": "LlamaIndex"
    },
    # 排除 LLM 可见的元数据字段
    excluded_llm_metadata_keys=["file_name"],
    # 排除 Embedding 可见的元数据字段
    excluded_embed_metadata_keys=["file_name"],
)

# 查看 LLM 实际看到的内容
print("LLM 看到的内容:")
print(document.get_content(metadata_mode=MetadataMode.LLM))

# 查看 Embedding 模型实际看到的内容
print("Embedding 模型看到的内容:")
print(document.get_content(metadata_mode=MetadataMode.EMBED))

4. 自定义元数据格式

你可以控制元数据在文本中的呈现格式:

document = Document(
    text="文档内容",
    metadata={"key1": "value1", "key2": "value2"},
    metadata_seperator=" | ",           # 键值对之间的分隔符,默认为 "\n"
    metadata_template="{key}: {value}",  # 每个键值对的格式,默认为 "{key}: {value}"
    text_template="元数据:{metadata_str}\n---\n内容:{content}"  # 最终模板
)

四、批量加载与高级用法

1. 批量加载目录

from llama_index.core import SimpleDirectoryReader

# 加载整个目录
documents = SimpleDirectoryReader(
    input_dir="./docs",
    recursive=True,           # 递归读取子目录
    required_exts=[".pdf", ".txt"],  # 只加载特定扩展名
    exclude=["temp", "draft"]         # 排除特定目录
).load_data()

2. 处理大文档

对于大型文档,建议在加载时进行流式处理或设置合适的 chunk 大小:

from llama_index.core import Settings
from llama_index.core.node_parser import SentenceSplitter

# 全局设置 chunk 大小
Settings.chunk_size = 512

# 或使用自定义解析器
documents = SimpleDirectoryReader("./data").load_data()
parser = SentenceSplitter(chunk_size=512, chunk_overlap=20)
nodes = parser.get_nodes_from_documents(documents)

3. 使用 DocStore 管理多个索引

你可以将文档存储在 Document Store 中,供多个索引重用,避免数据重复:

from llama_index.core import SimpleDirectoryReader, VectorStoreIndex, SummaryIndex
from llama_index.core.storage.docstore import SimpleDocumentStore
from llama_index.core import StorageContext
from llama_index.core.node_parser import SentenceSplitter

# 加载并解析文档
documents = SimpleDirectoryReader("./data").load_data()
nodes = SentenceSplitter().get_nodes_from_documents(documents)

# 创建文档存储
docstore = SimpleDocumentStore()
docstore.add_documents(nodes)
storage_context = StorageContext.from_defaults(docstore=docstore)

# 多个索引共享同一个文档存储
vector_index = VectorStoreIndex(nodes, storage_context=storage_context)
summary_index = SummaryIndex(nodes, storage_context=storage_context)

# 验证:两个索引使用相同的底层节点
print(f"文档存储中的节点数:{len(storage_context.docstore.docs)}")

五、完整示例:从加载到索引

以下是一个完整的文档加载到索引构建的示例:

from llama_index.core import (
    SimpleDirectoryReader, 
    VectorStoreIndex,
    Settings
)
from llama_index.core.node_parser import SentenceSplitter
from llama_index.llms.openai import OpenAI

# 1. 配置 LLM(可选)
Settings.llm = OpenAI(model="gpt-3.5-turbo", temperature=0)

# 2. 加载文档
documents = SimpleDirectoryReader(
    "./data",
    encoding="utf-8",
    filename_as_id=True  # 使用文件名作为文档 ID
).load_data()

print(f"成功加载 {len(documents)} 个文档")

# 3. 查看文档信息
for doc in documents[:3]:  # 显示前3个
    print(f"文档ID: {doc.doc_id}")
    print(f"文件名: {doc.metadata.get('file_name', 'Unknown')}")
    print(f"内容长度: {len(doc.text)} 字符")
    print("-" * 50)

# 4. 设置文本分割
Settings.chunk_size = 512
Settings.chunk_overlap = 20

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

# 6. 查询测试
query_engine = index.as_query_engine()
response = query_engine.query("你的问题在这里")
print(response)

六、常见问题与最佳实践

1. 中文编码问题

加载中文文档时,务必指定 encoding="utf-8",否则可能导致乱码:

documents = SimpleDirectoryReader("./chinese_docs", encoding="utf-8").load_data()

2. 元数据最佳实践

  • 保留文件路径和文件名,便于溯源
  • 对敏感元数据使用 excluded_llm_metadata_keys 排除
  • 保持元数据值类型简单(str、float、int),以便向量数据库兼容

3. 性能优化

  • 对于大型文档集,合理设置 chunk_size(通常 512-1024)
  • 使用 recursive=False 避免不必要的深层遍历
  • 考虑使用 SimpleDirectoryReaderfile_metadata 回调函数批量添加元数据

4. 文档刷新策略

通过设置 filename_as_id=True,可以方便地检测文档变化并刷新索引:

# 设置文件名作为 ID,便于检测变化
documents = SimpleDirectoryReader("./data", filename_as_id=True).load_data()
# 后续可以使用索引的 refresh() 方法更新

总结

LlamaIndex 的文档加载机制灵活而强大,核心要点包括:

  1. Document 是核心抽象:统一表示各种数据源的标准化容器
  2. SimpleDirectoryReader 是主力工具:覆盖大部分本地文件加载需求
  3. 元数据是关键:合理设置元数据可大幅提升 RAG 效果
  4. LlamaHub 提供扩展:数百种数据连接器满足各种场景需求
  5. DocStore 支持多索引共享:避免数据冗余,提升效率

掌握这些文档加载技巧,将为后续的索引构建和智能问答打下坚实基础。