ai
  • index
  • 1.欢迎来到LlamaIndex
  • 2.高级概念
  • 3.安装与设置
  • 4.入门教程(使用OpenAI)
  • 5.入门教程(使用本地LLMs)
  • 6.构建一个LLM应用
  • 7.使用LLMs
  • pydantic
  • asyncio
  • apikey
  • 8.RAG简介
  • 9.加载数据
  • 10.索引
  • 11.存储
  • 12.查询
  • weaviate
  • Cohere
  • warnings
  • WeaviateStart
  • spacy
  • 使用LlamaIndex构建全栈Web应用指南
  • back2
  • back4
  • front2
  • front4
  • front6
  • front8
  • llamaindex_backend
  • llamaindex_frontend
  • 1. 加载数据(摄取)
    • 1.1 数据摄取流程概述
    • 1.2 加载器
      • 1.2.1 使用SimpleDirectoryReader加载数据
      • 1.2.2 使用来自LlamaHub的Readers
        • 1.2.2.1 sqlite3
        • 1.2.2.2 mysql
      • 1.2.3 直接创建Documents
  • 2. 转换
    • 2.1 高级转换API
    • 2.2 低级转换API
      • 2.2.1 将您的文档拆分为Nodes
      • 2.2.2 添加元数据
      • 2.2.3 添加Embeddings
      • 2.2.4 直接创建并传递Nodes
  • 3. Document VS Node
    • 3.1 基本概念
    • 3.2 继承关系
      • 3.2.1 Document和TextNode
      • 3.2.2 类层次结构图
      • 3.2.3 实际用途
    • 3.3 转换过程
    • 3.4 关系特点
      • 3.4.1 一对多关系
      • 3.4.2 元数据继承
    • 3.5 实际应用场景
      • 3.5.1 文档检索优化
      • 3.5.2 粒度控制
      • 3.5.3 语义精确性
      • 3.5.4 实际效果对比
    • 3.6 总结
  • 4. 总结

1. 加载数据(摄取) #

在您选定的LLM能够处理数据之前,首先需要对数据进行处理和加载。这与机器学习领域中的数据清洗/特征工程流程类似,或传统数据环境中的ETL流程相似。

1.1 数据摄取流程概述 #

该数据摄取流程通常包含三个主要阶段:

  1. 加载数据 - 从各种数据源获取原始数据
  2. 转换数据 - 对数据进行处理和格式化
  3. 索引并存储数据 - 将处理后的数据存储到索引中

我们将在索引和存储部分详细讨论索引和存储相关内容。在本指南中,我们将主要讨论加载器(loaders)和转换(transformations)。

1.2 加载器 #

在您选择的LLM能够处理数据之前,您需要先加载数据。LlamaIndex通过数据连接器(也称为数据适配器)来实现这一过程。Reader 数据连接器从不同数据源摄取数据,并将数据格式化为 Document 对象。一个 Document 是一组数据(目前为文本,未来将包括图像和音频)及其相关元数据的集合。

1.2.1 使用SimpleDirectoryReader加载数据 #

最易用的阅读器是我们的 SimpleDirectoryReader,它能将指定目录下的每个文件转换为文档。该工具内置于LlamaIndex,支持读取多种格式,包括Markdown、PDF、Word文档、PowerPoint幻灯片、图片、音频和视频文件。

# 导入SimpleDirectoryReader类
# 这是LlamaIndex内置的文档读取器,用于从目录中读取各种格式的文件
from llama_index.core import SimpleDirectoryReader

# 创建SimpleDirectoryReader实例,指定数据目录路径
# 这会读取./data目录下的所有支持格式的文件
documents = SimpleDirectoryReader("./data").load_data()

# 打印加载的文档数量
print(f"成功加载了 {len(documents)} 个文档")

# 显示每个文档的基本信息
for i, doc in enumerate(documents):
    print(f"文档 {i+1}: {doc.metadata.get('file_name', '未知文件名')}")

1.2.2 使用来自LlamaHub的Readers #

由于数据来源的可能性众多,这些连接器并未全部内置。相反,您需要从我们的数据连接器注册表中下载它们,即 LlamaHub。

在此示例中,LlamaIndex下载并安装名为数据库读取器的连接器,它会对SQL数据库执行查询并将结果的每一行作为 Document:

uv add llama-index-readers-database
1.2.2.1 sqlite3 #
# 导入os模块,用于操作系统相关功能
import os

