開源 AI 食譜文件
使用 Hugging Face Zephyr 和 LangChain 為 GitHub Issues 構建簡單的 RAG
並獲得增強的文件體驗
開始使用
使用 Hugging Face Zephyr 和 LangChain 為 GitHub Issues 構建簡單的 RAG
本筆記演示瞭如何使用 HuggingFaceH4/zephyr-7b-beta
模型和 LangChain,為一個專案的 GitHub issues 快速構建一個 RAG (檢索增強生成) 系統。
什麼是 RAG?
RAG 是一種流行的方法,用於解決強大的大語言模型(LLM)因特定內容未包含在其訓練資料中而無法感知,或即使見過也可能產生幻覺的問題。這些特定內容可能是專有的、敏感的,或者像本例中一樣,是近期且經常更新的。
如果你的資料是靜態的並且不經常變化,你可能會考慮微調一個大模型。然而,在許多情況下,微調成本高昂,並且當重複進行時(例如為了解決資料漂移問題),會導致“模型漂移”。這是指模型的行為發生了不期望的改變。
RAG (檢索增強生成) 不需要模型微調。相反,RAG 透過向 LLM 提供從相關資料中檢索到的額外上下文來工作,以便它能生成一個資訊更全面的響應。
這是一個簡單的圖示
外部資料透過一個單獨的嵌入模型轉換成嵌入向量,這些向量被儲存在資料庫中。嵌入模型通常較小,因此定期更新嵌入向量比微調一個模型更快、更便宜、更容易。
同時,由於不需要微調,當有更強大的 LLM 可用時,你可以自由地替換它,或者如果你需要更快的推理速度,也可以切換到一個更小的蒸餾版本。
讓我們用一個開源的 LLM、嵌入模型和 LangChain 來演示如何構建一個 RAG。
首先,安裝所需的依賴項
!pip install -q torch transformers accelerate bitsandbytes transformers sentence-transformers faiss-gpu
# If running in Google Colab, you may need to run this cell to make sure you're using UTF-8 locale to install LangChain
import locale
locale.getpreferredencoding = lambda: "UTF-8"
!pip install -q langchain langchain-community
準備資料
在本例中,我們將載入 PEFT 庫的程式碼倉庫中所有的 issues(包括開放和關閉的)。
首先,你需要獲取一個 GitHub 個人訪問令牌 來訪問 GitHub API。
from getpass import getpass
ACCESS_TOKEN = getpass("YOUR_GITHUB_PERSONAL_TOKEN")
接下來,我們將載入 huggingface/peft 倉庫中的所有 issues。
- 預設情況下,pull requests 也被視為 issues,這裡我們透過設定
include_prs=False
將它們排除在資料之外。 - 設定
state = "all"
意味著我們將載入開放和關閉的 issues。
from langchain.document_loaders import GitHubIssuesLoader
loader = GitHubIssuesLoader(repo="huggingface/peft", access_token=ACCESS_TOKEN, include_prs=False, state="all")
docs = loader.load()
單個 GitHub issue 的內容可能比嵌入模型能接受的輸入要長。如果我們想嵌入所有可用的內容,我們需要將文件分塊成適當大小的片段。
最常見和直接的分塊方法是定義一個固定的塊大小以及它們之間是否應該有重疊。在塊之間保留一些重疊可以讓我們保留塊之間的某些語義上下文。對於通用文字,推薦的分詞器是 RecursiveCharacterTextSplitter,我們這裡就用它。
from langchain.text_splitter import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter(chunk_size=512, chunk_overlap=30)
chunked_docs = splitter.split_documents(docs)
建立嵌入向量和檢索器
現在文件都已是適當的大小,我們可以用它們的嵌入向量建立一個數據庫了。
為了建立文件塊的嵌入向量,我們將使用 HuggingFaceEmbeddings
和 BAAI/bge-base-en-v1.5
嵌入模型。Hub 上有許多其他可用的嵌入模型,你可以透過檢視 大規模文字嵌入基準(MTEB)排行榜來關注表現最好的模型。
為了建立向量資料庫,我們將使用 FAISS
,一個由 Facebook AI 開發的庫。這個庫為密集向量提供了高效的相似性搜尋和聚類,這正是我們所需要的。FAISS 是目前在海量資料集中進行最近鄰搜尋最常用的庫之一。
我們將透過 LangChain API 來訪問嵌入模型和 FAISS。
from langchain.vectorstores import FAISS
from langchain.embeddings import HuggingFaceEmbeddings
db = FAISS.from_documents(chunked_docs, HuggingFaceEmbeddings(model_name="BAAI/bge-base-en-v1.5"))
我們需要一種方法來根據非結構化查詢返回(檢索)文件。為此,我們將使用 as_retriever
方法,以 db
作為其後端。
search_type="similarity"
意味著我們想在查詢和文件之間進行相似性搜尋。search_kwargs={'k': 4}
指示檢索器返回前 4 個結果。
retriever = db.as_retriever(search_type="similarity", search_kwargs={"k": 4})
向量資料庫和檢索器現在已經設定好了,接下來我們需要設定鏈的下一個部分——模型。
載入量化模型
在本例中,我們選擇了 HuggingFaceH4/zephyr-7b-beta
,一個體積小但功能強大的模型。
由於每週都有許多模型釋出,你可能想用最新最好的模型來替代這個模型。跟蹤開源 LLM 的最佳方式是檢視開源 LLM 排行榜。
為了加快推理速度,我們將載入模型的量化版本。
import torch
from transformers import AutoTokenizer, AutoModelForCausalLM, BitsAndBytesConfig
model_name = "HuggingFaceH4/zephyr-7b-beta"
bnb_config = BitsAndBytesConfig(
load_in_4bit=True, bnb_4bit_use_double_quant=True, bnb_4bit_quant_type="nf4", bnb_4bit_compute_dtype=torch.bfloat16
)
model = AutoModelForCausalLM.from_pretrained(model_name, quantization_config=bnb_config)
tokenizer = AutoTokenizer.from_pretrained(model_name)
設定 LLM 鏈
最後,我們擁有了設定 LLM 鏈所需的所有部分。
首先,使用載入的模型及其分詞器建立一個文字生成管道(pipeline)。
接下來,建立一個提示詞模板——這應該遵循模型的格式,所以如果你替換了模型檢查點,請確保使用適當的格式。
from langchain.llms import HuggingFacePipeline
from langchain.prompts import PromptTemplate
from transformers import pipeline
from langchain_core.output_parsers import StrOutputParser
text_generation_pipeline = pipeline(
model=model,
tokenizer=tokenizer,
task="text-generation",
temperature=0.2,
do_sample=True,
repetition_penalty=1.1,
return_full_text=True,
max_new_tokens=400,
)
llm = HuggingFacePipeline(pipeline=text_generation_pipeline)
prompt_template = """
<|system|>
Answer the question based on your knowledge. Use the following context to help:
{context}
</s>
<|user|>
{question}
</s>
<|assistant|>
"""
prompt = PromptTemplate(
input_variables=["context", "question"],
template=prompt_template,
)
llm_chain = prompt | llm | StrOutputParser()
注意:你也可以使用 tokenizer.apply_chat_template
將訊息列表(以字典形式:{'role': 'user', 'content': '(...)'}
)轉換為具有適當聊天格式的字串。
最後,我們需要將 llm_chain
與檢索器結合起來,建立一個 RAG 鏈。我們將原始問題以及檢索到的上下文文件傳遞給最終的生成步驟。
from langchain_core.runnables import RunnablePassthrough
retriever = db.as_retriever()
rag_chain = {"context": retriever, "question": RunnablePassthrough()} | llm_chain
比較結果
讓我們看看 RAG 在生成針對特定庫的問題的答案時有什麼不同。
question = "How do you combine multiple adapters?"
首先,讓我們看看僅使用模型本身,不新增任何上下文,能得到什麼樣的答案。
llm_chain.invoke({"context": "", "question": question})
如你所見,模型將問題解釋為關於物理計算機介面卡的問題,而在 PEFT 的上下文中,“介面卡”指的是 LoRA 介面卡。讓我們看看新增來自 GitHub issues 的上下文是否有助於模型給出更相關的答案。
rag_chain.invoke(question)
正如我們所見,新增的上下文確實幫助了完全相同的模型,為特定庫的問題提供了更相關、資訊更豐富的答案。
值得注意的是,將多個介面卡組合用於推理的功能已經新增到庫中,並且可以在文件中找到此資訊,因此對於此 RAG 的下一次迭代,可能值得包括文件的嵌入向量。
< > 在 GitHub 上更新