开源 AI 食谱文档

使用 Hugging Face Zephyr 和 LangChain 的 GitHub 议题简易 RAG

Hugging Face's logo
加入 Hugging Face 社区

并获得增强的文档体验

开始使用

Open In Colab

使用 Hugging Face Zephyr 和 LangChain 的 GitHub 议题简易 RAG

作者:Maria Khalusova

本笔记本演示了如何使用 HuggingFaceH4/zephyr-7b-beta 模型和 LangChain 为项目的 GitHub 议题快速构建 RAG(检索增强生成)。

什么是 RAG?

RAG 是一种流行的方法,用于解决强大的 LLM 由于特定内容不在其训练数据中而无法感知特定内容,或者即使之前见过也会产生幻觉的问题。此类特定内容可能是专有的、敏感的,或者如本例所示,是最新的且经常更新的。

如果您的数据是静态的并且不会定期更改,您可以考虑微调大型模型。 然而,在许多情况下,微调成本可能很高,并且当重复进行(例如,为了解决数据漂移)时,会导致“模型漂移”。 这是指模型行为以不希望的方式发生变化。

RAG(检索增强生成)不需要模型微调。 相反,RAG 的工作原理是为 LLM 提供从相关数据中检索到的额外上下文,以便它可以生成更明智的响应。

这是一个快速说明

RAG diagram

  • 外部数据使用单独的嵌入模型转换为嵌入向量,并且这些向量保存在数据库中。 嵌入模型通常很小,因此定期更新嵌入向量比微调模型更快、更便宜且更容易。

  • 与此同时,不需要微调这一事实让您可以自由地将 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 库的 repo 中的所有议题(包括打开的和关闭的)。

首先,您需要获取 GitHub 个人访问令牌才能访问 GitHub API。

from getpass import getpass

ACCESS_TOKEN = getpass("YOUR_GITHUB_PERSONAL_TOKEN")

接下来,我们将加载 huggingface/peft repo 中的所有议题

  • 默认情况下,拉取请求也被视为议题,在这里我们选择通过设置 include_prs=False 将它们从数据中排除
  • 设置 state = "all" 意味着我们将加载打开和关闭的议题。
from langchain.document_loaders import GitHubIssuesLoader

loader = GitHubIssuesLoader(repo="huggingface/peft", access_token=ACCESS_TOKEN, include_prs=False, state="all")

docs = loader.load()

单个 GitHub 议题的内容可能比嵌入模型可以作为输入的内容更长。 如果我们想要嵌入所有可用的内容,我们需要将文档分块成适当大小的片段。

最常见和直接的分块方法是定义固定大小的块以及它们之间是否应该有任何重叠。 在块之间保留一些重叠可以让我们保留块之间的一些语义上下文。 通用文本的推荐拆分器是 RecursiveCharacterTextSplitter,这就是我们将在此处使用的拆分器。

from langchain.text_splitter import RecursiveCharacterTextSplitter

splitter = RecursiveCharacterTextSplitter(chunk_size=512, chunk_overlap=30)

chunked_docs = splitter.split_documents(docs)

创建嵌入 + 检索器

现在文档都具有适当的大小,我们可以创建一个包含其嵌入的数据库。

为了创建文档块嵌入,我们将使用 HuggingFaceEmbeddingsBAAI/bge-base-en-v1.5 嵌入模型。 Hub 上还有许多其他嵌入模型可用,您可以通过查看 大规模文本嵌入基准 (MTEB) 排行榜来关注性能最佳的模型。

为了创建向量数据库,我们将使用 FAISS,这是一个由 Facebook AI 开发的库。 该库提供高效的相似性搜索和密集向量聚类,这正是我们这里需要的。 FAISS 目前是用于大规模数据集 NN 搜索的最常用库之一。

我们将通过 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 链所需的所有部分。

首先,使用加载的模型及其分词器创建 text_generation 管道。

接下来,创建一个提示模板 - 这应该遵循模型的格式,因此如果您替换模型检查点,请确保使用适当的格式。

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 议题的上下文是否有助于模型给出更相关的答案

rag_chain.invoke(question)

正如我们所看到的,添加的上下文确实有助于完全相同的模型,为库特定问题提供更相关和更明智的答案。

值得注意的是,用于推理的多个适配器的组合已添加到库中,并且可以在文档中找到此信息,因此对于此 RAG 的下一次迭代,可能值得包含文档嵌入。

< > 在 GitHub 上更新