Langchain系列文章目录
01-玩转LangChain:从模型调用到Prompt模板与输出解析的完整指南
02-玩转 LangChain Memory 模块:四种记忆类型详解及应用场景全覆盖
03-全面掌握 LangChain:从核心链条构建到动态任务分配的实战指南
04-玩转 LangChain:从文档加载到高效问答系统构建的全程实战
05-玩转 LangChain:深度评估问答系统的三种高效方法(示例生成、手动评估与LLM辅助评估)
06-从 0 到 1 掌握 LangChain Agents:自定义工具 + LLM 打造智能工作流!
07-【深度解析】从GPT-1到GPT-4:ChatGPT背后的核心原理全揭秘
08-【万字长文】MCP深度解析:打通AI与世界的“USB-C”,模型上下文协议原理、实践与未来
Python系列文章目录
PyTorch系列文章目录
机器学习系列文章目录
深度学习系列文章目录
Java系列文章目录
JavaScript系列文章目录
Llamaindex系列文章目录
01-【LlamaIndex核心组件指南 | 模型篇】一文通晓 LlamaIndex 模型层:LLM、Embedding 及多模态应用全景解析
02-【LlamaIndex核心组件指南 | Prompt篇】深度解析LlamaIndex提示模板的设计与实战
03-【LlamaIndex核心组件指南 | 数据加载篇】从原始数据到向量的全链路深度解析
文章目录
前言
在人工智能技术快速发展的背景下,大语言模型(LLM)
虽然能力强大,但其知识往往局限于训练数据,无法直接访问我们私有的、实时的外部数据源。如何安全、高效地将 LLM 与我们的数据连接起来,构建强大的检索增强生成(RAG)
应用,已成为开发者的核心议题。LlamaIndex
正是为解决这一问题而生的。
LlamaIndex 是一个领先的开源数据框架,旨在帮助开发者轻松构建、优化和部署基于自定义数据的 LLM 应用。通过 LlamaIndex,开发者可以无缝集成数据加载、索引构建、查询引擎、响应合成等一系列复杂功能,极大地简化了 RAG 应用的开发流程。
LlamaIndex 的核心组件
LlamaIndex 由一系列高度模块化的组件构成,每个组件都专注于 RAG 流程中的特定任务:
-
Loading (数据加载)
- 提供数百个数据连接器(Data Connectors),用于从各种来源(如本地文件、Notion、数据库、API)摄取数据。
- 将加载的数据统一转换为标准的
Document
对象,便于后续处理。
-
Indexing (索引构建)
- 负责将非结构化或结构化的
Document
数据转换为 LLM 能够高效查询的数据结构(即索引)。 - 支持多种索引类型,如
向量存储索引 (VectorStoreIndex)
、知识图谱索引 (PropertyGraphIndex)
等。
- 负责将非结构化或结构化的
-
Storing (持久化存储)
- 管理数据和索引的持久化,确保应用的可扩展性和状态保持。
- 包含
文档存储 (Docstore)
、索引存储 (IndexStore)
和向量存储 (VectorStore)
三大组件。
-
Querying (查询引擎)
- 是 RAG 的核心执行者,接收用户查询,并从索引中检索相关信息。
- 通过
响应合成 (Response Synthesis)
模块将检索到的上下文和用户查询整合,生成最终答案。
-
Models (模型)
- 提供与各种
大语言模型 (LLMs)
、嵌入模型 (Embeddings)
和多模态模型 (Multi-modal Models)
交互的统一接口。 - 是驱动整个应用理解、表示和生成信息的大脑。
- 提供与各种
-
Agents (智能体)
- 赋予 LLM 超越简单问答的能力,使其能够使用外部工具(如 API 调用、数据库查询)来执行多步骤的复杂任务。
- 是构建自动化工作流和实现更高级别自主性的关键。
LlamaIndex 的应用场景
LlamaIndex 的模块化和 RAG 专注设计使其在以下场景中表现出色:
- 智能问答与知识库:构建基于海量私有文档(PDF、Word、Notion)、数据库或企业知识图谱的智能问答机器人。
- 文档理解与摘要:对大量、复杂的非结构化文档进行深度分析、信息提取和自动化摘要。
- 结构化数据分析:连接到 SQL 或图数据库,用自然语言查询结构化数据并获得分析结果。
- 自主研究智能体:创建能够主动查询外部数据源、执行代码、并综合信息生成研究报告的自动化智能体。
- 多模态 RAG 应用:构建能够同时理解文本和图像内容,并基于图文信息进行问答的应用。
摘要
本文深度剖析了 LlamaIndex 框架中最为核心的数据处理链路,旨在为 RAG (Retrieval-Augmented Generation) 应用开发者提供一份详尽、专业且易于理解的技术指南。文章对 LlamaIndex 官网的组件文档进行了系统性的结构重组与价值提升,从基础的数据抽象 Document
与 Node
讲起,逐步深入到多样化的数据加载器(如 SimpleDirectoryReader
、LlamaHub
)、精细化的文本切分与节点解析器 (Node Parser
),再到利用大语言模型(LLM)进行自动化元数据提取,最后聚焦于终极利器——Ingestion Pipeline
,它如何将整个数据处理流程串联、缓存并实现高效的自动化。本文通过丰富的代码示例、操作步骤和应用场景,帮助初学者快速上手,同时为进阶者提供高级特性的深度解读,如图文档管理、并行处理和自定义转换等。无论您是刚刚接触 LlamaIndex,还是希望优化现有 RAG 应用的数据处理流程,本文都将为您提供坚实的理论基础和宝贵的实战经验。
一、LlamaIndex 概览:构建 RAG 应用的基石
LlamaIndex 是一个强大的数据框架,旨在帮助开发者轻松地将自定义数据源与大型语言模型(LLM)连接起来。它提供了一整套工具,用于数据的获取(Ingestion)、索引(Indexing)、查询(Querying),是构建和部署生产级 RAG 应用的关键。其核心思想在于,通过高效地处理和组织外部数据,为 LLM 提供精准、相关的上下文,从而显著提升回答的准确性和相关性。
LlamaIndex 的架构是模块化的,由一系列可插拔的组件构成。理解这些核心组件是高效使用 LlamaIndex 的前提。下表概览了其主要构成部分:
核心模块 (Category) | 组件 (Component) | 核心功能 |
---|---|---|
模型 (Models) | LLMs , Embeddings , Multi Modal | 封装语言模型、嵌入模型以及多模态模型,负责文本生成、推理和数据向量化。 |
提示词 (Prompts) | Prompt Templates | 提供灵活的提示词工程能力,指导 LLM 如何使用上下文并生成回应。 |
数据加载 (Loading) | Data Connectors , Node Parsers | 从各种来源加载数据,并将其解析、切分为标准化的 Document 和 Node 结构。 |
数据索引 (Indexing) | Vector Store Index , Property Graph Index | 构建数据索引,以便于后续的高效检索。最常见的是基于向量的语义索引。 |
数据存储 (Storing) | Vector Stores , Document Stores , Index Stores | 持久化存储嵌入向量、文档内容以及索引元数据。 |
数据查询 (Querying) | Query Engines , Chat Engines , Retrievers | 负责接收用户查询、从索引中检索相关上下文、并合成最终的、连贯的回答。 |
高级组件 (Advanced) | Agents , Workflows , Evaluation | 提供构建智能体、复杂工作流以及评估 RAG 应用性能的高级功能。 |
本文将聚焦于 数据加载 (Loading) 这一关键阶段,它是一切后续步骤的基础。我们将深入探讨从原始文件到可供索引的 Node
对象的完整生命周期。
二、数据基石:深入理解 Document
与Node
在 LlamaIndex 中,所有的数据处理都围绕两个核心的抽象概念展开:Document
和 Node
。
2.1.1 Document:通用数据容器
Document
是 LlamaIndex 中表示任意数据源的通用容器。它可以是一个 PDF 文件、一个 API 的返回结果、或者数据库中的一条记录。Document
对象主要包含文本内容以及一系列描述性属性。
- 核心属性:
text
: 文档的主要文本内容。metadata
: 一个字典,用于存储关于数据的任意附加信息(元数据),如文件名、类别、创建日期等。这些元数据可以用于过滤、增强检索和溯源。relationships
: 一个字典,定义了该Document
与其他Document
或Node
之间的关系。
(1) 创建 Document 对象
Document
对象既可以由数据加载器自动创建,也可以手动进行实例化。
自动创建 (推荐):使用 LlamaIndex 提供的数据加载器(如 SimpleDirectoryReader
或 LlamaHub 上的连接器)可以自动将原始文件或数据源转换为 Document
对象列表。
from llama_index.core import SimpleDirectoryReader
# 加载器会自动为目录下的每个文件创建一个 Document 对象
documents = SimpleDirectoryReader("./data").load_data()
手动创建:在某些场景下,您可能需要根据内存中的文本列表或其他数据源手动构建 Document
。
from llama_index.core import Document
text_list = ["这是第一段文本。", "这是第二段文本。", "这是第三段文本。"]
documents = [Document(text=t) for t in text_list]
# 为了快速原型验证,也可以创建一个示例 Document
document = Document.example()
(2) 自定义 Document
Document
对象提供了丰富的自定义选项,以满足复杂的应用需求。
1. 添加元数据 (Metadata)
元数据对于后续的检索和响应生成至关重要。默认情况下,元数据会与文本内容一起被用于生成嵌入(Embedding)和传入 LLM。
# 方式一:在构造函数中指定
document = Document(
text="这是一段关于金融的文本...",
metadata={"filename": "finance_report.txt", "category": "finance"},
)
# 方式二:创建后动态添加
document.metadata = {"filename": "finance_report.txt"}
# 方式三:使用 SimpleDirectoryReader 的 file_metadata 钩子函数自动添加
# 该函数会为每个读取的文件自动调用,并将返回值作为 metadata
filename_fn = lambda filename: {"file_name": filename}
documents = SimpleDirectoryReader(
"./data", file_metadata=filename_fn
).load_data()
2. 自定义文档ID (doc_id)
doc_id
是 Document
的唯一标识符,对于在索引中更新或刷新文档至关重要。
from llama_index.core import SimpleDirectoryReader
# 使用 SimpleDirectoryReader 时,可将文件路径作为 doc_id
documents = SimpleDirectoryReader("./data", filename_as_id=True).load_data()
print([x.doc_id for x in documents])
# 也可以直接手动设置
doc = Document(text="...")
doc.doc_id = "my_custom_doc_id_123"
注意:doc_id
也可以通过 id_
或 node_id
属性进行设置,因为它继承自 TextNode
。
3. 高级元数据定制
有时,我们希望某些元数据(如文件名)能影响嵌入的生成(以提高检索相关性),但又不希望它被 LLM 读取(以避免干扰回答的生成)。LlamaIndex 提供了精确控制元数据可见性的功能。
excluded_llm_metadata_keys
: 设置在此列表中的元数据键将不会被 LLM 看到。excluded_embed_metadata_keys
: 设置在此列表中的元数据键将不会被用于生成嵌入。
此外,元数据注入文本时的格式也可以自定义:
metadata_seperator
: 多个元数据键值对之间的分隔符,默认为"\n"
。metadata_template
: 每个键值对的格式化模板,默认为"{key}: {value}"
。text_template
: 最终元数据字符串与原始文本内容结合的模板,默认为{metadata_str}\n\n{content}
。
下面是一个综合示例:
from llama_index.core import Document
from llama_index.core.schema import MetadataMode
document = Document(
text="这是一个高度定制化的文档。",
metadata={
"file_name": "super_secret_document.txt",
"category": "finance",
"author": "LlamaIndex",
},
# LLM 将看不到 file_name
excluded_llm_metadata_keys=["file_name"],
# 自定义元数据格式
metadata_seperator=" | ",
metadata_template="{key} -> {value}",
text_template="[元数据]: {metadata_str}\n-----\n[正文]: {content}",
)
# 查看 LLM 将会看到的内容
print(
"LLM 看到的内容: \n",
document.get_content(metadata_mode=MetadataMode.LLM),
)
# 查看嵌入模型将看到的内容
print(
"\nEmbedding 模型看到的内容: \n",
document.get_content(metadata_mode=MetadataMode.EMBED),
)
2.1.2 Node:文档的数据块
Node
代表源 Document
的一个“块”(chunk)。它可以是文本块、图像块或其他类型的数据。Node
是 LlamaIndex 中的一等公民,它继承了其父 Document
的元数据,并包含与其他 Node
的关系信息。将 Document
分割成 Node
是构建索引前的关键一步,因为 LLM 的上下文窗口大小有限,且更小的、更具针对性的文本块通常能带来更好的检索效果。
(1) 创建 Node 对象
Node
对象通常由 NodeParser
(文本切分器)从 Document
列表中自动生成。
from llama_index.core import Document
from llama_index.core.node_parser import SentenceSplitter
# 假设 documents 已经加载
documents = [Document(text="这是一个很长很长的文档,需要被切分成多个节点...")]
# 使用句子切分器创建 Nodes
parser = SentenceSplitter(chunk_size=100, chunk_overlap=10)
nodes = parser.get_nodes_from_documents(documents)
当然,您也可以完全手动创建 Node
对象,并定义它们之间的关系,这在构建复杂的索引结构(如图索引)时非常有用。
from llama_index.core.schema import TextNode, NodeRelationship, RelatedNodeInfo
node1 = TextNode(text="这是第一个节点。", id_="node-1")
node2 = TextNode(text="这是第二个节点。", id_="node-2")
# 定义节点关系:node1 的下一个节点是 node2
node1.relationships[NodeRelationship.NEXT] = RelatedNodeInfo(
node_id=node2.node_id
)
# 定义节点关系:node2 的上一个节点是 node1
node2.relationships[NodeRelationship.PREVIOUS] = RelatedNodeInfo(
node_id=node1.node_id
)
# 关系信息中还可以附加元数据
node2.relationships[NodeRelationship.PARENT] = RelatedNodeInfo(
node_id="document_id_abc", metadata={"source_type": "pdf_page"}
)
nodes = [node1, node2]
三、数据加载:从万物到 Document
数据加载是将外部世界的数据转换为 LlamaIndex 内部 Document
对象的入口。LlamaIndex 提供了多种灵活的加载方式。
3.1 SimpleDirectoryReader
:本地文件加载利器
SimpleDirectoryReader
是从本地文件系统加载数据最简单直接的方式,非常适合快速启动项目。
(1) 支持的文件类型
它默认支持多种常见文件类型,并能根据文件扩展名自动选择合适的解析器:
- 文本文件:
.txt
,.md
- 文档:
.csv
,.docx
,.pdf
,.epub
,.hwp
- 演示文稿:
.ppt
,.pptm
,.pptx
- 其他:
.ipynb
(Jupyter Notebook),.mbox
(邮件存档) - 图像/音视频:
.jpeg
,.jpg
,.png
,.mp3
,.mp4
对于 .json
文件,推荐使用专门的 JSONLoader
。
(2) 核心用法
from llama_index.core import SimpleDirectoryReader
# 1. 基本用法:加载指定目录下的所有支持文件
reader = SimpleDirectoryReader(input_dir="./my_data")
documents = reader.load_data()
# 2. 并行加载:通过 num_workers 加速多文件加载
documents = reader.load_data(num_workers=4)
# 3. 递归加载:读取所有子目录中的文件
recursive_reader = SimpleDirectoryReader(input_dir="./my_data", recursive=True)
# 4. 迭代加载:逐个文件加载并处理
all_docs = []
for docs_per_file in recursive_reader.iter_data():
# 对单个文件的文档列表进行处理
all_docs.extend(docs_per_file)
# 5. 文件筛选
# 只加载指定文件
file_reader = SimpleDirectoryReader(input_files=["./data/file1.pdf", "./data/file2.txt"])
# 排除指定文件
exclude_reader = SimpleDirectoryReader(input_dir="./data", exclude=["./data/temp.txt"])
# 只加载特定扩展名的文件
ext_reader = SimpleDirectoryReader(input_dir="./data", required_exts=[".pdf", ".docx"])
# 6. 指定文件编码
encoding_reader = SimpleDirectoryReader(input_dir="./data", encoding="latin-1")
(3) 扩展与定制
自定义元数据提取:如前所述,可以使用 file_metadata
参数传入一个函数来定制每个文件的元数据。
扩展支持新文件类型:通过 file_extractor
参数,您可以为 SimpleDirectoryReader
添加对新文件类型的支持。
from llama_index.core.readers.base import BaseReader
from llama_index.core import Document, SimpleDirectoryReader
# 自定义一个读取 .myfile 文件的 Reader
class MyFileReader(BaseReader):
def load_data(self, file, extra_info=None):
with open(file, "r", encoding="utf-8") as f:
text = f.read()
return [Document(text=text + " [Custom Loaded]", extra_info=extra_info or {})]
# 将 .myfile 扩展名映射到自定义的 Reader 实例
reader = SimpleDirectoryReader(
input_dir="./data", file_extractor={".myfile": MyFileReader()}
)
documents = reader.load_data()
print(documents)
支持远程文件系统:通过 fs
参数,SimpleDirectoryReader
可以读取如 AWS S3、Google Drive 等远程文件系统中的数据,前提是安装了相应的 fsspec
库(如 s3fs
)。
# 示例:从 S3 加载数据
from s3fs import S3FileSystem
from llama_index.core import SimpleDirectoryReader
s3_fs = S3FileSystem(key="...", secret="...")
bucket_name = "my-document-bucket"
reader = SimpleDirectoryReader(
input_dir=bucket_name,
fs=s3_fs,
recursive=True,
)
documents = reader.load_data()
3.2 LlamaHub
数据连接器:连接无限数据源
当您的数据源不是本地文件时,LlamaHub
就是您的宝库。它是一个开源的、由社区贡献的庞大数据加载器(Reader
)仓库,支持数百种数据源,如 Notion, Slack, Discord, Google Docs, 数据库等。
使用模式:
使用 LlamaHub
的核心是 download_loader
函数,它会从仓库下载并加载指定的 Reader
模块。
from llama_index.core import VectorStoreIndex, download_loader
# 假设我们要从 Google Docs 加载
# from llama_index.readers.google import GoogleDocsReader # 新版本可以直接导入
# 使用 download_loader (适用于社区贡献的加载器)
GoogleDocsReader = download_loader("GoogleDocsReader")
# 你的 Google Doc ID 列表
gdoc_ids = ["your_google_doc_id_here"]
loader = GoogleDocsReader()
documents = loader.load_data(document_ids=gdoc_ids)
# 后续可以构建索引
index = VectorStoreIndex.from_documents(documents)
3.3 LlamaParse
:专业级文档解析服务
LlamaParse
是 LlamaIndex 官方推出的一项托管API服务,专为高效解析复杂文档(特别是PDF)而设计。它能够更好地处理表格、图表和复杂的布局,生成更适合RAG应用的结构化表示。它支持 PDF, Word, PowerPoint, Excel 等多种文件格式,并能与 LlamaIndex 无缝集成。
四、文本切分与节点解析 (Node Parser
)
加载 Document
之后,下一步是将其切分为更小的 Node
块,这个过程由 NodeParser
(也称为 TextSplitter
)完成。选择合适的切分策略对 RAG 的最终效果至关重要。
通用使用模式:
NodeParser
可以独立使用,也可以作为转换(Transformation)步骤集成到索引构建或 Ingestion Pipeline
中。
from llama_index.core import Document, VectorStoreIndex, Settings
from llama_index.core.node_parser import SentenceSplitter
# 1. 独立使用
node_parser = SentenceSplitter(chunk_size=512, chunk_overlap=20)
nodes = node_parser.get_nodes_from_documents([Document(text="...")])
# 2. 在构建索引时作为转换步骤
index = VectorStoreIndex.from_documents(
documents,
transformations=[SentenceSplitter(chunk_size=512, chunk_overlap=20)],
)
# 3. 设置为全局默认切分器
Settings.text_splitter = SentenceSplitter(chunk_size=512, chunk_overlap=20)
# 之后所有 .from_documents() 调用都会默认使用此切分器
index_global = VectorStoreIndex.from_documents(documents)
LlamaIndex 提供了多种类型的 NodeParser
:
4.1 基于文本内容的切分器
这类切分器关注文本本身,而不关心其原始文件格式。
TokenTextSplitter
: 基于 Token 数量进行切分,是最基础的切分方式。from llama_index.core.node_parser import TokenTextSplitter splitter = TokenTextSplitter(chunk_size=256, chunk_overlap=20, separator=" ")
SentenceSplitter
: 尝试在句子边界进行切分,尽可能保持句子的完整性,是推荐的通用切分器。from llama_index.core.node_parser import SentenceSplitter splitter = SentenceSplitter(chunk_size=1024, chunk_overlap=20)
SemanticSplitterNodeParser
: “语义切分”,这是一种更智能的切分方法。它不依赖固定的块大小,而是通过计算句子之间嵌入向量的相似度来决定断点,从而确保每个Node
内部的句子在语义上是高度相关的。from llama_index.core.node_parser import SemanticSplitterNodeParser from llama_index.embeddings.openai import OpenAIEmbedding splitter = SemanticSplitterNodeParser( buffer_size=1, breakpoint_percentile_threshold=95, embed_model=OpenAIEmbedding() )
CodeSplitter
: 专门用于切分代码文件,支持多种编程语言。from llama_index.core.node_parser import CodeSplitter splitter = CodeSplitter(language="python", chunk_lines=40, chunk_lines_overlap=15)
4.2 基于文件结构的解析器
这类解析器理解特定文件的结构,如 HTML, JSON, Markdown。
HTMLNodeParser
: 使用BeautifulSoup
解析HTML,并根据指定的标签(如p
,h1
,li
)提取内容块。JSONNodeParser
: 解析 JSON 数据。MarkdownNodeParser
: 解析 Markdown 文本。
4.3 高级切分策略
SentenceWindowNodeParser
: 此解析器将文档切分为单个句子,但在每个Node
的元数据中包含了该句子前后的“上下文窗口”(即周围的句子)。这使得在生成嵌入时可以聚焦于单个句子的精确语义,而在后续传递给 LLM 时,可以通过后处理器(MetadataReplacementNodePostProcessor
)将上下文窗口替换回来,为 LLM 提供更完整的信息。from llama_index.core.node_parser import SentenceWindowNodeParser parser = SentenceWindowNodeParser.from_defaults(window_size=3)
HierarchicalNodeParser
: “层级节点解析器”,它会将文档切分成多个不同尺寸的块(例如,2048、512、128个字符),并建立它们之间的父子关系。当与AutoMergingRetriever
结合使用时,如果检索器发现多个较小的子节点被命中,它会自动向上合并,返回它们的父节点,从而为 LLM 提供一个更宏观、更完整的上下文。from llama_index.core.node_parser import HierarchicalNodeParser parser = HierarchicalNodeParser.from_defaults(chunk_sizes=[2048, 512, 128])
五、自动化元数据提取
除了手动添加元数据,LlamaIndex 还可以利用 LLM 的能力来自动从文本中提取有价值的元数据,进一步增强 Node
的信息含量。
MetadataExtractor
模块可以实现这一功能,它包含多种“特征提取器”:
SummaryExtractor
: 为每个Node
生成一段摘要。QuestionsAnsweredExtractor
: 为每个Node
提取它能够回答的一系列问题。TitleExtractor
: 为每个Node
的内容提取一个标题。EntityExtractor
: 从Node
的内容中提取实体(如人名、地名、组织名)。
这些提取器可以无缝地集成到数据处理流程中,通常是在文本切分之后。
from llama_index.core.extractors import TitleExtractor, QuestionsAnsweredExtractor
from llama_index.core.node_parser import TokenTextSplitter
from llama_index.core.ingestion import IngestionPipeline
# 1. 定义转换链
text_splitter = TokenTextSplitter(separator=" ", chunk_size=512, chunk_overlap=128)
title_extractor = TitleExtractor(nodes=5) # nodes=5表示每5个节点生成一个标题
qa_extractor = QuestionsAnsweredExtractor(questions=3) # questions=3表示每个节点提取3个问题
# 2. 在 Ingestion Pipeline 中使用
pipeline = IngestionPipeline(
transformations=[text_splitter, title_extractor, qa_extractor]
)
nodes = pipeline.run(documents=documents, show_progress=True)
# 3. 或者直接在构建索引时使用
index = VectorStoreIndex.from_documents(
documents, transformations=[text_splitter, title_extractor, qa_extractor]
)
执行后,提取出的标题、问题等会自动添加到每个 Node
的 metadata
字典中。
六、终极武器:构建高效 Ingestion Pipeline
IngestionPipeline
(摄取流水线) 是 LlamaIndex 中用于实现端到端、可重复、可缓存的数据处理流程的核心组件。它将一系列的 Transformation
(转换)应用于输入的 Document
,最终生成 Node
列表,并可以选择性地将其存入向量数据库。
6.1 核心用法
(1) 定义并运行流水线
最简单的用法是定义一个包含多个转换步骤的流水线。
from llama_index.core import Document
from llama_index.embeddings.openai import OpenAIEmbedding
from llama_index.core.node_parser import SentenceSplitter
from llama_index.core.extractors import TitleExtractor
from llama_index.core.ingestion import IngestionPipeline
# 定义流水线,包含切分、标题提取和嵌入生成三个步骤
pipeline = IngestionPipeline(
transformations=[
SentenceSplitter(chunk_size=256, chunk_overlap=20),
TitleExtractor(),
OpenAIEmbedding(), # 注意:如果要存入向量库,必须包含嵌入生成
]
)
# 运行流水线
# 在真实场景中,documents 来自 SimpleDirectoryReader 等加载器
nodes = pipeline.run(documents=[Document.example()])
(2) 连接向量数据库
您可以将流水线直接连接到一个向量数据库,处理后的 Node
会被自动存入。
import qdrant_client
from llama_index.vector_stores.qdrant import QdrantVectorStore
# 假设使用 Qdrant 作为向量库
client = qdrant_client.QdrantClient(location=":memory:")
vector_store = QdrantVectorStore(client=client, collection_name="my_collection")
pipeline = IngestionPipeline(
transformations=[
SentenceSplitter(chunk_size=256, chunk_overlap=20),
TitleExtractor(),
OpenAIEmbedding(),
],
vector_store=vector_store, # 指定向量库
)
# 直接运行,节点将被自动处理并存入 Qdrant
pipeline.run(documents=[Document.example()])
# 后续可以直接从向量库构建索引,无需再次处理数据
from llama_index.core import VectorStoreIndex
index = VectorStoreIndex.from_vector_store(vector_store)
6.2 高级特性
(1) 缓存 (Caching)
IngestionPipeline
的一大优势是其内置的缓存机制。它会为每个(输入节点 + 转换步骤)的组合计算一个哈希值,并将结果缓存起来。当再次运行流水线处理相同的数据时,它会直接从缓存中读取结果,从而极大地节省了时间和API调用成本(特别是对于嵌入生成)。
-
本地缓存管理:
# 将流水线状态(包括缓存)持久化到本地磁盘 pipeline.persist("./pipeline_storage") # 在新的会话中,加载已有的流水线状态 new_pipeline = IngestionPipeline(transformations=[...]) # 需定义相同的转换 new_pipeline.load("./pipeline_storage") # 再次运行相同的数据,会瞬间完成 nodes = new_pipeline.run(documents=[Document.example()])
-
远程缓存管理: 支持 Redis, MongoDB, Firestore 等后端。
from llama_index.storage.kvstore.redis import RedisKVStore as RedisCache from llama_index.core.ingestion import IngestionCache # 使用 Redis 作为缓存后端 ingest_cache = IngestionCache( cache=RedisCache.from_host_and_port(host="127.0.0.1", port=6379), collection="my_cache_collection", ) pipeline = IngestionPipeline( transformations=[...], cache=ingest_cache, ) # 运行流水线,缓存会自动写入 Redis,无需手动 persist
(2) 文档管理 (Document Management)
通过为流水线配置 docstore
,可以激活文档管理功能。它利用 document.doc_id
来跟踪和管理文档的更新。
- 工作机制:
- 流水线维护一个
doc_id
到文档哈希值的映射。 - 当遇到一个已存在的
doc_id
时,它会比较新旧文档的哈希值。 - 如果哈希值已改变,说明文档已更新,流水线将重新处理该文档并更新(upsert)向量库中的相应节点。
- 如果哈希值未变,说明文档未更新,则跳过处理,节省资源。
- 流水线维护一个
from llama_index.core.storage.docstore import SimpleDocumentStore
pipeline = IngestionPipeline(
transformations=[...],
docstore=SimpleDocumentStore(),
vector_store=vector_store,
)
(3) 并行处理 (Parallel Processing)
通过设置 num_workers
参数,可以利用多进程并行执行数据转换,显著提升处理大量文档时的速度。
pipeline.run(documents=documents, num_workers=4)
(44) 异步支持 (Async Support)
IngestionPipeline
也完全支持异步操作。
nodes = await pipeline.arun(documents=documents)
七、Transformation
抽象:自定义你的处理流程
在 IngestionPipeline
的背后,是统一的 Transformation
抽象。任何实现了 TransformComponent
基类的组件都可以被视为一个转换步骤,并被无缝地集成到流水线中。
目前,以下核心组件都是 Transformation
的实例:
TextSplitter
/NodeParser
MetadataExtractor
Embedding
模型
这意味着你可以轻松地创建自定义的数据处理步骤。
示例:创建一个自定义的文本清理转换
下面的 TextCleaner
会移除节点文本中的所有非字母数字字符。
import re
from llama_index.core.schema import TransformComponent
class TextCleaner(TransformComponent):
def __call__(self, nodes, **kwargs):
for node in nodes:
# 使用正则表达式移除特殊字符和标点
node.text = re.sub(r"[^0-9A-Za-z ]", "", node.text)
return nodes
async def acall(self, nodes, **kwargs):
# 异步版本(可选实现)
return self.__call__(nodes, **kwargs)
# 在流水线中使用自定义转换
pipeline = IngestionPipeline(
transformations=[
SentenceSplitter(chunk_size=256, chunk_overlap=0),
TextCleaner(), # 将自定义组件加入流水线
OpenAIEmbedding(),
],
)
八、总结
本文系统地梳理了 LlamaIndex 框架中数据加载和处理的核心链路,通过对官方文档的重构和深化,我们描绘了一幅从异构数据源到结构化、可检索的 Node
对象的完整路线图。
核心要点回顾:
- 两大基石:
Document
作为通用数据容器,Node
作为其原子化的数据块,是 LlamaIndex 数据处理的基础。深入理解并善用它们的metadata
和relationship
属性是优化 RAG 应用的关键。 - 三类加载器:
SimpleDirectoryReader
满足本地文件加载需求,LlamaHub
连接海量第三方数据源,而LlamaParse
为复杂文档的解析提供了专业解决方案。 - 多维切分策略:
NodeParser
的选择直接影响检索效果。从基础的Token/SentenceSplitter
,到智能的SemanticSplitter
,再到面向特定场景的SentenceWindow
和Hierarchical
解析器,LlamaIndex 提供了丰富的工具箱。 - 自动化元数据: 利用
MetadataExtractor
可以借助 LLM 自动为数据块生成摘要、标题、可回答问题等丰富元数据,极大地提升了数据的可利用价值。 - 一个终极武器:
IngestionPipeline
将上述所有步骤(加载、切分、提取、嵌入)有机地串联起来,并通过缓存、文档管理、并行处理等高级功能,构建了一个高效、可维护、可扩展的生产级数据摄取流水线。 - 无限扩展性:
Transformation
抽象允许开发者轻松创建自定义处理逻辑,并将其无缝集成到现有工作流中,体现了 LlamaIndex 框架高度的灵活性和可扩展性。
掌握 LlamaIndex 的数据加载模块,是构建高性能 RAG 应用的第一步,也是最重要的一步。希望本文能帮助您在这条路上走得更稳、更远。