LlamaIndex 基础 05-RAG-Index

构建 RAG 的第一步是构建私域数据的索引,以便后续提取到相关上下文。构建索引一般分为以下三个步骤:

image5

  • 加载数据:从不同的数据源加载数据,如 txt、pdf、excle、sql 等
  • 转换数据:加载上来的数据不直接存储,需要分块、提取元数据等操作
  • 索引及存储数据:构建分块的索引,存储到向量库中

加载数据

加载数据是为了从 Markdown、PDF、Word 文档、PowerPoint 幻灯片、图像、音频和视频等不同数据源提取数据,并将其用于初始化 Document 对象

最简单的加载器是 SimpleDirectoryReader,从指定目录加载 Markdown、PDF、Word 文档、PowerPoint 幻灯片、图像、音频和视频等数据源

1
2
3
from llama_index.core import SimpleDirectoryReader

documents = SimpleDirectoryReader("data\三国演义白话文",recursive=True).load_data(show_progress=True)

Loading files: 100%|██████████████| 42/42 [00:00<00:00, 44.97 file/s]

除此之外,我们可以使用更加低级的接口,直接将文本加载为 Document 对象

1
2
3
4
5
6
7
from llama_index.core import Document

doc1=Document(text="123456")
doc2=Document(text="abcdef")

documents=[doc1,doc2]
print(documents)
1
[Document (id_='dfb 3 fd 37-f 8 a 6-4203-96 a 4-9509 b 28537 ee', embedding=None, metadata={}, excluded_embed_metadata_keys=[], excluded_llm_metadata_keys=[], relationships={}, metadata_template='{key}: {value}', metadata_separator='\n', text='123456', mimetype='text/plain', start_char_idx=None, end_char_idx=None, metadata_seperator='\n', text_template='{metadata_str}\n\n{content}'), Document (id_='75 fec 794-dabd-47 ca-b 232-47954 c 153 b 41', embedding=None, metadata={}, excluded_embed_metadata_keys=[], excluded_llm_metadata_keys=[], relationships={}, metadata_template='{key}: {value}', metadata_separator='\n', text='abcdef', mimetype='text/plain', start_char_idx=None, end_char_idx=None, metadata_seperator='\n', text_template='{metadata_str}\n\n{content}')]

除了以上 LlamaIndex 直接提供的接口外,在 Llama Hub 提供针对不同数据源的加载方式,还提供将这些加载对象转为其他框架的接口,以下是针对网页的加载器示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 默认不带这个加载器,通过命令安装:python -m pip install llama-index-readers-web
from llama_index.readers.web import SimpleWebPageReader

# 从网页加载数据
documents = SimpleWebPageReader(html_to_text=True).load_data(
["https://docs.llamaindex.ai/en/stable/understanding/loading/loading/"]
)
print(len(documents))

# 加载模型
from llama_index.core import Settings
from llama_index.llms.ollama import Ollama
Settings.llm = Ollama(model="qwen2.5:latest", request_timeout=360.0,base_url='http://192.168.3.155:11434')

# 创建摘要索引
from llama_index.core import SummaryIndex
from IPython.display import Markdown, display
index = SummaryIndex.from_documents(documents)

# 创建查询引擎并查询内容
query_engine = index.as_query_engine()
response = query_engine.query("LlamaIndex加载数据有几种方式?")

# 显示查询结果
display(Markdown(f"<b>{response}</b>"))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
 <b>LlamaIndex 加载数据主要有以下几种方式:
