RAG實戰(zhàn)篇:構(gòu)建一個最小可行性的Rag系統(tǒng)

1 評論 5014 瀏覽 15 收藏 14 分鐘

在人工智能的世界里,RAG(Retrieval-Augmented Generation)技術(shù)正成為提升AI理解和生成能力的關(guān)鍵。本文將帶你進(jìn)入RAG系統(tǒng)的實戰(zhàn)篇,從構(gòu)建一個最小可行性的RAG系統(tǒng)開始,詳細(xì)介紹如何將這一技術(shù)應(yīng)用于實際場景中。

在《AI大模型實戰(zhàn)篇》系列文章中,風(fēng)叔通過八篇文章,從最經(jīng)典的ReAct模式開始,沿著規(guī)劃路線介紹了REWOO、Plan&Execute和LLM Compiler,沿著反思路線介紹了Basic Reflection、Self Discover和Reflexion,并以最強(qiáng)大的設(shè)計模式LATS作為收尾。

但是,所有的這些設(shè)計模式,都只是在告訴AI Agent應(yīng)該如何規(guī)劃和思考,且只能依賴于大模型既有的知識儲備。而實際應(yīng)用中,我們往往更希望AI Agent結(jié)合我們給定的知識和信息,在更專業(yè)的垂直領(lǐng)域內(nèi)進(jìn)行規(guī)劃和思考。

比如我們希望Agent幫我們做論文分析、書籍總結(jié),或者在企業(yè)級場景中,讓AI Agent寫營銷計劃、內(nèi)部知識問答、智能客服等等非常多的場景,只靠上面幾種Agent設(shè)計模式是遠(yuǎn)遠(yuǎn)不夠的,我們必須給大模型外掛知識庫,并且通過工作流進(jìn)一步約束和規(guī)范Agent的思考方向和行為模式。

解決這個問題的最佳方式是利用RAG技術(shù),接下來我們正式開啟《RAG實戰(zhàn)篇》系列。對于RAG還不太熟悉的朋友,可以先參考下面兩篇文章:

《聊聊炙手可熱的Rag:產(chǎn)生原因、基本原理與實施路徑》

《Rag系統(tǒng)的發(fā)展歷程,從樸素、高級到模塊化》

一、RAG系統(tǒng)實現(xiàn)方案概覽

我們將基于下圖所示的框架,來構(gòu)建一個完整的RAG系統(tǒng)。

1. Indexing(索引)

Indexing是任何RAG系統(tǒng)的第一步,在實際應(yīng)用場景中,文檔尺寸可能非常大,因此需要將長篇文檔分割成多個文本塊,以便更高效地處理和檢索信息。

Indexing環(huán)節(jié)主要面臨三個難題:

首先,內(nèi)容表述不完整,內(nèi)容塊的語義信息受分割方式影響,致使在較長的語境中,重要信息被丟失或被掩蓋。

其次,塊相似性搜索不準(zhǔn)確,隨著數(shù)據(jù)量增多,檢索中的噪聲增大,導(dǎo)致頻繁與錯誤數(shù)據(jù)匹配,使得檢索系統(tǒng)脆弱且不可靠。

最后,參考軌跡不明晰,檢索到的內(nèi)容塊可能來自任何文檔,沒有引用痕跡,可能出現(xiàn)來自多個不同文檔的塊,盡管語義相似,但包含的卻是完全不同主題的內(nèi)容。

在這個框架中,我們將在索引環(huán)節(jié)實現(xiàn)Chunk optimization(塊優(yōu)化)、Multi-representation indexing、Specialized Embeddings(特殊嵌入)和Hierachical Indexing(多級索引)這四種優(yōu)化方案。

2. Query Translation

Query Translation主要處理用戶的輸入。在初始的RAG系統(tǒng)中,往往直接使用原始query進(jìn)行檢索,可能會存在三個問題:

第一,原始query的措辭不當(dāng),尤其是涉及到很多專業(yè)詞匯時,query可能存在概念使用錯誤的問題;

第二,往往知識庫內(nèi)的數(shù)據(jù)無法直接回答,需要組合知識才能找到答案;

第三,當(dāng)query涉及比較多的細(xì)節(jié)時,由于檢索效率有限,大模型往往無法進(jìn)行高質(zhì)量的回答。

在這個框架中,我們將在這個環(huán)節(jié)實現(xiàn)Multi-query(多查詢)、Rag-Fusion、Decomposition(查詢分解)、Stepback和HYDE這五種優(yōu)化方案

3. Routing(路由)

路由的作用,是為每個Query選擇最合適的處理管道,以及依據(jù)來自模型的輸入或補(bǔ)充的元數(shù)據(jù),來確定將啟用哪些模塊。比如在索引環(huán)節(jié)引入多重索引技術(shù)后,就需要使用多級路由機(jī)制,根據(jù)Query引導(dǎo)至最合適的父級索引。

在路由環(huán)節(jié),我們將實現(xiàn)Logical routing(基于邏輯的路由)和Sematic Routing(基于語義的路由)兩種方案。

4. Query Construction(查詢構(gòu)建)

查詢構(gòu)建主要是為了將自然語言的Query,轉(zhuǎn)化為某種特定機(jī)器或軟件能理解的語言。因為隨著大模型在各行各業(yè)的滲透,除文本數(shù)據(jù)外,諸如表格和圖形數(shù)據(jù)等越來越多的結(jié)構(gòu)化數(shù)據(jù)正被融入 RAG 系統(tǒng)。

