主题
在你选定的 LLM 能够对你的数据起作用之前,你得先处理并加载这些数据。这与机器学习世界里的数据清洗/特征工程流水线,或者传统数据领域的 ETL 流水线是相通的。
这条摄取流水线通常由三个主要阶段组成:
- 加载数据
- 变换数据
- 索引并存储数据
索引与存储我们在后续的章节里讲,本指南主要讨论加载器与变换。
加载器
在你选定的 LLM 用到你的数据之前,需要先把数据加载进来。LlamaIndex 的做法是通过数据连接器,也叫 Reader。数据连接器从各种数据来源摄取数据,并把数据组织成 Document 对象。一个 Document 是一组数据(目前是文本,将来还有图像与音频)以及关于这组数据的元数据。
使用 SimpleDirectoryReader 加载
最容易上手的加载器是我们的 SimpleDirectoryReader,它会为指定目录下的每一个文件创建文档。它内置在 LlamaIndex 里,能读取多种格式,包括 Markdown、PDF、Word 文档、PowerPoint 演示、图片、音频和视频。
from llama_index.core import SimpleDirectoryReader
documents = SimpleDirectoryReader("./data").load_data()使用 Reader 集成
由于数据的出处多得数不清,它们并非全部内置。你需要把它们作为独立的包安装。如何找到它们,见数据连接器。
这个例子里我们使用 DatabaseReader 连接器(pip install llama-index-readers-database),它对 SQL 数据库执行一次查询,并把结果集的每一行返回成一个 Document:
from llama_index.readers.database import DatabaseReader
reader = DatabaseReader(
scheme=os.getenv("DB_SCHEME"),
host=os.getenv("DB_HOST"),
port=os.getenv("DB_PORT"),
user=os.getenv("DB_USER"),
password=os.getenv("DB_PASS"),
dbname=os.getenv("DB_NAME"),
)
query = "SELECT * FROM users"
documents = reader.load_data(query=query)可选择的连接器有数百个!
直接创建 Document
除了使用加载器,你也可以直接使用 Document。
from llama_index.core import Document
doc = Document(text="text")变换
数据加载之后,在写入存储系统之前还需要处理和变换。这些变换包括分块、抽取元数据,以及为每个块生成嵌入。只有这样,数据才能被恰当检索,并被 LLM 最优地使用。
变换的输入/输出都是 Node 对象(Document 是 Node 的子类)。变换还可以叠加和重新排序。
针对文档变换,我们同时提供了高层和低层两套 API。
高层变换 API
索引有一个 .from_documents() 方法,接受一组 Document 对象并正确地完成解析与分块。但有时你会更想自己掌控文档的切分方式。
from llama_index.core import VectorStoreIndex
vector_index = VectorStoreIndex.from_documents(documents)
vector_index.as_query_engine()其底层做法是把你的 Document 切分成 Node 对象。Node 与 Document 相似(同样包含文本和元数据),但它与自己所属的父 Document 之间存在关联关系。
如果想在这个抽象里定制核心组件(比如文本切分器),你可以传入自定义的 transformations 列表,或者直接作用于全局的 Settings:
from llama_index.core.node_parser import SentenceSplitter
text_splitter = SentenceSplitter(chunk_size=512, chunk_overlap=10)
# global
from llama_index.core import Settings
Settings.text_splitter = text_splitter
# per-index
index = VectorStoreIndex.from_documents(
documents, transformations=[text_splitter]
)低层变换 API
你也可以把这些步骤显式地写出来。
做法有两种:把我们的变换模块(文本切分器、元数据抽取器等)当作独立组件使用,或者把它们组合进我们的声明式变换流水线接口。
我们沿着下面的步骤走一遍。
把文档切分成节点
处理文档的关键一步,是把它们切成“块”/Node 对象。核心思想是把数据加工成适合检索、也适合直接喂给 LLM 的小片段。
LlamaIndex 支持范围很广的文本切分器,从基于段落/句子/token 的切分器,到面向文件格式的 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()
pipeline = IngestionPipeline(transformations=[TokenTextSplitter(), ...])
nodes = pipeline.run(documents=documents)添加元数据
你也可以选择为文档和节点添加元数据。既可以手工添加,也可以使用自动元数据抽取器。
这里有两篇指南:1)如何定制 Document,2)如何定制 Node。
document = Document(
text="text",
metadata={"filename": "<doc_file_name>", "category": "<category>"},
)添加嵌入
要把节点插入向量索引,它就需要有嵌入。更多细节见我们的摄取流水线或嵌入指南。
直接创建并传入节点
如果有需要,你可以直接创建节点,把一组 Node 直接传给索引器:
from llama_index.core.schema import TextNode
node1 = TextNode(text="<text_chunk>", id_="<node_id>")
node2 = TextNode(text="<text_chunk>", id_="<node_id>")
index = VectorStoreIndex([node1, node2])