1. **使用 `SimpleDirectoryReader` 载入简单目录中的文件**
```python
from llama_index. Core import SimpleDirectoryReader
documents = SimpleDirectoryReader ("./data"). Load_data ()
\```
2. **利用读取器从 LlamaHub 加载数据**
例如,使用 `DatabaseReader` 从 SQL 数据库加载数据。
```python
from llama_index. Core import download_loader
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)
\```
3. **直接创建文档**:
```python
from llama_index. Core import Document
doc = Document (text="text")
\```
>这些方法提供了灵活的数据加载选项,可以根据具体需求选择合适的方式来处理和加载数据。</b>

转换

转换的目的是将 documents 内的所有文档进行分块、提取元数据操作,在代码层面,就是将 documents 转为分块的 nodes
以下代码通过自定义文本分割器,实现文本切割

1
2
3
4
5
6
7
8
9
10
11
from llama_index.core import SimpleDirectoryReader
from llama_index.core.ingestion import IngestionPipeline
from llama_index.core.node_parser import TokenTextSplitter

documents = SimpleDirectoryReader("data\三国演义白话文",recursive=True).load_data(show_progress=True)
print(len(documents))

pipeline=IngestionPipeline(transformations=[
TokenTextSplitter(chunk_size=500,chunk_overlap=100)])

nodes=pipeline.run(documents=documents)
1
2
3
4
5
6
7
Loading files: 100%|████████████| 41/41 [00:00<00:00, 1320.46 file/s]
Node ID: 371 f 3091-6281-4 e 0 f-a 771-04 ca 7368 afe 4
Text: 第一回桃园结义东汉末年,朝政腐败,再加上连年灾荒,老百姓的日子非常困苦。巨鹿人张角见人民怨恨官府,便与他的弟弟张
梁、张宝在河北、河南、山东、湖北、江苏等地,招收了五十万人,举行起义,一起向官兵进攻。没有几天,四方百姓,头裹黄巾,跟随张角三兄弟杀向
官府,声势非常浩大。汉灵帝得到各地报告,连忙下令各地官军防备。又派中郎将卢植、皇甫嵩、朱隽率领一精一兵,分路攻打张角兄弟的黄巾军。
张角领军攻打幽州地界,幽州太守连忙召校尉邹靖商议,邹靖说幽州兵少不能抵挡。建议写榜文到各县招募兵马。
榜文行到涿县,引出一名英雄,这人姓刘名备,字玄德。因家里贫寒,靠贩麻鞋、织草席为生。这天他进城来看榜文。

索引及存储

索引是通过 embedding 模型,得到文档 nodes 的嵌入向量,然后存储到向量数据库中,所以这步的目的是创建向量数据库
基于转换后的文档 nodes,可以直接创建向量数据库

1
2
3
4
5
6
7
8
9
# 设置embedding model 
from llama_index.embeddings.ollama import OllamaEmbedding
from llama_index.core import Settings
Settings.embed_model = OllamaEmbedding(model_name="quentinz/bge-large-zh-v1.5:latest",base_url='http://192.168.3.155:11434')

# 构建向量数据库
from llama_index.core import VectorStoreIndex
index=VectorStoreIndex(nodes=nodes,show_progress=True)
print(index)

Generating embeddings: 0%| | 0/258 [00:00<?, ?It/s]

如果不需要定制转换方式,可以用以下接口快速从 documents 生成向量数据库

1
2
From llama_index. Core import VectorStoreIndex
Index=VectorStoreIndex. From_documents (documents=documents, show_progress=True)

Parsing nodes: 0%| | 0/41 [00:00<?, ?It/s]
Generating embeddings: 0%| | 0/127 [00:00<?, ?It/s]

如果需要定制转换过程时,可以通过以下方式进行

1
2
3
4
5
6
7
8
9
10
11
From llama_index. Core. Node_parser import SentenceSplitter
Text_splitter = SentenceSplitter (chunk_size=1024, chunk_overlap=100)

# Global
From llama_index. Core import Settings
Settings. Text_splitter = text_splitter

# Per-index
Index = VectorStoreIndex. From_documents (
Documents, transformations=[text_splitter], show_progress=True
)

Parsing nodes: 0%| | 0/41 [00:00<?, ?It/s]
Generating embeddings: 0%| | 0/125 [00:00<?, ?It/s]

也有更加低级的接口,如果已知每个 chunk 的文本,可以直接定义文档的 node 对象,然后初始化向量数据库

1
2
3
4
5
From llama_index. Core. Schema import TextNode
Node 1 = TextNode (text="abc", id_="100")
Node 2 = TextNode (text="123", id_="200")

Index = VectorStoreIndex ([node 1, node 2], show_progress=True)

Generating embeddings: 0%| | 0/2 [00:00<?, ?It/s]

完成向量数据库的构建后,下一步可以将向量数据库持久化到磁盘,下次直接加载使用即可,而不必要重新构建

1
2
3
4
5
6
7
8
# 向量数据库持久化 
Persist_dir='./vector_storage'
Index. Storage_context.Persist (persist_dir=persist_dir)

# 加载持久化的向量数据库
From llama_index. Core import StorageContext, load_index_from_storage
Storage_context=StorageContext. From_defaults (persist_dir=persist_dir)
Index=load_index_from_storage (storage_context=storage_context)

代码中的 storage_context 指的是不同的向量数据库,所谓不同,是指向量数据库存储机制、检索机制不同,如有的向量数据库擅长存储关系型数据,有的向量数据库可以进行更加复杂的检索方式。LlamaIndex 支持多种向量数据库,比如 chroma

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
Import chromadb
From llama_index. Core import VectorStoreIndex, SimpleDirectoryReader
# 需要先 pip install llama-index-vector-stores-chroma
From llama_index. Vector_stores. Chroma import ChromaVectorStore
From llama_index. Core import StorageContext

Documents = SimpleDirectoryReader ("data\三国演义白话文", recursive=True). Load_data (show_progress=True)

# Initialize client, setting path to save data
Db = chromadb.PersistentClient (path="./chroma_db")

# Create collection
Chroma_collection = db. Get_or_create_collection ("quickstart")

# Assign chroma as the vector_store to the context
Vector_store = ChromaVectorStore (chroma_collection=chroma_collection)
Storage_context = StorageContext. From_defaults (vector_store=vector_store)

# Create your index
Index = VectorStoreIndex. From_documents (
Documents, storage_context=storage_context, show_progress=True
)

# Create a query engine and query
Query_engine = index. As_query_engine ()
Response = query_engine.Query ("张飞长坂坡做了什么事?")
Print (response)
1
2
3
4
Loading files: 100%|█████████████| 41/41 [00:00<00:00, 694.39 file/s]
Parsing nodes: 0%| | 0/41 [00:00\<?, ?It/s]
Generating embeddings: 0%| | 0/127 [00:00\<?, ?It/s]
在长坂坡,张飞独立桥上,身后尘土飞扬。曹操怀疑这是诸葛亮的计策,不敢轻易靠近。张飞一声大喝,吓得曹军兵马人仰马翻而退。随后,他拆断了桥梁,并前去见刘备,报告了这一情况。