从零构建数据摄取流程¶
本教程将展示如何构建一个向量数据库的数据摄取管道。
我们使用 Pinecone 作为向量数据库。
您将学习以下内容:
- 如何加载文档
- 如何使用文本分割器拆分文档
- 如何手动为每个文本块构建节点
- [可选] 为每个节点添加元数据
- 如何为每个文本块生成嵌入向量
- 如何插入到向量数据库中
Pinecone¶
本教程需要 pinecone.io 的 API 密钥。您可以免费注册获取 Starter 账户。
若创建 Starter 账户,可为应用程序任意命名。
拥有账户后,请在 Pinecone 控制台导航至「API Keys」页面。您可以使用默认密钥,或为本教程创建新密钥。
请保存您的 API 密钥及其环境(免费账户为 gcp_starter),后续步骤将会用到。
如果您在 Colab 上打开此 Notebook,可能需要安装 LlamaIndex 🦙。
%pip install llama-index-embeddings-openai
%pip install llama-index-vector-stores-pinecone
%pip install llama-index-llms-openai
!pip install llama-index
OpenAI¶
本教程需要使用 OpenAI 的 API 密钥。请登录您的 platform.openai.com 账户,点击右上角的个人资料图片,从菜单中选择「API Keys」。为本教程创建一个 API 密钥并妥善保存,后续步骤将会用到它。
环境配置¶
首先添加项目依赖项。
!pip -q install python-dotenv pinecone-client llama-index pymupdf
dotenv_path = (
"env" # Google Colabs will not let you open a .env, but you can set
)
with open(dotenv_path, "w") as f:
f.write('PINECONE_API_KEY="<your api key>"\n')
f.write('OPENAI_API_KEY="<your api key>"\n')
在我们创建的文件中设置您的 OpenAI API 密钥、Pinecone API 密钥及环境变量。
import os
from dotenv import load_dotenv
load_dotenv(dotenv_path=dotenv_path)
配置步骤¶
我们构建了一个空的 Pinecone 索引,并定义了必要的 LlamaIndex 封装/抽象层,以便开始向 Pinecone 加载数据。
注意事项:请勿在代码中保存您的 API 密钥,也不要将 pinecone_env 添加到代码仓库中!
from pinecone import Pinecone, Index, ServerlessSpec
api_key = os.environ["PINECONE_API_KEY"]
pc = Pinecone(api_key=api_key)
index_name = "llamaindex-rag-fs"
# [Optional] Delete the index before re-running the tutorial.
# pinecone.delete_index(index_name)
# dimensions are for text-embedding-ada-002
if index_name not in pc.list_indexes().names():
pc.create_index(
index_name,
dimension=1536,
metric="euclidean",
spec=ServerlessSpec(cloud="aws", region="us-east-1"),
)
pinecone_index = pc.Index(index_name)
# [Optional] drop contents in index - will not work on free accounts
pinecone_index.delete(deleteAll=True)
创建 PineconeVectorStore¶
这是一个用于 LlamaIndex 的简单封装抽象层。通过封装到 StorageContext 中,我们可以轻松加载节点数据。
from llama_index.vector_stores.pinecone import PineconeVectorStore
vector_store = PineconeVectorStore(pinecone_index=pinecone_index)
从零构建数据摄取管道¶
我们将演示如何构建引言中提到的数据摄取管道。
请注意,步骤(2)和(3)可通过我们的NodeParser抽象层处理,该模块负责文本分割和节点创建。
出于本教程的目的,我们将展示如何手动创建这些对象。
1. 加载数据¶
!mkdir data
!wget --user-agent "Mozilla" "https://arxiv.org/pdf/2307.09288.pdf" -O "data/llama2.pdf"
--2023-10-13 01:45:14-- https://arxiv.org/pdf/2307.09288.pdf Resolving arxiv.org (arxiv.org)... 128.84.21.199 Connecting to arxiv.org (arxiv.org)|128.84.21.199|:443... connected. HTTP request sent, awaiting response... 200 OK Length: 13661300 (13M) [application/pdf] Saving to: ‘data/llama2.pdf’ data/llama2.pdf 100%[===================>] 13.03M 7.59MB/s in 1.7s 2023-10-13 01:45:16 (7.59 MB/s) - ‘data/llama2.pdf’ saved [13661300/13661300]
import fitz
file_path = "./data/llama2.pdf"
doc = fitz.open(file_path)
2. 使用文本分割器拆分文档¶
此处我们导入 SentenceSplitter,用于将文档文本分割成较小的片段,同时尽可能保留段落/句子的完整性。
from llama_index.core.node_parser import SentenceSplitter
text_parser = SentenceSplitter(
chunk_size=1024,
# separator=" ",
)
text_chunks = []
# maintain relationship with source doc index, to help inject doc metadata in (3)
doc_idxs = []
for doc_idx, page in enumerate(doc):
page_text = page.get_text("text")
cur_text_chunks = text_parser.split_text(page_text)
text_chunks.extend(cur_text_chunks)
doc_idxs.extend([doc_idx] * len(cur_text_chunks))
3. 从文本块手动构建节点¶
我们将每个文本块转换为 TextNode 对象——这是 LlamaIndex 中的底层数据抽象,不仅能存储内容,还可定义元数据及与其他节点的关联关系。
我们将文档中的元数据注入每个节点。
这本质上复现了 SentenceSplitter 中的逻辑。
from llama_index.core.schema import TextNode
nodes = []
for idx, text_chunk in enumerate(text_chunks):
node = TextNode(
text=text_chunk,
)
src_doc_idx = doc_idxs[idx]
src_page = doc[src_doc_idx]
nodes.append(node)
print(nodes[0].metadata)
# print a sample node
print(nodes[0].get_content(metadata_mode="all"))
from llama_index.core.extractors import (
QuestionsAnsweredExtractor,
TitleExtractor,
)
from llama_index.core.ingestion import IngestionPipeline
from llama_index.llms.openai import OpenAI
llm = OpenAI(model="gpt-3.5-turbo")
extractors = [
TitleExtractor(nodes=5, llm=llm),
QuestionsAnsweredExtractor(questions=3, llm=llm),
]
pipeline = IngestionPipeline(
transformations=extractors,
)
nodes = await pipeline.arun(nodes=nodes, in_place=False)
print(nodes[0].metadata)
5. 为每个节点生成嵌入向量¶
使用我们的 OpenAI 嵌入模型(text-embedding-ada-002)为每个节点生成文档嵌入向量。
将这些嵌入向量存储在每个节点的 embedding 属性上。
from llama_index.embeddings.openai import OpenAIEmbedding
embed_model = OpenAIEmbedding()
for node in nodes:
node_embedding = embed_model.get_text_embedding(
node.get_content(metadata_mode="all")
)
node.embedding = node_embedding
6. 将节点加载至向量存储库¶
现在我们将这些节点插入到 PineconeVectorStore 中。
注意:此处跳过了高层抽象类 VectorStoreIndex(该类同时处理数据摄取流程)。我们将在下一章节使用 VectorStoreIndex 来快速实现检索/查询功能。
vector_store.add(nodes)
from llama_index.core import VectorStoreIndex
from llama_index.core import StorageContext
index = VectorStoreIndex.from_vector_store(vector_store)
query_engine = index.as_query_engine()
query_str = "Can you tell me about the key concepts for safety finetuning"
response = query_engine.query(query_str)
print(str(response))