用檢索增強(qiáng)生成讓大模型更強(qiáng)大,這里有個(gè)手把手的Python實(shí)現(xiàn)
本文首先將關(guān)注 RAG 的概念和理論。然后將展示可以如何使用用于編排(orchestration)的 LangChain、OpenAI 語(yǔ)言模型和 Weaviate 向量數(shù)據(jù)庫(kù)來(lái)實(shí)現(xiàn)一個(gè)簡(jiǎn)單的 RAG。
檢索增強(qiáng)生成是什么?
檢索增強(qiáng)生成(RAG)這一概念是指通過(guò)外部知識(shí)源來(lái)為 LLM 提供附加的信息。這讓 LLM 可以生成更準(zhǔn)確和更符合上下文的答案,同時(shí)減少幻覺(jué)。
問(wèn)題
當(dāng)前最佳的 LLM 都是使用大量數(shù)據(jù)訓(xùn)練出來(lái)的,因此其神經(jīng)網(wǎng)絡(luò)權(quán)重中存儲(chǔ)了大量一般性知識(shí)(參數(shù)記憶)。但是,如果在通過(guò) prompt 讓 LLM 生成結(jié)果時(shí)需要其訓(xùn)練數(shù)據(jù)之外的知識(shí)(比如新信息、專(zhuān)有數(shù)據(jù)或特定領(lǐng)域的信息),就可能出現(xiàn)事實(shí)不準(zhǔn)確的問(wèn)題(幻覺(jué)),如下截圖所示:
因此,將 LLM 的一般性知識(shí)與附加上下文整合起來(lái)是非常重要的,這有助于 LLM 生成更準(zhǔn)確且更符合上下文的結(jié)果,同時(shí)幻覺(jué)也更少。
解決方案
傳統(tǒng)上講,通過(guò)微調(diào)模型,可以讓神經(jīng)網(wǎng)絡(luò)適應(yīng)特定領(lǐng)域的或?qū)S械男畔?。盡管這種技術(shù)是有效的,但其需要密集的計(jì)算,成本高,還需要技術(shù)專(zhuān)家的支持,因此就難以敏捷地適應(yīng)不斷變化的信息。
2020 年,Lewis et al. 的論文《Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks》提出了一種更為靈活的技術(shù):檢索增強(qiáng)生成(RAG)。在這篇論文中,研究者將生成模型與一個(gè)檢索模塊組合到了一起;這個(gè)檢索模塊可以用一個(gè)更容易更新的外部知識(shí)源提供附加信息。
用大白話來(lái)講:RAG 之于 LLM 就像開(kāi)卷考試之于人類(lèi)。在開(kāi)卷考試時(shí),學(xué)生可以攜帶教材和筆記等參考資料,他們可以從中查找用于答題的相關(guān)信息。開(kāi)卷考試背后的思想是:這堂考試考核的重點(diǎn)是學(xué)生的推理能力,而不是記憶特定信息的能力。
類(lèi)似地,事實(shí)知識(shí)與 LLM 的推理能力是分開(kāi)的,并且可以保存在可輕松訪問(wèn)和更新的外部知識(shí)源中:
- 參數(shù)化知識(shí):在訓(xùn)練期間學(xué)習(xí)到的知識(shí),以隱含的方式儲(chǔ)存在神經(jīng)網(wǎng)絡(luò)權(quán)重之中。
- 非參數(shù)化知識(shí):儲(chǔ)存于外部知識(shí)源,比如向量數(shù)據(jù)庫(kù)。
下圖展示了最基本的 RAG 工作流程:
檢索增強(qiáng)生成(RAG)的工作流程
- 檢索:將用戶查詢用于檢索外部知識(shí)源中的相關(guān)上下文。為此,要使用一個(gè)嵌入模型將該用戶查詢嵌入到同一個(gè)向量空間中,使其作為該向量數(shù)據(jù)庫(kù)中的附加上下文。這樣一來(lái),就可以執(zhí)行相似性搜索,并返回該向量數(shù)據(jù)庫(kù)中與用戶查詢最接近的 k 個(gè)數(shù)據(jù)對(duì)象。
- 增強(qiáng):然后將用戶查詢和檢索到的附加上下文填充到一個(gè) prompt 模板中。
- 生成:最后,將經(jīng)過(guò)檢索增強(qiáng)的 prompt 饋送給 LLM。
使用 LangChain 實(shí)現(xiàn)檢索增強(qiáng)生成
下面將介紹如何通過(guò) Python 實(shí)現(xiàn) RAG 工作流程,這會(huì)用到 OpenAI LLM 以及 Weaviate 向量數(shù)據(jù)庫(kù)和一個(gè) OpenAI 嵌入模型。LangChain 的作用是編排。
必要前提
請(qǐng)確保你已安裝所需的 Python 軟件包:
- langchain,編排
- openai,嵌入模型和 LLM
- weaviate-client,向量數(shù)據(jù)庫(kù)
#!pip install langchain openai weaviate-client
另外,在根目錄下用一個(gè) .env 文件定義相關(guān)環(huán)境變量。你需要一個(gè) OpenAI 賬戶來(lái)獲取 OpenAI API Key,然后在 API keys(https://platform.openai.com/account/api-keys )「創(chuàng)建新的密鑰」。
OPENAI_API_KEY="<YOUR_OPENAI_API_KEY>"
然后,運(yùn)行以下命令來(lái)加載相關(guān)環(huán)境變量。
import dotenv
dotenv.load_dotenv()
準(zhǔn)備工作
在準(zhǔn)備階段,你需要準(zhǔn)備一個(gè)作為外部知識(shí)源的向量數(shù)據(jù)庫(kù),用于保存所有的附加信息。這個(gè)向量數(shù)據(jù)庫(kù)的構(gòu)建包含以下步驟:
- 收集并載入數(shù)據(jù)
- 將文檔分塊
- 對(duì)文本塊進(jìn)行嵌入操作并保存
第一步是收集并載入數(shù)據(jù)。舉個(gè)例子,如果我們使用拜登總統(tǒng) 2022 年的國(guó)情咨文作為附加上下文。LangChain 的 GitHub 庫(kù)提供了其原始文本文檔。為了載入這些數(shù)據(jù),我們可以使用 LangChain 內(nèi)置的許多文檔加載工具。一個(gè)文檔(Document)是一個(gè)由文本和元數(shù)據(jù)構(gòu)成的詞典。為了加載文本,可以使用 LangChain 的 TextLoader。
原始文檔地址:https://raw.githubusercontent.com/langchain-ai/langchain/master/docs/docs/modules/state_of_the_union.txt
import requests
from langchain.document_loaders import TextLoader
url = "https://raw.githubusercontent.com/langchain-ai/langchain/master/docs/docs/modules/state_of_the_union.txt"
res = requests.get(url)
with open("state_of_the_union.txt", "w") as f:
f.write(res.text)
loader = TextLoader('./state_of_the_union.txt')
documents = loader.load()
接下來(lái),將文檔分塊。因?yàn)槲臋n的原始狀態(tài)很長(zhǎng),無(wú)法放入 LLM 的上下文窗口,所以就需要將其拆分成更小的文本塊。LangChain 也有很多內(nèi)置的拆分工具。對(duì)于這個(gè)簡(jiǎn)單示例,我們可以使用 CharacterTextSplitter,其 chunk_size 設(shè)為 500,chunk_overlap 設(shè)為 50,這樣可以保持文本塊之間的文本連續(xù)性。
from langchain.text_splitter import CharacterTextSplitter
text_splitter = CharacterTextSplitter(chunk_size=500, chunk_overlap=50)
chunks = text_splitter.split_documents(documents)
最后,對(duì)文本塊進(jìn)行嵌入操作并保存。為了讓語(yǔ)義搜索能夠跨文本塊執(zhí)行,就需要為每個(gè)文本塊生成向量嵌入,并將它們與它們的嵌入保存在一起。為了生成向量嵌入,可以使用 OpenAI 嵌入模型;至于儲(chǔ)存,則可使用 Weaviate 向量數(shù)據(jù)庫(kù)。通過(guò)調(diào)用 .from_documents (),可以自動(dòng)將文本塊填充到向量數(shù)據(jù)庫(kù)中。
from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import Weaviate
import weaviate
from weaviate.embedded import EmbeddedOptions
client = weaviate.Client(
embedded_options = EmbeddedOptions()
)
vectorstore = Weaviate.from_documents(
client = client,
documents = chunks,
embedding = OpenAIEmbeddings(),
by_text = False
)
步驟 1:檢索
填充完向量數(shù)據(jù)庫(kù)之后,我們可以將其定義成一個(gè)檢索器組件,其可根據(jù)用戶查詢和嵌入塊之間的語(yǔ)義相似性獲取附加上下文。
retriever = vectorstore.as_retriever()
步驟 2:增強(qiáng)
from langchain.prompts import ChatPromptTemplate
template = """You are an assistant for question-answering tasks.
Use the following pieces of retrieved context to answer the question.
If you don't know the answer, just say that you don't know.
Use three sentences maximum and keep the answer concise.
Question: {question}
Context: {context}
Answer:
"""
prompt = ChatPromptTemplate.from_template(template)
print(prompt)
接下來(lái),為了使用附加上下文增強(qiáng) prompt,需要準(zhǔn)備一個(gè) prompt 模板。如下所示,使用 prompt 模板可以輕松地定制 prompt。
步驟 3:生成
最后,我們可以為這個(gè) RAG 流程構(gòu)建一個(gè)思維鏈,將檢索器、prompt 模板和 LLM 鏈接起來(lái)。定義完成 RAG 鏈之后,便可以調(diào)用它了。
from langchain.chat_models import ChatOpenAI
from langchain.schema.runnable import RunnablePassthrough
from langchain.schema.output_parser import StrOutputParser
llm = ChatOpenAI(model_name="gpt-3.5-turbo", temperature=0)
rag_chain = (
{"context": retriever, "question": RunnablePassthrough()}
| prompt
| llm
| StrOutputParser()
)
query = "What did the president say about Justice Breyer"
rag_chain.invoke(query)
"The president thanked Justice Breyer for his service and acknowledged his dedication to serving the country.
The president also mentioned that he nominated Judge Ketanji Brown Jackson as a successor to continue Justice Breyer's legacy of excellence."
下圖展示了這個(gè)具體示例的 RAG 流程:
總結(jié)
本文介紹了 RAG 的概念,其最早來(lái)自 2020 年的論文《Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks》。在介紹了 RAG 背后的理論(包括動(dòng)機(jī)和解決方案)之后,本文又介紹了如何用 Python 實(shí)現(xiàn)它。本文展示了如何使用 OpenAI LLM 加上 Weaviate 向量數(shù)據(jù)庫(kù)和 OpenAI 嵌入模型來(lái)實(shí)現(xiàn)一個(gè) RAG 工作流程。其中 LangChain 的作用是編排。