作者:Vipra Singh
编译:ronghuaiyang
导读
在这篇博客文章中,我们将使用LangChain创建一个基础的LLM应用程序。
介绍
我们之前的博客文章深入探讨了大型语言模型(LLMs),覆盖了它们的发展历程和广泛的应用场景。现在,让我们更近距离地聚焦这一旅程的核心:本地构建LLM应用程序。
在这篇博客文章中,我们将使用LangChain创建一个基础的LLM应用程序。
之后,我们将继续在当地开发另外三个开源的LLM应用程序。
以下是RAG(Retrieval-Augmented Generation)应用的一个非常合适的架构:
从头构建LLM的应用
我们将快速构建一个针对项目GitHub问题的RAG(Retrieval Augmented Generation)应用,使用HuggingFaceH4/zephyr-7b-beta模型和LangChain。
这里有一个快速演示:
.assets/0uvr3Qq02_CCWfTgS.png)
- 外部数据通过一个独立的嵌入模型转化为嵌入向量,并将这些向量存储在一个数据库中。嵌入模型通常较小,因此定期更新嵌入向量比微调模型更快、更便宜、更容易。
- 同时,由于不需要微调,这就给了你自由更换更强大的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
准备数据
在这个例子中,我们将加载来自PEFT库的仓库的所有问题(包括开放和已关闭的问题)。
首先,你需要获取一个GitHub个人访问token以访问GitHub API。
from getpass import getpass
ACCESS_TOKEN = getpass("YOUR_GITHUB_PERSONAL_TOKEN")
接下来,我们将加载huggingface/peft仓库中的所有问题:
- 默认情况下,拉取请求也被视为问题,但我们通过设置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)
创建嵌入与检索器
既然文档都已经分割成合适的大小,我们现在可以创建一个包含它们嵌入的数据库。
为了创建文档片段的嵌入,我们将使用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。
接下来,创建一个提示模板——这应该遵循模型的格式,因此如果你替换了模型checkpoint,确保使用适当的格式化。
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}
<|user|>
{question}
<|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})
输出:
To combine multiple adapters, you need to ensure that they are compatible with each other and the devices you want to connect. Here's how you can do it:
1. Identify the types of connectors you need: Before combining adapters, determine which connectors you require for both the input and output devices. For example, if you want to connect a USB-C device to an HDMI display, you may need a USB-C to HDMI adapter, and then a separate HDMI to DVI adapter if your display only supports DVI.
2. Check compatibility: Make sure that the adapters you choose are compatible with each other and the devices you want to connect. This is important because some adapters may not work together or may cause signal loss or interference.
3. Connect the adapters: Once you have identified the compatible adapters, connect them in the correct order. Typically, the adapter closest to the device you want to connect goes first, followed by any additional adapters required.
4. Test the connection: After connecting all the adapters, test the connection to ensure that everything is working correctly. If you encounter any issues, try rearranging the order of the adapters or using different adapters altogether.
5. Secure the connections: To prevent accidental disconnections, use cable ties or clips to secure the adapters and cables in place. This will also help to organize your setup and make it easier to manage.
Remember, combining multiple adapters can sometimes result in signal loss or interference, so it's essential to test the connection thoroughly before relying on it for critical tasks. Additionally, be aware of the maximum length of the combined cables to avoid signal degradation.As you can see, the model interpreted the question as one about physical computer adapters, while in the context of PEFT, “adapters” refer to LoRA adapters.
让我们看看添加来自GitHub问题的上下文是否能帮助模型给出更相关、更准确的答案:
rag_chain.invoke(question)
输出:
Based on the provided context, here are some potential ways to combine multiple adapters using PEFT (Python Extension for Transformers):
1. Load each adapter separately and concatenate their outputs:
```python
from peft import Peft
# Load the base model and adapter 1
base_model = AutoModelForSequenceClassification.from_pretrained("your_base_model")
adapter1 = Peft("adapter1").requires_grad_(False)
adapter1(base_model).load_state_dict(torch.load("path/to/adapter1.bin"))
# Load adapter 2
adapter2 = Peft("adapter2").requires_grad_(False)
adapter2(base_model).load_state_dict(torch.load("path/to/adapter2.bin"))
# Concatenate the outputs of both adapters
def forward(self, input_ids, attention_mask):
x = self.base_model(input_ids, attention_mask)[0]
x = torch.cat([x, adapter1(x), adapter2(x)], dim=-1)
return x
# Create a new model class that includes the concatenated outputs
class MyModel(BaseModel):
def __init__(self):
super().__init__()
self.forward = forward
# Instantiate the new model class and use it for inference
my_model = MyModel()
2. Freeze multiple adapters and selectively activate them during inference:
```python
from peft import Peft
# Load the base model and all ad
正如我们所见,添加的上下文确实帮助了同一模型,对库特定问题给出了更相关、更有见地的回答。
值得注意的是,多个适配器的组合用于推理已经被添加到库中,人们可以在文档中找到这些信息,因此对于RAG的下一次迭代,包含文档嵌入可能是值得的。
现在,我们理解了如何从头开始构建一个LLM RAG应用。
Google Colab: https://colab.research.google.com/github/huggingface/cookbook/blob/main/notebooks/en/rag_zephyr_langchain.ipynb
接下来,我们将利用我们的理解来构建另外三个LLM应用。
对于下面的应用,我们将使用Ollama作为我们的LLM服务器。让我们先从下面了解更多关于LLM服务器的信息。
LLM 服务器
这个应用最关键的部分是LLM服务器。借助于Ollama,我们拥有一个强大且可在本地设置的LLM服务器。
什么是Ollama?
Ollama可以从ollama.ai网站进行安装。
Ollama不是一个单一的语言模型,而是一个框架,它让我们能够在本地机器上运行多个开源LLM。可以把它想象成一个平台,用来运行不同的语言模型,如Llama2、Mistral等,而不仅仅是一个特定的模型。
此外,我们可以使用Langchain SDK,这是一个工具,可以让与Ollama的交互更加便捷。
在命令行使用Ollama非常简单。以下是一些我们可以尝试在计算机上运行Ollama的命令:
- ollama pull — 此命令从Ollama模型中心拉取模型。
- ollama rm — 此命令用于从本地计算机移除已下载的模型。
- ollama cp — 此命令用于复制模型。
- ollama list — 此命令用于查看已下载模型的列表。
- ollama run — 此命令用于运行模型,如果模型尚未下载,它将拉取模型并提供服务。
- ollama serve — 此命令用于启动服务器,以提供已下载模型的服务。
我们可以在本地机器上下载这些模型,然后通过命令行提示与这些模型进行交互。或者,当我们运行模型时,Ollama也会运行一个默认位于端口11434的推理服务器,我们可以通过API和其他库(如Langchain)与其进行交互。
截至目前,Ollama拥有74个模型,其中包括嵌入模型等类别。
.assets/1xqE17xnt2zQ0e62oiec9ZQ.png)Source : Ollama
聊天机器人应用
接下来我们将构建的三个关键聊天机器人应用是:
- 使用LangChain、ChromaDB和Streamlit与多个PDF文件聊天
- 带有Open WebUI的聊天机器人
- 使用Docker部署聊天机器人。
通过构建这三个应用,我们将建立起对如何大规模构建和部署工业级应用的直观认识。
应用1:与多个PDF文件聊天
我们将构建一个类似于ChatPDF但更简单的应用,用户可以上传多个PDF文档并通过直观的界面提问。
我们的技术栈非常简单,使用Langchain、Ollama和Streamlit。
- LLM服务器:这个应用最关键的部分就是LLM服务器。多亏了Ollama,我们拥有一个既强大又能在本地,甚至是笔记本电脑上设置的LLM服务器。虽然llama.cpp也是一个选择,但我发现用Go编写的Ollama更容易设置和运行,基于我的专业经验。
- RAG:毫无疑问,在LLM领域领先的两个库是Langchain和LLamIndex。对于这个项目,我将使用Langchain,因为我对其比较熟悉。任何RAG框架的关键组成部分之一是向量存储。在这里,我们将使用Chroma,因为它与Langchain的集成非常好。
- 聊天UI:用户界面也是重要组成部分。尽管有许多可用的技术,但我更倾向于使用Streamlit,这是一个Python库,使用起来让人安心。
好的,让我们开始设置吧。
聊天机器人可以从不同的PDF中获取信息。下面是具体分解:
- 数据源:多个PDF文件
- 存储:ChromaDB的向量存储(高效地存储和检索信息)
- 处理:LangChain API为大型语言模型(LLM)准备数据
- LLM集成:可能涉及到增强响应的检索增强生成(RAG)
- 用户界面:Streamlit创建了一个友好的聊天界面
GitHub仓库结构:https://github.com/vsingh9076/Building_LLM_Applications.git
- 克隆仓库到本地:
git clone https://github.com/vsingh9076/Building_LLM_Applications.git
cd 7_Ollama/local-pdf-chatbot
- 创建虚拟环境
python3 -m venv myenv
source myenv/bin/activate
- 安装需要的包
pip install -r requirements.txt
- 安装Ollama并根据config.yml中指定的模型拉取LLM模型[我们已经在上面的部分中覆盖了Ollama的设置]
- 使用Ollama运行LLama2模型
ollama pull llama2
ollama run llama2
- 使用Streamlit CLI运行app.py文件。执行以下命令:
streamlit run app.py
应用2:带有Open WebUI的聊天机器人
在这个应用中,我们将使用Open WebUI而不是Streamlit、Chainlit或Gradio作为用户界面来构建聊天机器人。
以下是步骤:
- 安装Ollama并部署LLM
我们可以在本地机器上直接安装Ollama,也可以在本地部署Ollama的Docker容器。选择权在我们手中,无论哪种方式都可以适用于langchain Ollama接口、Ollama官方Python接口以及Open WebUI接口。
以下是在本地系统中直接安装Ollama的说明:
设置和运行Ollama很简单。首先,访问ollama.ai,并下载适合我们操作系统的应用程序。
接下来,打开终端并执行以下命令来拉取最新的模型。虽然有众多其他LLM模型可供选择,我选择Mistral-7B是因为其紧凑的大小和竞争力的质量。
ollama pull llama2
ollama run llama2
对于所有其他模型,设置程序是相同的。我们需要拉取并运行。
2. 安装open-webui(ollama-webui)
Open WebUI是一个可扩展、功能丰富且用户友好的自托管WebUI,设计用于完全离线运行。它支持多种LLM运行器,包括Ollama和兼容OpenAI的API。
官方GitHub仓库:https://github.com/open-webui/open-webui
运行以下Docker命令以在本地机器上部署open-webui的Docker容器。
docker run -d -p 3000:8080 --add-host=host.docker.internal:host-gateway -v ollama-webui:/app/backend/data --name ollama-webui --restart always ghcr.io/ollama-webui/ollama-webui:main
3. 打开浏览器
打开浏览器并且使用端口3000访问localhost
http://localhost:3000
要开始使用,我们需要首次注册。只需点击“Sign up”按钮来创建我们的账户即可
注册后,我们将访问到open-webui的主页。
根据我们在本地机器上部署的LLM,这些选项将会在下拉菜单中显示供我们选择。
选择了之后,就可以进行聊天:
这避免了我们为了实验各种开源LLM、做演示等目的而需要创建Streamlit或Gradio的用户界面。
现在,我们可以与任何PDF文件进行聊天:
附上一个演示GIF文件,展示我们如何使用open-webui与图像进行聊天。
让我们来看看如何使用Ollama与我们定制的模型配合工作。以下是相应的步骤。
如何使用定制模型?
从GGUF导入
Ollama支持在Modelfile中导入GGUF模型:
- 创建一个名为Modelfile的文件,其中包含一条FROM指令,指向我们想要导入的模型的本地文件路径。
FROM ./vicuna-33b.Q4_0.gguf
- 在 Ollama中创建模型
ollama create example -f Modelfile
- 运行这个模型
ollama run example
从PyTorch或Safetensors导入
更多关于导入模型的信息,请参阅指南:https://github.com/ollama/ollama/blob/main/docs/import.md。
定制提示
来自Ollama库的模型可以通过提示进行定制。例如,要定制llama2模型:
ollama pull llama2
创建 Modelfile:
FROM llama2
# set the temperature to 1 [higher is more creative, lower is more coherent]
PARAMETER temperature 1
# set the system message
SYSTEM """
You are Mario from Super Mario Bros. Answer as Mario, the assistant, only.
"""
接下来,创建并运行模型:
ollama create mario -f ./Modelfile
ollama run mario
>>> hi
Hello! It's your friend Mario.
应用3:使用Docker部署聊天机器人
让我们使用Langshan构建聊天机器人应用,为了从Python应用中访问我们的模型,我们将构建一个简单的Steamlit聊天机器人应用。我们将在一个容器中部署这个Python应用,并在另一个容器中使用Ollama。我们将使用docker-compose来构建基础设施。
下图展示了容器之间的交互架构,以及它们将访问的端口。
我们构建了两个容器,
- Ollama容器使用主机卷来存储和加载模型(/root/.ollama映射到本地的./data/ollama)。Ollama容器监听11434端口(外部端口,内部映射到11434)。
- Streamlit聊天机器人应用将监听8501端口(外部端口,内部映射到8501)。
GitHub仓库:
这段翻译详细描述了两个容器的设置,包括Ollama容器和Streamlit聊天机器人应用的端口映射情况,以及指出了GitHub仓库的存在,如果需要进一步的翻译或有其他问题,随时告诉我。然而,实际的GitHub仓库链接需要由用户提供,这里只是描述了其存在。
克隆仓库到本地:
git clone https://github.com/vsingh9076/Building_LLM_Applications.git
cd 7_Ollama/docker-pdf-chatbot
创建虚拟环境:
python3 -m venv ./ollama-langchain-venv
source ./ollama-langchain-venv/bin/activate
按照所需的包:
pip install -r requirements.txt
Ollama是一个框架,允许我们将Ollama服务器作为Docker镜像运行。这对于构建使用Ollama模型的微服务应用非常有用。我们可以在Docker生态系统中轻松部署我们的应用,如OpenShift、Kubernetes等。要在Docker中运行Ollama,我们必须使用docker run命令,如下所示。在此之前,我们应该已经在我们的系统中安装了Docker。
docker run -d -v ollama:/root/.ollama -p 11434:11434 --name ollama ollama/ollama
下面是输出 :
然后,我们应该能够使用docker exec与这个容器进行交互,如下所示,并运行提示。
docker exec -it ollama ollama run phi
在上面的命令中,我们使用docker运行phi
curl http://localhost:11434/api/generate -d '{
"model": "phi:latest",
"prompt":"Who are you?",
"stream":false
}'
下面是结果:
需要注意的是,Docker容器是短暂的,无论我们拉取什么模型,当重启容器时都会消失。在下一篇博客中,我们将解决这个问题,从零开始构建一个分布式Streamlit应用。我们将把容器的卷与宿主机进行映射。
Ollama是一个强大的工具,它开启了在云上创建和运行LLM应用的新途径。它简化了开发流程,并提供了灵活的部署选项。它还允许轻松管理和扩展应用。
现在,让我们开始Streamlit应用的构建吧。
我们正在使用Ollama,并通过Ollama Langchain库(这是langchain_community的一部分)调用模型。
我们在requirement.txt中定义依赖项。
现在,让我们定义一个Dockerfile来构建Streamlit应用的Docker镜像。
我们使用Python的Docker镜像作为基础镜像,并创建一个名为/app的工作目录。然后,我们将应用文件复制到该目录,并运行pip install来安装所有依赖。接着,我们暴露8501端口,并启动streamlit应用。
可以使用docker build命令来构建Docker镜像,如下所示。
docker build . -t viprasingh/ollama-langchain:0.1
我们应该能够使用docker images命令检查Docker镜像是否已经构建,如下所示。
现在,让我们构建一个docker-compose配置文件,来定义Streamlit应用和Ollama容器之间的网络,以便它们能够相互通信。我们还将定义上图中所示的各种端口配置。对于Ollama,我们还将映射卷,这样拉取的任何模型都将被持久化。
我们可以通过运行docker-compose up命令来启动应用,一旦执行docker-compose up,我们会看到两个容器都开始运行,如下图所示。
我们可以通过执行docker-compose ps命令,如下所示,来查看容器是否正在运行。
img
我们现在访问http://localhost:11434,如下图所示,来检查Ollama是否正在运行。
现在,让我们通过使用docker exec命令登录到docker容器,如下所示,来下载所需的模型。
docker exec -it docker-pdf-chatbot-ollama-container-1 ollama run phi
因为我们使用的是phi模型,所以我们正在拉取这个模型,并通过运行它来进行测试。我们可以在下面的截图中看到,phi模型已经被下载,并将开始运行(因为我们使用了-it标志,所以我们应该能够通过交互和使用示例提示来测试它)
我们可以在本地文件夹./data/ollama中看到下载的模型文件和清单(在容器内部,这个文件夹映射到/root/.ollama,这是Ollama查找已下载模型以提供服务的位置)
现在,让我们通过在浏览器中打开http://localhost:8501来访问我们的Streamlit应用。以下截图显示了应用界面:
让我们尝试运行一个提示:“generate a story about dog called bozo”。我们应该能够在控制台日志中看到来自我们Streamlit应用的API调用,如下所示:
我们可以在下面的截图中看到,对于我发送的提示,我得到了这样的响应:
我们可以通过调用docker-compose down来停止部署。
以下截图显示了输出结果:
就这样完成了。在本次博客中,我们非常愉快地探索了如何让Ollama与Langchain协同工作,并使用Docker-Compose在Docker上部署它们。
总结
这篇博客探讨了在本地构建大型语言模型(LLM)应用,重点放在了增强检索生成(RAG)链上。它涵盖了LLM服务器(由Ollama驱动)、LangChain框架、用于嵌入的Chroma以及用于Web应用的Streamlit等组件。它详细介绍了如何使用Ollama、LangChain、ChromaDB和Streamlit创建聊天机器人应用,包括GitHub仓库结构和Docker部署。总体而言,它提供了一份高效开发LLM应用的实用指南。
—END—
英文原文:https://medium.com/@
vipra_singh/building-llm-applications-open-source-chatbots-part-7-1ca9c3653175