# 导入sqlite3模块,用于操作SQLite数据库
import sqlite3

# 从llama_index.core模块导入Document类,用于文档对象
from llama_index.core import Document

# 从llama_index.core模块导入VectorStoreIndex类,用于向量索引
from llama_index.core import VectorStoreIndex


# 定义创建SQLite数据库和示例数据的函数
def create_sample_database():
    """创建示例SQLite数据库和表"""
    # 连接到名为sample.db的SQLite数据库(如果不存在则自动创建)
    conn = sqlite3.connect("sample.db")
    # 获取数据库游标对象
    cursor = conn.cursor()

    # 创建users表(如果不存在则新建)
    cursor.execute(
        """
        CREATE TABLE IF NOT EXISTS users (
            id INTEGER PRIMARY KEY,
            name TEXT NOT NULL,
            email TEXT,
            department TEXT,
            salary REAL
        )
    """
    )

    # 定义要插入的示例数据
    sample_data = [
        (1, "张三", "zhangsan@company.com", "技术部", 8000),
        (2, "李四", "lisi@company.com", "市场部", 7500),
        (3, "王五", "wangwu@company.com", "人事部", 7000),
        (4, "赵六", "zhaoliu@company.com", "技术部", 8500),
        (5, "钱七", "qianqi@company.com", "财务部", 7200),
    ]

    # 批量插入示例数据到users表
    cursor.executemany(
        """
        INSERT OR REPLACE INTO users (id, name, email, department, salary)
        VALUES (?, ?, ?, ?, ?)
    """,
        sample_data,
    )

    # 提交事务,保存更改
    conn.commit()
    # 关闭数据库连接
    conn.close()
    # 打印数据库创建完成提示
    print("✅ 示例数据库创建完成")


# 定义从数据库查询数据并转换为Document对象的函数
def load_data_from_database():
    """从SQLite数据库加载数据并转换为Document对象"""
    # 连接到sample.db数据库
    conn = sqlite3.connect("sample.db")
    # 获取数据库游标对象
    cursor = conn.cursor()

    # 执行SQL查询,获取users表所有数据
    cursor.execute("SELECT * FROM users")
    # 获取所有查询结果
    rows = cursor.fetchall()

    # 初始化文档对象列表
    documents = []
    # 遍历每一行数据
    for row in rows:
        # 构造格式化的文本内容
        text_content = f"用户信息: ID={row[0]}, 姓名={row[1]}, 邮箱={row[2]}, 部门={row[3]}, 薪资={row[4]}"

        # 创建Document对象,包含文本内容和元数据
        doc = Document(
            text=text_content,
            metadata={
                "id": row[0],
                "name": row[1],
                "email": row[2],
                "department": row[3],
                "salary": row[4],
                "source": "database",
            },
        )
        # 将Document对象添加到列表
        documents.append(doc)

    # 关闭数据库连接
    conn.close()
    # 返回文档对象列表
    return documents


# 定义主函数
def main():
    """主函数,演示从数据库加载数据"""
    try:
        # 打印演示标题
        print("🗄️ 数据库文档加载演示")
        # 打印分隔线
        print("=" * 50)

        # 创建示例数据库
        create_sample_database()

        # 打印加载数据提示
        print("\n📊 正在从数据库加载数据...")
        # 从数据库加载文档对象
        documents = load_data_from_database()

        # 打印加载的文档数量
        print(f"✅ 从数据库加载了 {len(documents)} 个文档")

        # 打印前3个文档的内容和元数据
        print("\n📋 文档内容预览:")
        for i, doc in enumerate(documents[:3]):
            print(f"文档 {i+1}: {doc.text}")
            print(f"   元数据: {doc.metadata}")
            print()

        # 打印数据库统计信息
        print(f"📈 数据库统计:")
        print(f"- 总文档数: {len(documents)}")
        print(
            f"- 平均文档长度: {sum(len(doc.text) for doc in documents) / len(documents):.0f} 字符"
        )

        # 按部门统计文档数量
        departments = {}
        for doc in documents:
            # 获取部门名称
            dept = doc.metadata["department"]
            # 统计每个部门的文档数
            departments[dept] = departments.get(dept, 0) + 1

        # 打印部门分布统计
        print(f"- 部门分布: {departments}")

        # 创建向量索引
        print("\n🤖 创建向量索引...")
        index = VectorStoreIndex.from_documents(documents)

        # 创建查询引擎
        print("🔍 创建查询引擎...")
        query_engine = index.as_query_engine()

        # 打印自然语言查询演示标题
        print("\n📝 自然语言查询演示:")
        print("-" * 40)

        # 查询1:技术部有哪些员工
        print("查询1: 技术部有哪些员工?")
        response1 = query_engine.query("技术部有哪些员工?")
        print(f"回答: {response1}")
        print()

        # 查询2:薪资最高的员工是谁
        print("查询2: 薪资最高的员工是谁?")
        response2 = query_engine.query("薪资最高的员工是谁?")
        print(f"回答: {response2}")
        print()

        # 查询3:市场部员工信息
        print("查询3: 市场部的员工信息")
        response3 = query_engine.query("市场部的员工信息")
        print(f"回答: {response3}")
        print()

        # 查询4:公司的平均薪资是多少
        print("查询4: 公司的平均薪资是多少?")
        response4 = query_engine.query("公司的平均薪资是多少?")
        print(f"回答: {response4}")
        print()

        # 打印演示完成提示
        print("\n✅ 演示完成!")

    # 捕获异常并打印错误信息
    except Exception as e:
        print(f"❌ 数据库操作出错: {e}")
        print("请检查数据库连接和查询语句")


