构建 RAG 的第一步是构建私域数据的索引,以便后续提取到相关上下文。构建索引一般分为以下三个步骤:
加载数据 :从不同的数据源加载数据,如 txt、pdf、excle、sql 等转换数据 :加载上来的数据不直接存储,需要分块、提取元数据等操作索引及存储数据 :构建分块的索引,存储到向量库中 加载数据加载数据是为了从 Markdown、PDF、Word 文档、PowerPoint 幻灯片、图像、音频和视频等不同数据源提取数据,并将其用于初始化 Document 对象
最简单的加载器是 SimpleDirectoryReader,从指定目录加载 Markdown、PDF、Word 文档、PowerPoint 幻灯片、图像、音频和视频等数据源
1 2 3 from llama_index.core import SimpleDirectoryReaderdocuments = 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 Documentdoc1=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 from llama_index.readers.web import SimpleWebPageReaderdocuments = SimpleWebPageReader(html_to_text=True ).load_data( ["https://docs.llamaindex.ai/en/stable/understanding/loading/loading/" ] ) print (len (documents))from llama_index.core import Settingsfrom llama_index.llms.ollama import OllamaSettings.llm = Ollama(model="qwen2.5:latest" , request_timeout=360.0 ,base_url='http://192.168.3.155:11434' ) from llama_index.core import SummaryIndexfrom IPython.display import Markdown, displayindex = 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 SimpleDirectoryReaderfrom llama_index.core.ingestion import IngestionPipelinefrom llama_index.core.node_parser import TokenTextSplitterdocuments = 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][A 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 from llama_index.embeddings.ollama import OllamaEmbeddingfrom llama_index.core import SettingsSettings.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 VectorStoreIndexindex=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 ) From llama_index. Core import Settings Settings. Text_splitter = text_splitter 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 From llama_index. Vector_stores. Chroma import ChromaVectorStore From llama_index. Core import StorageContext Documents = SimpleDirectoryReader ("data\三国演义白话文" , recursive=True ). Load_data (show_progress=True ) Db = chromadb.PersistentClient (path="./chroma_db" ) Chroma_collection = db. Get_or_create_collection ("quickstart" ) Vector_store = ChromaVectorStore (chroma_collection=chroma_collection) Storage_context = StorageContext. From_defaults (vector_store=vector_store) Index = VectorStoreIndex. From_documents ( Documents, storage_context=storage_context, show_progress=True ) 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] 在长坂坡,张飞独立桥上,身后尘土飞扬。曹操怀疑这是诸葛亮的计策,不敢轻易靠近。张飞一声大喝,吓得曹军兵马人仰马翻而退。随后,他拆断了桥梁,并前去见刘备,报告了这一情况。