RAG實(shí)戰(zhàn)篇:構(gòu)建一個(gè)最小可行性的Rag系統(tǒng)
在人工智能的世界里,RAG(Retrieval-Augmented Generation)技術(shù)正成為提升AI理解和生成能力的關(guān)鍵。本文將帶你進(jìn)入RAG系統(tǒng)的實(shí)戰(zhàn)篇,從構(gòu)建一個(gè)最小可行性的RAG系統(tǒng)開始,詳細(xì)介紹如何將這一技術(shù)應(yīng)用于實(shí)際場(chǎng)景中。
在《AI大模型實(shí)戰(zhàn)篇》系列文章中,風(fēng)叔通過八篇文章,從最經(jīng)典的ReAct模式開始,沿著規(guī)劃路線介紹了REWOO、Plan&Execute和LLM Compiler,沿著反思路線介紹了Basic Reflection、Self Discover和Reflexion,并以最強(qiáng)大的設(shè)計(jì)模式LATS作為收尾。
但是,所有的這些設(shè)計(jì)模式,都只是在告訴AI Agent應(yīng)該如何規(guī)劃和思考,且只能依賴于大模型既有的知識(shí)儲(chǔ)備。而實(shí)際應(yīng)用中,我們往往更希望AI Agent結(jié)合我們給定的知識(shí)和信息,在更專業(yè)的垂直領(lǐng)域內(nèi)進(jìn)行規(guī)劃和思考。
比如我們希望Agent幫我們做論文分析、書籍總結(jié),或者在企業(yè)級(jí)場(chǎng)景中,讓AI Agent寫營(yíng)銷計(jì)劃、內(nèi)部知識(shí)問答、智能客服等等非常多的場(chǎng)景,只靠上面幾種Agent設(shè)計(jì)模式是遠(yuǎn)遠(yuǎn)不夠的,我們必須給大模型外掛知識(shí)庫,并且通過工作流進(jìn)一步約束和規(guī)范Agent的思考方向和行為模式。
解決這個(gè)問題的最佳方式是利用RAG技術(shù),接下來我們正式開啟《RAG實(shí)戰(zhàn)篇》系列。對(duì)于RAG還不太熟悉的朋友,可以先參考下面兩篇文章:
《聊聊炙手可熱的Rag:產(chǎn)生原因、基本原理與實(shí)施路徑》
《Rag系統(tǒng)的發(fā)展歷程,從樸素、高級(jí)到模塊化》
一、RAG系統(tǒng)實(shí)現(xiàn)方案概覽
我們將基于下圖所示的框架,來構(gòu)建一個(gè)完整的RAG系統(tǒng)。
1. Indexing(索引)
Indexing是任何RAG系統(tǒng)的第一步,在實(shí)際應(yīng)用場(chǎng)景中,文檔尺寸可能非常大,因此需要將長(zhǎng)篇文檔分割成多個(gè)文本塊,以便更高效地處理和檢索信息。
Indexing環(huán)節(jié)主要面臨三個(gè)難題:
首先,內(nèi)容表述不完整,內(nèi)容塊的語義信息受分割方式影響,致使在較長(zhǎng)的語境中,重要信息被丟失或被掩蓋。
其次,塊相似性搜索不準(zhǔn)確,隨著數(shù)據(jù)量增多,檢索中的噪聲增大,導(dǎo)致頻繁與錯(cuò)誤數(shù)據(jù)匹配,使得檢索系統(tǒng)脆弱且不可靠。
最后,參考軌跡不明晰,檢索到的內(nèi)容塊可能來自任何文檔,沒有引用痕跡,可能出現(xiàn)來自多個(gè)不同文檔的塊,盡管語義相似,但包含的卻是完全不同主題的內(nèi)容。
在這個(gè)框架中,我們將在索引環(huán)節(jié)實(shí)現(xiàn)Chunk optimization(塊優(yōu)化)、Multi-representation indexing、Specialized Embeddings(特殊嵌入)和Hierachical Indexing(多級(jí)索引)這四種優(yōu)化方案。
2. Query Translation
Query Translation主要處理用戶的輸入。在初始的RAG系統(tǒng)中,往往直接使用原始query進(jìn)行檢索,可能會(huì)存在三個(gè)問題:
第一,原始query的措辭不當(dāng),尤其是涉及到很多專業(yè)詞匯時(shí),query可能存在概念使用錯(cuò)誤的問題;
第二,往往知識(shí)庫內(nèi)的數(shù)據(jù)無法直接回答,需要組合知識(shí)才能找到答案;
第三,當(dāng)query涉及比較多的細(xì)節(jié)時(shí),由于檢索效率有限,大模型往往無法進(jìn)行高質(zhì)量的回答。
在這個(gè)框架中,我們將在這個(gè)環(huán)節(jié)實(shí)現(xiàn)Multi-query(多查詢)、Rag-Fusion、Decomposition(查詢分解)、Stepback和HYDE這五種優(yōu)化方案
3. Routing(路由)
路由的作用,是為每個(gè)Query選擇最合適的處理管道,以及依據(jù)來自模型的輸入或補(bǔ)充的元數(shù)據(jù),來確定將啟用哪些模塊。比如在索引環(huán)節(jié)引入多重索引技術(shù)后,就需要使用多級(jí)路由機(jī)制,根據(jù)Query引導(dǎo)至最合適的父級(jí)索引。
在路由環(huán)節(jié),我們將實(shí)現(xiàn)Logical routing(基于邏輯的路由)和Sematic Routing(基于語義的路由)兩種方案。
4. Query Construction(查詢構(gòu)建)
查詢構(gòu)建主要是為了將自然語言的Query,轉(zhuǎn)化為某種特定機(jī)器或軟件能理解的語言。因?yàn)殡S著大模型在各行各業(yè)的滲透,除文本數(shù)據(jù)外,諸如表格和圖形數(shù)據(jù)等越來越多的結(jié)構(gòu)化數(shù)據(jù)正被融入 RAG 系統(tǒng)。
比如在一些ChatBI的場(chǎng)景下,就需要將用戶的Query內(nèi)容,轉(zhuǎn)化為SQL語句,進(jìn)行數(shù)據(jù)庫查詢,這就是Text-to-SQL。再比如工業(yè)設(shè)計(jì)場(chǎng)景下,可能需要將用戶的Query轉(zhuǎn)化為設(shè)計(jì)指令,或者設(shè)備控制指令,這就是Text-to-Cypher。
在查詢構(gòu)建環(huán)節(jié),我們將實(shí)現(xiàn)Text-to-SQL、Text-to-Cypher和Self-Query(讓大模型自行構(gòu)建Query)三種優(yōu)化方案。
5. Retrieval(檢索)
在檢索的時(shí)候,用戶的問題會(huì)被輸入到嵌入模型中進(jìn)行向量化處理,然后系統(tǒng)會(huì)在向量數(shù)據(jù)庫中搜索與該問題向量語義上相似的知識(shí)文本或歷史對(duì)話記錄并返回。
在樸素RAG中,系統(tǒng)會(huì)將所有檢索到的塊直接輸入到 LLM生成回答,導(dǎo)致出現(xiàn)中間內(nèi)容丟失、噪聲占比過高、上下文長(zhǎng)度限制等問題。
在檢索環(huán)節(jié),我們將實(shí)現(xiàn)Reranking(重排序)、Refinement(壓縮)、Corrective Rag(糾正性Rag)等方案。
6. Generation(生成)
在生成環(huán)節(jié),可能會(huì)出現(xiàn)以下問題:
第一,當(dāng)系統(tǒng)忽略了以特定格式(例如表格或列表)提取信息的指令時(shí),輸出可能會(huì)出現(xiàn)格式錯(cuò)誤;
第二,輸出錯(cuò)誤或者輸出不完整,比如對(duì)于一些比較類問題的處理往往不盡人意,以及可能出現(xiàn)的幻覺問題;
第三,可能會(huì)輸出一些不太符合人類/社會(huì)偏好,政治不正確的回答
在生成環(huán)節(jié),我們將重點(diǎn)介紹Self-Rag方案。
要覆蓋所有上面提到的優(yōu)化環(huán)節(jié),需要較長(zhǎng)的內(nèi)容篇幅,因此風(fēng)叔會(huì)分成幾篇文章來寫。接下來,我們先從整體上,看看一個(gè)最小化的RAG系統(tǒng)是如何實(shí)現(xiàn)的。
二、構(gòu)建最小化的Naive Rag系統(tǒng)
RAG發(fā)展初期,其核心框架由索引、檢索和生成構(gòu)成,這種范式被稱作Naive RAG。Naive Rag的原理非常簡(jiǎn)單,包括以下三個(gè)步驟:
索引:這一過程通常在離線狀態(tài)下進(jìn)行,將原始文檔或數(shù)據(jù)進(jìn)行清洗并分塊,然后將分塊后的知識(shí)通過embedding模型生成語義向量,并創(chuàng)建索引。
檢索:對(duì)用戶輸入的Query問題,使用相同的embedding模型,計(jì)算Query嵌入和文檔塊嵌入之間的向量相似度,然后選擇相似度最高的前N個(gè)文檔塊作為當(dāng)前問題的增強(qiáng)上下文信息。
生成:將原始Query和相關(guān)文檔合并為新的提示,然后由大型語言模型基于提供的信息回答問題。如果有歷史對(duì)話信息,也可以合并到提示中,用于進(jìn)行多輪對(duì)話。
下面,風(fēng)叔通過實(shí)際的源碼,詳細(xì)介紹如何構(gòu)建一個(gè)最小化的Naive Rag系統(tǒng)。
關(guān)注公眾號(hào)【風(fēng)叔云】,回復(fù)關(guān)鍵詞【最小Rag系統(tǒng)】,獲取Naive Rag設(shè)計(jì)模式的完整源代碼。
第一步建立索引
首先,我們導(dǎo)入一些示例Documents,以導(dǎo)入外部博客為例,我們直接使用WebBaseLoader從目標(biāo)地址讀取數(shù)據(jù)。
import bs4
from langchain_community.document_loaders import WebBaseLoader
loader = WebBaseLoader(
web_paths=("https://lilianweng.github.io/posts/2023-06-23-agent/",),
bs_kwargs=dict(
parse_only=bs4.SoupStrainer(
class_=("post-content", "post-title", "post-header")
)
),)
blog_docs = loader.load()
然后我們需要對(duì)文檔進(jìn)行分塊。在這個(gè)例子中,我們先把流程跑通,采用最簡(jiǎn)單的文本分割器,盡量按照段落進(jìn)行分割。
# Split
from langchain.text_splitter import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder(
chunk_size=300,
chunk_overlap=50)
# Make splits
splits = text_splitter.split_documents(blog_docs)
接下來,我們需要將文本分割的結(jié)果存入向量數(shù)據(jù)庫,默認(rèn)使用了OpenAI的Embedding模型。
# Index
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
vectorstore=Chroma.from_documents(documents=splits,embedding=OpenAIEmbeddings())
第二步 檢索
檢索過程非常簡(jiǎn)單。首先構(gòu)建檢索器retriever,設(shè)置K=1,即只召回最相關(guān)的一個(gè)內(nèi)容塊;然后根據(jù)問題找到最相關(guān)的內(nèi)容,存入docs
retriever = vectorstore.as_retriever(search_kwargs={"k": 1})
docs=retriever.get_relevant_documents("What is Task Decomposition?")
第三步 生成
生成環(huán)節(jié),我們先定義Prompt。先跑通流程,我們定義一個(gè)最簡(jiǎn)單的Prompt
from langchain_openai import ChatOpenAI
from langchain.prompts import ChatPromptTemplate
# Prompt
template = """Answer the question based only on the following context:{context}
Question: {question}"""
prompt=ChatPromptTemplate.from_template(template)
然后調(diào)用大模型生成最終回復(fù),我們使用了gpt-3.5-turbo。我們先把temperature調(diào)到0,減少大模型輸出的隨機(jī)性。
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
llm = ChatOpenAI(model_name="gpt-3.5-turbo", temperature=0)
rag_chain = (
{"context": retriever, "question": RunnablePassthrough()}
| prompt
| llm
| StrOutputParser())
rag_chain.invoke("What is Task Decomposition?")
到這里,一個(gè)最最簡(jiǎn)單的Rag系統(tǒng)就搭建完了,其原理非常簡(jiǎn)單易懂?!奥槿鸽m小,五臟俱全”,大家也可以拿這段代碼自己做一些修改,比如讀取pdf文件、word文檔等等。
總結(jié)
經(jīng)過上述流程,我們搭建了一個(gè)非常簡(jiǎn)單的Naive RAG系統(tǒng),這個(gè)系統(tǒng)解析了一篇博客文章,然后接收用戶提問,并使用博客的內(nèi)容做增強(qiáng)生成。這是一個(gè)非常簡(jiǎn)單的框架,也很易于理解。
但是在實(shí)際應(yīng)用中還有非常多需要優(yōu)化的地方,包括Indexing(索引)、Query Translation(查詢轉(zhuǎn)換)、Routing(路由)、Query Construction(查詢構(gòu)建)、Retrival(檢索)和Generation(生成),每個(gè)環(huán)節(jié)都有多種有效的優(yōu)化方式。
在下一篇文章中,風(fēng)叔將重點(diǎn)圍繞Indexing(索引)環(huán)節(jié),詳細(xì)介紹優(yōu)化索引的四種高級(jí)方法。
本文由人人都是產(chǎn)品經(jīng)理作者【風(fēng)叔】,微信公眾號(hào):【風(fēng)叔云】,原創(chuàng)/授權(quán) 發(fā)布于人人都是產(chǎn)品經(jīng)理,未經(jīng)許可,禁止轉(zhuǎn)載。
題圖來自Unsplash,基于 CC0 協(xié)議。
講的很清楚,大概按照其步驟上手一遍就知道怎么做了。