# 程序入口点,判断是否为主模块
if __name__ == "__main__":
    # 调用主函数
    main()
1.2.2.2 mysql #
uv add mysqlclient
# 导入os模块,用于读取环境变量
import os
# 从llama_index.core模块导入download_loader(虽然本代码未用到)
from llama_index.core import download_loader
# 从llama_index.readers.database模块导入DatabaseReader类
from llama_index.readers.database import DatabaseReader

# 创建数据库读取器实例,使用环境变量配置数据库连接参数
reader = DatabaseReader(
    # 获取数据库类型,默认为mysql
    scheme=os.getenv("DB_SCHEME", "mysql"),
    # 获取数据库主机地址,默认为localhost
    host=os.getenv("DB_HOST", "localhost"),
    # 获取数据库端口,默认为3306
    port=os.getenv("DB_PORT", "3306"),
    # 获取数据库用户名,默认为root
    user=os.getenv("DB_USER", "root"),
    # 获取数据库密码,默认为123456
    password=os.getenv("DB_PASS", "123456"),
    # 获取数据库名称,默认为jobs
    dbname=os.getenv("DB_NAME", "jobs"),
)

# 定义SQL查询语句,查询users表的所有数据
query = "SELECT * FROM users"

# 执行SQL查询,并将结果加载为文档对象列表
documents = reader.load_data(query=query)

# 打印从数据库加载的文档数量
print(f"从数据库加载了 {len(documents)} 个文档")

# 遍历前3个文档,打印每个文档的前100个字符内容
for i, doc in enumerate(documents[:3]):
    print(f"文档 {i+1}: {doc.text[:100]}...")

有数百种连接器可供使用于 LlamaHub!

1.2.3 直接创建Documents #

除了使用加载器,您也可以直接创建 Document 对象:

# 从llama_index.core模块导入Document类
from llama_index.core import Document

# 创建一个Document对象,text参数指定文档的文本内容
doc = Document(text="这是一个示例文档的文本内容。")

# 创建一个带有元数据的Document对象
# text参数指定文档内容,metadata参数为字典类型,包含文档的属性信息
doc_with_metadata = Document(
    text="这是另一个示例文档。",
    metadata={"source": "手动创建", "category": "示例", "created_date": "2024-01-01"},
)

# 打印第一个文档的文本内容
print(f"文档1: {doc.text}")
# 打印第二个文档的文本内容
print(f"文档2: {doc_with_metadata.text}")
# 打印第二个文档的元数据信息
print(f"文档2元数据: {doc_with_metadata.metadata}")

2. 转换 #

数据加载完成后,在存入存储系统前还需进行加工和转换。这些转换包括分块处理、提取元数据以及为每个数据块生成嵌入向量。这些步骤对于确保数据能被有效检索并被LLM高效使用至关重要。

转换的输入/输出是 Node 对象(一个 Document 是 Node 类的一个子类)。转换操作还可以堆叠和重新排序。

我们提供用于文档转换的高级和低级API。

2.1 高级转换API #

索引具有 .from_documents() 方法,该方法接受一个Document对象数组,并能正确解析和分块处理它们。但有时您可能希望对文档的拆分方式拥有更大的控制权。

# 导入VectorStoreIndex类,用于创建向量存储索引
from llama_index.core import VectorStoreIndex

