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