比如在一些ChatBI的場景下,就需要將用戶的Query內(nèi)容,轉(zhuǎn)化為SQL語句,進(jìn)行數(shù)據(jù)庫查詢,這就是Text-to-SQL。再比如工業(yè)設(shè)計場景下,可能需要將用戶的Query轉(zhuǎn)化為設(shè)計指令,或者設(shè)備控制指令,這就是Text-to-Cypher。

在查詢構(gòu)建環(huán)節(jié),我們將實現(xiàn)Text-to-SQL、Text-to-Cypher和Self-Query(讓大模型自行構(gòu)建Query)三種優(yōu)化方案。

5. Retrieval(檢索)

在檢索的時候,用戶的問題會被輸入到嵌入模型中進(jìn)行向量化處理,然后系統(tǒng)會在向量數(shù)據(jù)庫中搜索與該問題向量語義上相似的知識文本或歷史對話記錄并返回。

在樸素RAG中,系統(tǒng)會將所有檢索到的塊直接輸入到 LLM生成回答,導(dǎo)致出現(xiàn)中間內(nèi)容丟失、噪聲占比過高、上下文長度限制等問題。

在檢索環(huán)節(jié),我們將實現(xiàn)Reranking(重排序)、Refinement(壓縮)、Corrective Rag(糾正性Rag)等方案。

6. Generation(生成)

在生成環(huán)節(jié),可能會出現(xiàn)以下問題:

第一,當(dāng)系統(tǒng)忽略了以特定格式(例如表格或列表)提取信息的指令時,輸出可能會出現(xiàn)格式錯誤;

第二,輸出錯誤或者輸出不完整,比如對于一些比較類問題的處理往往不盡人意,以及可能出現(xiàn)的幻覺問題;

第三,可能會輸出一些不太符合人類/社會偏好,政治不正確的回答

在生成環(huán)節(jié),我們將重點介紹Self-Rag方案。

要覆蓋所有上面提到的優(yōu)化環(huán)節(jié),需要較長的內(nèi)容篇幅,因此風(fēng)叔會分成幾篇文章來寫。接下來,我們先從整體上,看看一個最小化的RAG系統(tǒng)是如何實現(xiàn)的。

二、構(gòu)建最小化的Naive Rag系統(tǒng)

RAG發(fā)展初期,其核心框架由索引、檢索和生成構(gòu)成,這種范式被稱作Naive RAG。Naive Rag的原理非常簡單,包括以下三個步驟:

索引:這一過程通常在離線狀態(tài)下進(jìn)行,將原始文檔或數(shù)據(jù)進(jìn)行清洗并分塊,然后將分塊后的知識通過embedding模型生成語義向量,并創(chuàng)建索引。

檢索:對用戶輸入的Query問題,使用相同的embedding模型,計算Query嵌入和文檔塊嵌入之間的向量相似度,然后選擇相似度最高的前N個文檔塊作為當(dāng)前問題的增強(qiáng)上下文信息。

生成:將原始Query和相關(guān)文檔合并為新的提示,然后由大型語言模型基于提供的信息回答問題。如果有歷史對話信息,也可以合并到提示中,用于進(jìn)行多輪對話。

下面,風(fēng)叔通過實際的源碼,詳細(xì)介紹如何構(gòu)建一個最小化的Naive Rag系統(tǒng)。

關(guān)注公眾號【風(fēng)叔云】,回復(fù)關(guān)鍵詞【最小Rag系統(tǒng)】,獲取Naive Rag設(shè)計模式的完整源代碼。

第一步建立索引

首先,我們導(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()

然后我們需要對文檔進(jì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())

第二步 檢索

檢索過程非常簡單。首先構(gòu)建檢索器retriever,設(shè)置K=1,即只召回最相關(guān)的一個內(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。先跑通流程,我們定義一個最簡單的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?")

到這里,一個最最簡單的Rag系統(tǒng)就搭建完了,其原理非常簡單易懂?!奥槿鸽m小,五臟俱全”,大家也可以拿這段代碼自己做一些修改,比如讀取pdf文件、word文檔等等。

總結(jié)

經(jīng)過上述流程,我們搭建了一個非常簡單的Naive RAG系統(tǒng),這個系統(tǒng)解析了一篇博客文章,然后接收用戶提問,并使用博客的內(nèi)容做增強(qiáng)生成。這是一個非常簡單的框架,也很易于理解。

但是在實際應(yīng)用中還有非常多需要優(yōu)化的地方,包括Indexing(索引)、Query Translation(查詢轉(zhuǎn)換)、Routing(路由)、Query Construction(查詢構(gòu)建)、Retrival(檢索)和Generation(生成),每個環(huán)節(jié)都有多種有效的優(yōu)化方式。

在下一篇文章中,風(fēng)叔將重點圍繞Indexing(索引)環(huán)節(jié),詳細(xì)介紹優(yōu)化索引的四種高級方法。

本文由人人都是產(chǎn)品經(jīng)理作者【風(fēng)叔】,微信公眾號:【風(fēng)叔云】,原創(chuàng)/授權(quán) 發(fā)布于人人都是產(chǎn)品經(jīng)理,未經(jīng)許可,禁止轉(zhuǎn)載。

題圖來自Unsplash,基于 CC0 協(xié)議。

更多精彩內(nèi)容,請關(guān)注人人都是產(chǎn)品經(jīng)理微信公眾號或下載App
評論
評論請登錄
  1. 講的很清楚,大概按照其步驟上手一遍就知道怎么做了。

    來自廣東 回復(fù)