# 导入Document类,用于构建文档对象
from llama_index.core import Document

# 创建一个包含多个文档的列表,每个文档都用Document类封装
documents = [
    # 第一个文档,内容为人工智能的简介
    Document(
        text="这是一个关于人工智能的文档。人工智能是计算机科学的一个分支,致力于创建能够执行通常需要人类智能的任务的系统。"
    ),
    # 第二个文档,内容为机器学习的简介
    Document(
        text="机器学习是人工智能的一个子领域,它使计算机能够在没有明确编程的情况下学习和改进。"
    ),
    # 第三个文档,内容为深度学习的简介
    Document(
        text="深度学习是机器学习的一个分支,使用多层神经网络来模拟人脑的学习过程。"
    ),
]

# 使用documents列表创建向量存储索引
# 该方法会自动将文档分块并生成向量嵌入
vector_index = VectorStoreIndex.from_documents(documents)

# 基于向量索引创建查询引擎
# 查询引擎可以用自然语言进行检索
query_engine = vector_index.as_query_engine()

# 使用查询引擎进行一次自然语言查询
response = query_engine.query("请介绍一下这些文档的内容")
# 输出查询结果
print(response)

在底层实现中,这会将您的Document拆分为Node对象。这些Node与Documents类似(包含文本和元数据),但与父级Document存在关联关系。

如果您想自定义核心组件,比如文本分割器,通过这个抽象层您可以传入一个自定义的 transformations 列表或设置全局的 Settings:

  • books
  • englishbooks
# 导入SimpleDirectoryReader类,用于从目录中读取文档
from llama_index.core import SimpleDirectoryReader

# 导入IngestionPipeline类,用于构建数据摄取管道
from llama_index.core.ingestion import IngestionPipeline

# 导入TokenTextSplitter类,用于将文本分割成小块
from llama_index.core.node_parser import TokenTextSplitter

# 导入TitleExtractor和KeywordExtractor类,用于提取标题和关键词
from llama_index.core.extractors import TitleExtractor, KeywordExtractor

# 导入OpenAIEmbedding类,用于生成文本嵌入向量
from llama_index.embeddings.openai import OpenAIEmbedding

# 使用SimpleDirectoryReader从指定目录加载文档
documents = SimpleDirectoryReader("./data").load_data()

# 创建数据摄取管道,组合多个转换步骤
pipeline = IngestionPipeline(
    transformations=[
        # 使用TokenTextSplitter将文本分割为长度为512、重叠10的块
        TokenTextSplitter(chunk_size=512, chunk_overlap=10),
        # 提取每个节点的标题
        TitleExtractor(),
        # 提取每个节点的关键词
        KeywordExtractor(),
        # 生成每个节点的嵌入向量
        OpenAIEmbedding(),
    ]
)

# 运行数据摄取管道,将文档转换为节点
nodes = pipeline.run(documents=documents)

# 打印生成的节点数量
print(f"生成了 {len(nodes)} 个节点")

# 遍历所有节点,打印节点的部分内容、标题、关键词和嵌入维度
for i, node in enumerate(nodes):
    print(f"\n节点 {i+1}:")
    print(f"  文本内容: {node.text[:150]}...")
    print(f"  标题: {node.metadata.get('document_title', '无标题')}")
    print(f"  关键词: {node.metadata.get('excerpt_keywords', '无关键词')}")
    print(
        f"  嵌入维度: {len(node.embedding) if hasattr(node, 'embedding') else '无嵌入'}"
    )
    print("-" * 80)

2.2 低级转换API #

您也可以明确地定义这些步骤。您可以通过两种方式实现:将我们的转换模块(如文本分割器、元数据提取器等)作为独立组件使用,或者在我们的声明式框架中组合它们,即转换管道接口。

2.2.1 将您的文档拆分为Nodes #

处理文档的关键步骤是将它们分割成"块"/Node对象。核心思想是将数据分解为便于检索和输入LLM的小块片段。

LlamaIndex支持广泛的文本分割器,从基于段落/句子/标记的分割器到如HTML、JSON等基于文件的分割器。

这些可以单独使用或作为数据摄取管道的一部分。

# 导入必要的类
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()

# 创建数据摄取管道
# IngestionPipeline允许您组合多个转换步骤
pipeline = IngestionPipeline(
    transformations=[
        TokenTextSplitter(chunk_size=512, chunk_overlap=10),  # 文本分割器
        # 可以添加更多转换器,如元数据提取器、嵌入生成器等
    ]
)

