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

1 評(píng)論 5575 瀏覽 20 收藏 14 分鐘

在人工智能的世界里,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é)議。

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

    來自廣東 回復(fù)