今天,開始寫一個(gè)新的系列《大模型RAG實(shí)戰(zhàn)》。上個(gè)月我在2篇文章中,介紹了如何使用LlamaIndex框架,通過少量代碼,實(shí)現(xiàn)本地可部署和運(yùn)行的大模型RAG問答系統(tǒng)。我們要開發(fā)一個(gè)生產(chǎn)級(jí)的系統(tǒng),還需要對(duì)LlamaIndex的各個(gè)組件和技術(shù)進(jìn)行深度的理解、運(yùn)用和調(diào)優(yōu)。本系列將會(huì)聚焦在如何讓系統(tǒng)實(shí)用上,包括:知識(shí)庫(kù)的管理,檢索和查詢效果的提升,使用本地化部署的模型等主題。我將會(huì)講解相關(guān)的組件和技術(shù),輔以代碼示例。最終這些文章中的代碼將形成一套實(shí)用系統(tǒng)。 過去一年,大模型的發(fā)展突飛猛進(jìn)。月之暗面的Kimi爆火,Llama3開源發(fā)布,大模型各項(xiàng)能力提升之大有目共睹。 對(duì)于大模型檢索增強(qiáng)生成(RAG)系統(tǒng)來說,我們?cè)絹碓秸J(rèn)識(shí)到,其核心不在于模型的能力,而是在于如何更好地構(gòu)建和使用知識(shí)庫(kù),提升檢索的效能。 1 知識(shí)庫(kù)的三類數(shù)據(jù) 無論是企業(yè)還是個(gè)人,我們構(gòu)建知識(shí)庫(kù),自然希望高質(zhì)量的數(shù)據(jù)越多越好。這些數(shù)據(jù)主要分為三類。
對(duì)于個(gè)人用戶,建立個(gè)人知識(shí)庫(kù),主要是電腦上的文件資料和收藏的網(wǎng)頁(yè)信息。而對(duì)于企業(yè)來說,接入和利用已有信息系統(tǒng)的數(shù)據(jù)庫(kù)中的文本數(shù)據(jù),也非常關(guān)鍵。 LlamaIndex是一個(gè)專門針對(duì)構(gòu)建RAG系統(tǒng)開發(fā)的開源數(shù)據(jù)框架,對(duì)于以上三類數(shù)據(jù)的處理,都提供了很好的支持。 2 數(shù)據(jù)處理的四個(gè)步驟 無論是哪一類數(shù)據(jù),LlamaIndex處理數(shù)據(jù)的過程,都分為四步: 1)加載數(shù)據(jù)(Load) LlamaIndex提供了眾多數(shù)據(jù)接入組件(Data Connector),可以加載文件、網(wǎng)頁(yè)、數(shù)據(jù)庫(kù),并提取其中的文本,形成文檔(Document)。未來還將能提取圖片、音頻等非結(jié)構(gòu)化數(shù)據(jù)。 最常用的是SimpleDirectoryReader,用來讀取文件系統(tǒng)指定目錄中的PDF、DOCX、JPG等文件。 我們?cè)?strong>LlamaHub上,可以找到數(shù)百個(gè)數(shù)據(jù)接入組件,用來加載各種來源與格式的數(shù)據(jù),比如電子書epub格式文件的接入組件。 2) 轉(zhuǎn)換數(shù)據(jù)(Transform) 類似傳統(tǒng)的ETL,轉(zhuǎn)換數(shù)據(jù)是對(duì)文本數(shù)據(jù)的清洗和加工。通常,輸入是文檔(Document),輸出是節(jié)點(diǎn)(Node)。 數(shù)據(jù)處理的過程,主要是將文本分割為文本塊(Chunk),并通過嵌入模型,對(duì)文本塊進(jìn)行嵌入(Embedding)。同時(shí),可提取元數(shù)據(jù)(Metadata),例如原文件的文件名和路徑。 這里有兩個(gè)重要的參數(shù),chunk_size和chunk_overlap,分別是文本塊分割的大小,和相互之間重疊的大小。我們需要調(diào)整這些參數(shù),從而達(dá)到最好的檢索效果。 3)建立索引(Index) 索引是一種數(shù)據(jù)結(jié)構(gòu),以便于通過大語言模型(LLM)來查詢生成的節(jié)點(diǎn)。 最常使用的是向量存儲(chǔ)索引(Vector Store Index),這種方式把每個(gè)節(jié)點(diǎn)(Node)的文本,逐一創(chuàng)建向量嵌入(vector embeddings),可以理解是文本語義的一種數(shù)字編碼。 不同于傳統(tǒng)的關(guān)鍵詞匹配,通過向量檢索,比如余弦相似度,我們可以找到語義相近的文本,盡管在文字上有可能截然不同。 4)存儲(chǔ)數(shù)據(jù)(Store) 默認(rèn)情況下,以上操作生成的數(shù)據(jù)都保存在內(nèi)存中。要避免每次重來,我們需要將這些數(shù)據(jù)進(jìn)行持久化處理。 LlamaIndex 提供了內(nèi)置的persisit()方法,將索引數(shù)據(jù)持久化保存在磁盤文件中。更常見的做法是通過索引存儲(chǔ)器(Index Store),將索引保存在向量數(shù)據(jù)庫(kù)中,如Chroma、LanceDB等。 知識(shí)庫(kù)的管理,不僅要保存索引數(shù)據(jù),也要保存所有文檔(Document)及其提取的節(jié)點(diǎn)(Node)。 LLamaIndex提供了文檔存儲(chǔ)器(Document Store),把這些文本數(shù)據(jù)保存在MongoDB、Redis等NoSQL數(shù)據(jù)庫(kù)中,這樣我們可以對(duì)每一個(gè)節(jié)點(diǎn)進(jìn)行增刪改查。 3 代碼實(shí)現(xiàn)示例 下面結(jié)合代碼,介紹構(gòu)建知識(shí)庫(kù)的過程。 我們將使用LlamaIndex來加載和轉(zhuǎn)換文檔和網(wǎng)頁(yè)數(shù)據(jù),建立向量索引,并把索引保存在Chroma,把文檔和節(jié)點(diǎn)保存在MongoDB。 示例1:加載本地文件 對(duì)于在本地文件系統(tǒng)中的文件,LlamaIndex提供了非常簡(jiǎn)便的方法來讀取:SimpleDirectoryReader。 我們只需要將已有的文件,放在指定的目錄下,比如./data,通過這個(gè)方法就可以全部加載該目錄下的所有文件,包括子目錄中的文件。 from llama_index.core import SimpleDirectoryReader documents = SimpleDirectoryReader(input_dir='./data', recursive=True).load_data() print(f'Loaded {len(documents)} Files') 如果,我們?cè)谥R(shí)庫(kù)中上傳了新的文件,還可以指定加載這個(gè)文件,而非讀取整個(gè)目錄。
文件加載后,LlamaIndex會(huì)逐一提取其中的文本信息,形成文檔(Document)。通常一個(gè)文件對(duì)應(yīng)一個(gè)文檔。 示例2:加載網(wǎng)頁(yè)信息 LlamaIndex讀取和加載網(wǎng)頁(yè)信息也很簡(jiǎn)單。這里,我們用到另一個(gè)工具SimpleWebPageReader。 給出一組網(wǎng)頁(yè)的URL,我們使用這個(gè)工具可以提取網(wǎng)頁(yè)中的文字信息,并分別加載到文檔(Document)中。 pages = ['https://mp.weixin.qq.com/s/LQNWNp7nI_hI1iFpuZ9Dfw', 'https://mp.weixin.qq.com/s/m8eI2czdXT1knX-Q20T6OQ', 'https://mp.weixin.qq.com/s/prnDzOQ8HjUmonNp0jhRfw', 'https://mp.weixin.qq.com/s/YB44--865vYkmUJhEv73dQ', 'https://mp.weixin.qq.com/s/SzON91-fZgkQvzdzkXJoCA', 'https://mp.weixin.qq.com/s/zVlKUxJ_C6GjTh0ePS-f8w', 'https://mp.weixin.qq.com/s/gXlX_8mKnQSmI0R1me40Ag', 'https://mp.weixin.qq.com/s/z9eV8Q8TJ4c-IhvJ1Ag34w']
documents = SimpleWebPageReader(html_to_text=True).load_data( pages ) print(f'Loaded {len(documents)} Web Pages') 代碼中我給出的網(wǎng)頁(yè),是我寫的《大衛(wèi)說流程》系列文章。你可以改為任何你想讀取的網(wǎng)頁(yè)。未來,你可以針對(duì)這些網(wǎng)頁(yè)內(nèi)容來向大模型提問。 使用這個(gè)工具,我們需要安裝llama-index-readers-web和html2text組件。為了行文簡(jiǎn)潔,未來不再說明。你可以在運(yùn)行代碼時(shí)根據(jù)提示,安裝所需的Python庫(kù)和組件。 示例3:分割數(shù)據(jù)成塊 接下來,我們通過文本分割器(Text Splitter)將加載的文檔(Document)分割為多個(gè)節(jié)點(diǎn)(Node)。 LlamaIndex使用的默認(rèn)文本分割器是SentenceSplitter。我們可以設(shè)定文本塊的大小是256,重疊的大小是50。文本塊越小,那么節(jié)點(diǎn)的數(shù)量就越多。
之前我介紹過文本分割器Spacy,對(duì)中文支持更好。我們可以通過Langchain引入和使用。 from llama_index.core.node_parser import LangchainNodeParser from langchain.text_splitter import SpacyTextSplitter spacy_text_splitter = LangchainNodeParser(SpacyTextSplitter( pipeline='zh_core_web_sm', chunk_size = 256, chunk_overlap = 50 ))
上一步給出的數(shù)據(jù)轉(zhuǎn)換方法,其實(shí)并不實(shí)用。問題在于沒有對(duì)文檔進(jìn)行管理。我們重復(fù)運(yùn)行時(shí),將會(huì)重復(fù)加載,導(dǎo)致知識(shí)庫(kù)內(nèi)重復(fù)的內(nèi)容越來越多。 為了解決這個(gè)問題,我們可以使用LlamaIndex提供的數(shù)據(jù)采集管道(Ingestion Pipeline)的功能,默認(rèn)的策略為更新插入(upserts),實(shí)現(xiàn)對(duì)文檔進(jìn)行去重處理。
示例5:索引與存儲(chǔ)的配置 在上面的數(shù)據(jù)采集管道的代碼示例中,我們配置了用來生成向量索引的嵌入模型(embed_model),以及采用Chroma作為向量庫(kù),MongoDB作為文檔庫(kù),對(duì)數(shù)據(jù)進(jìn)行持久化存儲(chǔ)。 嵌入模型的配置如下。這里我們通過之前介紹過的HugginFace的命令行工具,將BAAI的bge-small-zh-v1.5嵌入模型下載到本地,放在“l(fā)ocalmodels”目錄下。 from llama_index.embeddings.huggingface import HuggingFaceEmbedding embed_model = HuggingFaceEmbedding(model_name='./localmodels/bge-small-zh-v1.5') 然后配置向量庫(kù),Chroma將把數(shù)據(jù)存儲(chǔ)在我們指定的“storage”目錄下。
我們可以使用Redis或MongoDB來存儲(chǔ)處理后的文檔、節(jié)點(diǎn)及相關(guān)信息,包括文檔庫(kù)(docstore)和索引信息庫(kù)(index_store)。 作為示例,我們選用在本機(jī)上安裝的MongoDB。 from llama_index.core import StorageContext from llama_index.storage.docstore.mongodb import MongoDocumentStore from llama_index.storage.index_store.mongodb import MongoIndexStore
MONGO_URI = 'mongodb://localhost:27017'
storage_context = StorageContext.from_defaults( docstore=MongoDocumentStore.from_uri(uri=MONGO_URI), index_store=MongoIndexStore.from_uri(uri=MONGO_URI), vector_store=chroma_vector_store, ) 示例6:構(gòu)建知識(shí)庫(kù) 現(xiàn)在,我們可以將此前的數(shù)據(jù)采集管道生成的文檔和節(jié)點(diǎn),載入到文檔知識(shí)庫(kù)中(docstore)。
這步完成后,我們?cè)贛ongoDB中,可以找到一個(gè)名為“db_docstore”的數(shù)據(jù)庫(kù),里面有三張表,分別是:
我們可以通過MongoDB,來查詢相關(guān)的文檔和節(jié)點(diǎn),元數(shù)據(jù)以及節(jié)點(diǎn)之間的關(guān)系信息。 未來,當(dāng)你有更多的文件和網(wǎng)頁(yè)需要放入知識(shí)庫(kù)中,只需要遵循以上的步驟加載和處理。 示例7:實(shí)現(xiàn)RAG問答 完成知識(shí)庫(kù)的構(gòu)建之后,我們可以設(shè)定使用本地的LLM,比如通過Ollama下載使用Gemma 2B模型。 然后,加載索引,生成查詢引擎(Query Engine),你就可以針對(duì)知識(shí)庫(kù)中的內(nèi)容進(jìn)行提問了。 from llama_index.llms.ollama import Ollama llm_ollama = Ollama(model='gemma:2b', request_timeout=600.0) Settings.llm = llm_ollama
from llama_index.core import VectorStoreIndex index = VectorStoreIndex.from_vector_store( vector_store=chroma_vector_store, )
query_engine = index.as_query_engine() response = query_engine.query('流程有哪三個(gè)特征?') 以上,主要介紹了使用LlamaIndex構(gòu)建知識(shí)庫(kù)的過程。 未來,我們可以結(jié)合Streamlit、Flask等前端框架,進(jìn)一步開發(fā)成一個(gè)完善的知識(shí)庫(kù)管理系統(tǒng),以便對(duì)知識(shí)內(nèi)容進(jìn)行持續(xù)的增加與更新,并支持靈活的配置文本分割的各項(xiàng)參數(shù)和選擇嵌入模型。 |
|