# 运行管道,将文档转换为节点
nodes = pipeline.run(documents=documents)

# 打印节点信息
print(f"生成了 {len(nodes)} 个节点")
for i, node in enumerate(nodes[:3]):
    print(f"节点 {i+1}: {node.text[:100]}...")

2.2.2 添加元数据 #

您还可以选择为您的文档和节点添加元数据。这一操作可以手动完成,也可以通过自动元数据提取器。

以下是关于:

  1. 如何自定义Documents
  2. 如何自定义Nodes

的详细指南。

# 导入Document类
from llama_index.core import Document

# 创建带有元数据的文档
# metadata参数可以包含任何键值对,用于描述文档的属性
document = Document(
    text="这是一个包含丰富元数据的示例文档。",
    metadata={
        "filename": "example.txt",           # 文件名
        "category": "技术文档",              # 文档类别
        "author": "张三",                    # 作者
        "created_date": "2024-01-01",       # 创建日期
        "tags": ["AI", "机器学习", "文档"],   # 标签列表
        "version": "1.0",                   # 版本号
        "language": "zh-CN"                 # 语言
    }
)

# 打印文档内容和元数据
print("文档内容:")
print(document.text)
print("\n文档元数据:")
for key, value in document.metadata.items():
    print(f"  {key}: {value}")

2.2.3 添加Embeddings #

要将节点插入向量索引,它必须包含一个embedding。请参阅我们的数据摄取管道或者我们的嵌入指南了解更多详情。

# 导入llama_index核心模块中的向量存储索引、设置项和简单目录读取器
from llama_index.core import VectorStoreIndex, Settings, SimpleDirectoryReader
# 导入OpenAI的嵌入模型
from llama_index.embeddings.openai import OpenAIEmbedding

# 读取指定目录下的数据文件,加载为文档对象
documents = SimpleDirectoryReader("./data").load_data()

# 设置全局嵌入模型为OpenAIEmbedding
Settings.embed_model = OpenAIEmbedding()

# 基于加载的文档创建向量存储索引
index = VectorStoreIndex.from_documents(documents)

# 通过索引对象创建查询引擎
query_engine = index.as_query_engine()

# 使用查询引擎对文档进行内容总结查询
response = query_engine.query("请总结一下这些文档的主要内容")
# 输出查询结果
print(response)

2.2.4 直接创建并传递Nodes #

如果您愿意,可以直接创建节点并将节点列表直接传递给索引器:

# 导入TextNode类,用于表示文本节点
from llama_index.core.schema import TextNode

# 导入VectorStoreIndex类,用于创建向量存储索引
from llama_index.core import VectorStoreIndex

# 创建第一个文本节点,包含文本内容和唯一标识符
node1 = TextNode(
    text="这是第一个文本块的内容。它包含了一些重要的信息。", id_="node_001"
)

# 创建第二个文本节点,包含文本内容和唯一标识符
node2 = TextNode(text="这是第二个文本块的内容。它包含了更多相关信息。", id_="node_002")

# 创建第三个文本节点,包含文本内容、唯一标识符和元数据信息
node3 = TextNode(
    text="这是第三个文本块的内容。",
    id_="node_003",
    metadata={"source": "手动创建", "category": "示例", "importance": "高"},
)

# 通过节点列表直接创建向量存储索引
index = VectorStoreIndex([node1, node2, node3])

# 通过索引对象创建查询引擎
query_engine = index.as_query_engine()

# 使用查询引擎进行测试查询
response = query_engine.query("这些节点包含了什么信息?")
# 打印查询结果
print(response)

# 打印索引中包含的节点数量
print(f"\n索引包含 {len([node1, node2, node3])} 个节点:")
# 遍历每个节点并打印其前50个字符内容
for i, node in enumerate([node1, node2, node3], 1):
    print(f"节点 {i}: {node.text[:50]}...")

3. Document VS Node #

3.1 基本概念 #

Document(文档):

  • 是LlamaIndex中表示原始数据的基本单位
  • 通常对应一个完整的文件或数据源
  • 包含文本内容和元数据
  • 是数据加载阶段的产物

Node(节点):

  • 是Document经过处理后的小块数据
  • 是索引和检索的基本单位
  • 也包含文本内容和元数据
  • 是数据转换阶段的产物

3.2 继承关系 #

3.2.1 Document和TextNode #

# 从llama_index.core模块导入Document类
from llama_index.core import Document

