用檢索增強(qiáng)生成讓大模型更強(qiáng)大,這里有個手把手的Python實現(xiàn)
本文首先將關(guān)注 RAG 的概念和理論。然后將展示可以如何使用用于編排(orchestration)的 LangChain、OpenAI 語言模型和 Weaviate 向量數(shù)據(jù)庫來實現(xiàn)一個簡單的 RAG。
檢索增強(qiáng)生成是什么?
檢索增強(qiáng)生成(RAG)這一概念是指通過外部知識源來為 LLM 提供附加的信息。這讓 LLM 可以生成更準(zhǔn)確和更符合上下文的答案,同時減少幻覺。
問題
當(dāng)前最佳的 LLM 都是使用大量數(shù)據(jù)訓(xùn)練出來的,因此其神經(jīng)網(wǎng)絡(luò)權(quán)重中存儲了大量一般性知識(參數(shù)記憶)。但是,如果在通過 prompt 讓 LLM 生成結(jié)果時需要其訓(xùn)練數(shù)據(jù)之外的知識(比如新信息、專有數(shù)據(jù)或特定領(lǐng)域的信息),就可能出現(xiàn)事實不準(zhǔn)確的問題(幻覺),如下截圖所示:
因此,將 LLM 的一般性知識與附加上下文整合起來是非常重要的,這有助于 LLM 生成更準(zhǔn)確且更符合上下文的結(jié)果,同時幻覺也更少。
解決方案
傳統(tǒng)上講,通過微調(diào)模型,可以讓神經(jīng)網(wǎng)絡(luò)適應(yīng)特定領(lǐng)域的或?qū)S械男畔ⅰ1M管這種技術(shù)是有效的,但其需要密集的計算,成本高,還需要技術(shù)專家的支持,因此就難以敏捷地適應(yīng)不斷變化的信息。
2020 年,Lewis et al. 的論文《Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks》提出了一種更為靈活的技術(shù):檢索增強(qiáng)生成(RAG)。在這篇論文中,研究者將生成模型與一個檢索模塊組合到了一起;這個檢索模塊可以用一個更容易更新的外部知識源提供附加信息。
用大白話來講:RAG 之于 LLM 就像開卷考試之于人類。在開卷考試時,學(xué)生可以攜帶教材和筆記等參考資料,他們可以從中查找用于答題的相關(guān)信息。開卷考試背后的思想是:這堂考試考核的重點是學(xué)生的推理能力,而不是記憶特定信息的能力。
類似地,事實知識與 LLM 的推理能力是分開的,并且可以保存在可輕松訪問和更新的外部知識源中:
- 參數(shù)化知識:在訓(xùn)練期間學(xué)習(xí)到的知識,以隱含的方式儲存在神經(jīng)網(wǎng)絡(luò)權(quán)重之中。
- 非參數(shù)化知識:儲存于外部知識源,比如向量數(shù)據(jù)庫。
下圖展示了最基本的 RAG 工作流程:
檢索增強(qiáng)生成(RAG)的工作流程
- 檢索:將用戶查詢用于檢索外部知識源中的相關(guān)上下文。為此,要使用一個嵌入模型將該用戶查詢嵌入到同一個向量空間中,使其作為該向量數(shù)據(jù)庫中的附加上下文。這樣一來,就可以執(zhí)行相似性搜索,并返回該向量數(shù)據(jù)庫中與用戶查詢最接近的 k 個數(shù)據(jù)對象。
- 增強(qiáng):然后將用戶查詢和檢索到的附加上下文填充到一個 prompt 模板中。
- 生成:最后,將經(jīng)過檢索增強(qiáng)的 prompt 饋送給 LLM。
使用 LangChain 實現(xiàn)檢索增強(qiáng)生成
下面將介紹如何通過 Python 實現(xiàn) RAG 工作流程,這會用到 OpenAI LLM 以及 Weaviate 向量數(shù)據(jù)庫和一個 OpenAI 嵌入模型。LangChain 的作用是編排。
必要前提
請確保你已安裝所需的 Python 軟件包:
- langchain,編排
- openai,嵌入模型和 LLM
- weaviate-client,向量數(shù)據(jù)庫
#!pip install langchain openai weaviate-client
另外,在根目錄下用一個 .env 文件定義相關(guān)環(huán)境變量。你需要一個 OpenAI 賬戶來獲取 OpenAI API Key,然后在 API keys(https://platform.openai.com/account/api-keys )「創(chuàng)建新的密鑰」。
OPENAI_API_KEY="<YOUR_OPENAI_API_KEY>"
然后,運(yùn)行以下命令來加載相關(guān)環(huán)境變量。
import dotenv
dotenv.load_dotenv()
準(zhǔn)備工作
在準(zhǔn)備階段,你需要準(zhǔn)備一個作為外部知識源的向量數(shù)據(jù)庫,用于保存所有的附加信息。這個向量數(shù)據(jù)庫的構(gòu)建包含以下步驟:
- 收集并載入數(shù)據(jù)
- 將文檔分塊
- 對文本塊進(jìn)行嵌入操作并保存
第一步是收集并載入數(shù)據(jù)。舉個例子,如果我們使用拜登總統(tǒng) 2022 年的國情咨文作為附加上下文。LangChain 的 GitHub 庫提供了其原始文本文檔。為了載入這些數(shù)據(jù),我們可以使用 LangChain 內(nèi)置的許多文檔加載工具。一個文檔(Document)是一個由文本和元數(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()
接下來,將文檔分塊。因為文檔的原始狀態(tài)很長,無法放入 LLM 的上下文窗口,所以就需要將其拆分成更小的文本塊。LangChain 也有很多內(nèi)置的拆分工具。對于這個簡單示例,我們可以使用 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)
最后,對文本塊進(jìn)行嵌入操作并保存。為了讓語義搜索能夠跨文本塊執(zhí)行,就需要為每個文本塊生成向量嵌入,并將它們與它們的嵌入保存在一起。為了生成向量嵌入,可以使用 OpenAI 嵌入模型;至于儲存,則可使用 Weaviate 向量數(shù)據(jù)庫。通過調(diào)用 .from_documents (),可以自動將文本塊填充到向量數(shù)據(jù)庫中。
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ù)庫之后,我們可以將其定義成一個檢索器組件,其可根據(jù)用戶查詢和嵌入塊之間的語義相似性獲取附加上下文。
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)
接下來,為了使用附加上下文增強(qiáng) prompt,需要準(zhǔn)備一個 prompt 模板。如下所示,使用 prompt 模板可以輕松地定制 prompt。
步驟 3:生成
最后,我們可以為這個 RAG 流程構(gòu)建一個思維鏈,將檢索器、prompt 模板和 LLM 鏈接起來。定義完成 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."
下圖展示了這個具體示例的 RAG 流程:
總結(jié)
本文介紹了 RAG 的概念,其最早來自 2020 年的論文《Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks》。在介紹了 RAG 背后的理論(包括動機(jī)和解決方案)之后,本文又介紹了如何用 Python 實現(xiàn)它。本文展示了如何使用 OpenAI LLM 加上 Weaviate 向量數(shù)據(jù)庫和 OpenAI 嵌入模型來實現(xiàn)一個 RAG 工作流程。其中 LangChain 的作用是編排。