# 从llama_index.core.schema模块导入TextNode和BaseNode类
from llama_index.core.schema import TextNode, BaseNode

# 判断Document是否是TextNode的子类,并输出结果
print(f"Document是否是TextNode的子类: {issubclass(Document, TextNode)}")
# 判断TextNode是否是Document的子类,并输出结果
print(f"TextNode是否是Document的子类: {issubclass(TextNode, Document)}")

# 判断Document是否继承自BaseNode,并输出结果
print(f"Document是否是BaseNode的子类: {issubclass(Document, BaseNode)}")
# 判断TextNode是否继承自BaseNode,并输出结果
print(f"TextNode是否是BaseNode的子类: {issubclass(TextNode, BaseNode)}")

# 创建一个Document对象,包含文本内容和元数据
doc = Document(
    text="这是一个完整的文档内容。它可能很长,包含很多信息。",
    metadata={"source": "example.txt", "author": "张三"},
)

# 输出Document对象的文本内容
print(f"\n文档文本: {doc.text}")
# 输出Document对象的元数据
print(f"文档元数据: {doc.metadata}")
# 输出Document对象的节点ID
print(f"文档ID: {doc.node_id}")

# 创建一个TextNode对象用于对比,包含文本内容和元数据
node = TextNode(
    text="这是一个文本节点。",
    metadata={"source": "manual", "type": "example"},
)

# 输出TextNode对象的文本内容
print(f"\n节点文本: {node.text}")
# 输出TextNode对象的元数据
print(f"节点元数据: {node.metadata}")
# 输出TextNode对象的节点ID
print(f"节点ID: {node.node_id}")

3.2.2 类层次结构图 #

Document 的继承链:

  • Document → Node → BaseNode → BaseComponent → BaseModel → object

TextNode 的继承链:

  • TextNode → BaseNode → BaseComponent → BaseModel → object
BaseNode (抽象基类)
├── Node (中间类)
│   └── Document (文档类)
└── TextNode (文本节点类)

3.2.3 实际用途 #

  • Document:当你从文件读取内容时使用
  • TextNode:当文档被分割成小块时使用
  • 它们都实现了 BaseNode 的接口,所以可以互换使用

3.3 转换过程 #

Document到Node的转换是LlamaIndex数据处理的核心步骤:

# 导入Document类,用于表示文档对象
from llama_index.core import Document

# 导入SentenceSplitter类,用于将文档按句子分割成节点
from llama_index.core.node_parser import SentenceSplitter

# 创建一个示例文档对象,包含文本内容和元数据信息
original_document = Document(
    text="人工智能是计算机科学的一个分支。它致力于创建能够执行通常需要人类智能的任务的系统。机器学习是人工智能的一个子领域。深度学习是机器学习的一个分支。",
    metadata={
        "source": "ai_introduction.txt",
        "category": "技术文档",
        "author": "专家",
    },
)

# 打印原始文档的标题
print("原始文档:")
# 打印文档的文本内容
print(f"内容: {original_document.text}")
# 打印文档的元数据信息
print(f"元数据: {original_document.metadata}")
# 打印文档内容的长度(字符数)
print(f"长度: {len(original_document.text)} 字符")
# 打印空行用于分隔
print()

# 创建一个句子分割器,设置每个分块最大长度为50,重叠部分为5
text_splitter = SentenceSplitter(chunk_size=50, chunk_overlap=5)

# 使用分割器将文档分割成若干节点
nodes = text_splitter.get_nodes_from_documents([original_document])

# 打印分割后节点的数量
print(f"🔪 分割后生成了 {len(nodes)} 个节点:")
# 打印分隔线
print("-" * 50)

# 遍历每个节点,显示其详细信息
for i, node in enumerate(nodes, 1):
    # 打印节点编号
    print(f"节点 {i}:")
    # 打印节点的文本内容
    print(f"  内容: {node.text}")
    # 打印节点内容的长度(字符数)
    print(f"  长度: {len(node.text)} 字符")
    # 打印节点的元数据信息
    print(f"  元数据: {node.metadata}")
    # 打印节点的唯一ID
    print(f"  节点ID: {node.node_id}")
    # 打印空行用于分隔
    print()

3.4 关系特点 #

3.4.1 一对多关系 #

# 导入Document类,用于创建文档对象
from llama_index.core import Document

# 导入TokenTextSplitter类,用于将文档按token分割为多个节点
from llama_index.core.node_parser import TokenTextSplitter

# 创建一个长文档,内容为"123456"重复10次,附带元数据
long_document = Document(text="123456" * 10, metadata={"123": "456"})  # 重复10次

# 打印原始文档的长度(字符数)
print(f"原始文档长度: {len(long_document.text)} 字符")
# 打印原始文档的全部内容
print(f"原始文档内容: {long_document.text}")
# 打印原始文档的元数据信息
print(f"元数据: {long_document.metadata}")

# 创建一个token分割器,设置分块大小为10,重叠为0
token_splitter = TokenTextSplitter(chunk_size=10, chunk_overlap=0) #chunk_overlap=3
# 使用分割器将长文档分割为多个节点
nodes = token_splitter.get_nodes_from_documents([long_document])

# 打印分割过程分析的标题
print(f"\n=== 分割过程分析 ===")
# 打印分块大小的设置
print(f"设置的分块大小: {token_splitter.chunk_size} tokens")
# 打印重叠大小的设置
print(f"设置的重叠大小: {token_splitter.chunk_overlap} tokens")
# 打印分割后节点的数量
print(f"分割后节点数量: {len(nodes)}")

# 打印各节点详细分析的标题
print(f"\n=== 各节点详细分析 ===")
# 遍历每个节点,输出其内容、长度和元数据
for i, node in enumerate(nodes):
    # 打印当前节点编号
    print(f"节点 {i+1}:")
    # 打印当前节点的内容
    print(f"  内容: '{node.text}'")
    # 打印当前节点的内容长度
    print(f"  长度: {len(node.text)} 字符")
    # 打印当前节点的元数据
    print(f"  元数据: {node.metadata}")

# 打印统计信息的标题
print(f"\n=== 统计信息 ===")
# 计算所有节点内容的总长度
total_length = sum(len(node.text) for node in nodes)
# 计算每个节点内容的平均长度
avg_length = total_length / len(nodes)
# 打印所有节点内容的总长度
print(f"所有节点总长度: {total_length} 字符")
# 打印每个节点内容的平均长度
print(f"平均每个节点长度: {avg_length:.0f} 字符")

# 将所有节点的内容拼接起来
combined_text = "".join(node.text for node in nodes)
# 打印验证结果的标题
print(f"\n=== 验证结果 ===")
# 打印拼接后内容的总长度
print(f"节点内容组合后长度: {len(combined_text)} 字符")
# 判断拼接后内容是否与原始文档内容一致
print(f"内容是否一致: {combined_text == long_document.text}")
# 打印拼接后的全部内容
print(f"组合后内容: {combined_text}")

3.4.2 元数据继承 #

# 导入Document类,用于创建带有元数据的文档对象
from llama_index.core import Document
# 导入SentenceSplitter类,用于将文档按句子分割为节点
from llama_index.core.node_parser import SentenceSplitter

# 创建一个包含丰富元数据的文档对象
document_with_metadata = Document(
    text="这是文档内容。包含重要信息。",  # 文档的正文内容
    metadata={  # 文档的元数据信息
        "source": "important_doc.txt",  # 来源文件名
        "category": "重要文档",          # 文档类别
        "priority": "高",                # 优先级
        "created_date": "2024-01-01",   # 创建日期
        "author": "张三",                # 作者
    },
)

# 创建句子分割器实例
splitter = SentenceSplitter()
# 使用分割器将文档分割成节点,每个节点会继承文档的元数据
nodes = splitter.get_nodes_from_documents([document_with_metadata])

# 打印元数据继承演示标题
print("元数据继承演示:")
# 打印原始文档的元数据
print(f"原始文档元数据: {document_with_metadata.metadata}")
print()

# 遍历所有分割得到的节点
for i, node in enumerate(nodes, 1):
    # 打印每个节点的元数据
    print(f"节点 {i} 元数据: {node.metadata}")
    # 遍历原始文档的每一项元数据,验证节点是否继承
    for key, value in document_with_metadata.metadata.items():
        assert node.metadata[key] == value, f"元数据 {key} 不匹配"  # 若不匹配则抛出异常
    # 打印继承验证通过的信息
    print("✅ 元数据继承正确")
    print()

3.5 实际应用场景 #

3.5.1 文档检索优化 #

# 导入Document和VectorStoreIndex类,用于文档对象和向量索引
from llama_index.core import Document, VectorStoreIndex

# 导入SentenceSplitter类,用于将文档按句子分割为节点
from llama_index.core.node_parser import SentenceSplitter

# 创建一个包含三篇示例文档的列表,每篇文档都带有元数据
documents = [
    Document(
        # 文档正文内容,介绍人工智能
        text="人工智能(AI)是计算机科学的一个分支,致力于创建能够执行通常需要人类智能的任务的系统。",
        # 文档元数据,包含主题和难度
        metadata={"topic": "AI基础", "difficulty": "初级"},
    ),
    Document(
        # 文档正文内容,介绍机器学习
        text="机器学习是人工智能的一个子领域,它使计算机能够在没有明确编程的情况下学习和改进。",
        # 文档元数据,包含主题和难度
        metadata={"topic": "机器学习", "difficulty": "中级"},
    ),
    Document(
        # 文档正文内容,介绍深度学习
        text="深度学习是机器学习的一个分支,使用多层神经网络来模拟人脑的学习过程。",
        # 文档元数据,包含主题和难度
        metadata={"topic": "深度学习", "difficulty": "高级"},
    ),
]

# 打印演示标题
print("🔍 文档检索优化演示:")
# 打印分隔线
print("=" * 50)

# 方法1:直接用文档对象创建向量索引
print("方法1: 直接使用文档")
# 从文档列表创建向量索引
index1 = VectorStoreIndex.from_documents(documents)
# 获取查询引擎
query_engine1 = index1.as_query_engine()
# 对索引进行查询,问题为“什么是机器学习?”
response1 = query_engine1.query("什么是机器学习?")
# 打印查询结果
print(f"回答: {response1}")
# 打印空行分隔
print()

# 方法2:先将文档分割成节点,再创建向量索引
print("方法2: 先分割成节点")
# 创建句子分割器,设置分块大小为50,重叠为5
splitter = SentenceSplitter(chunk_size=50, chunk_overlap=5)
# 使用分割器将文档分割为节点
nodes = splitter.get_nodes_from_documents(documents)
# 用分割后的节点创建向量索引
index2 = VectorStoreIndex(nodes)
# 获取查询引擎
query_engine2 = index2.as_query_engine()
# 对索引进行查询,问题为“什么是机器学习?”
response2 = query_engine2.query("什么是机器学习?")
# 打印查询结果
print(f"回答: {response2}")

3.5.2 粒度控制 #

  • 方法1:每个文档作为一个整体进行向量化,检索时匹配整个文档
  • 方法2:将文档分割成更小的语义单元(chunk_size=50字符),检索时匹配更精确的片段

3.5.3 语义精确性 #

# 原始文档
"机器学习是人工智能的一个子领域,它使计算机能够在没有明确编程的情况下学习和改进。"

# 分割后可能变成多个节点:
节点1: "机器学习是人工智能的一个子领域"
节点2: "它使计算机能够在没有明确编程的情况下学习和改进"

分割后的优势:

  • 更小的向量维度,计算效率更高
  • 更精确的语义匹配
  • 减少噪声信息干扰

3.5.4 实际效果对比 #

特性 方法1(直接文档) 方法2(分割节点)
检索粒度 粗粒度(整文档) 细粒度(语义片段)
匹配精度 较低 较高
计算效率 中等 较高
上下文保持 完整 通过重叠保持

3.6 总结 #

Document和Node的关系可以概括为:

  1. 继承关系:Document是Node的子类,具有Node的所有功能
  2. 转换关系:Document通过分割器转换为多个Node
  3. 一对多关系:一个Document可以产生多个Node
  4. 元数据继承:Node会继承Document的元数据
  5. 用途不同:
    • Document:数据加载阶段的基本单位
    • Node:索引和检索阶段的基本单位

这种设计使得LlamaIndex能够:

  • 灵活处理不同大小的文档
  • 优化检索精度和效率
  • 保持数据的完整性和关联性
  • 支持复杂的文档处理流程

4. 总结 #

数据加载和转换是构建RAG应用的基础步骤。通过LlamaIndex提供的各种工具,您可以:

  1. 灵活加载数据:从文件系统、数据库、API等多种数据源加载数据
  2. 智能转换数据:将原始数据转换为适合LLM处理的结构化格式
  3. 自定义处理流程:根据具体需求调整数据处理的各个环节
  4. 高效存储索引:将处理后的数据存储到向量索引中,便于后续检索

掌握这些技术将帮助您构建更强大、更智能的AI应用程序。

访问验证

请输入访问令牌

Token不正确,请重新输入