LangChain + Streamlit+ Llama: 将对话式AI带到你的本地机器

2023年07月07日 由 Alex 发表 159347 0
在过去的几个月中,大型语言模型(LLMs)引起了广泛关注,吸引了全球开发者的兴趣。这些模型为开发聊天机器人、个人助理和内容创作等领域带来了令人期待的前景。LLMs所带来的可能性在开发者、人工智能和自然语言处理社区中引发了潮流。

什么是LLMs?


大型语言模型(LLMs)指的是能够生成与人类语言密切相似并以自然方式理解提示信息的机器学习模型。这些模型通过使用包括书籍、文章、网站和其他来源的大量数据集进行训练。通过分析数据中的统计模式,LLMs预测给定输入后最可能出现的单词或短语。


近年来法LLMs的时间线


通过利用大型语言模型(LLMs),我们可以将特定领域的数据整合到模型中,进而有效地回答问题。当处理模型在初始训练时无法获得的信息时,比如公司的内部文档或知识库,这将特别有优势。

用于此目的的架构称为检索增强生成(Retrieval Augmentation Generation),或更少见地称为生成问答(Generative Question Answering)。

什么是LangChain?


LangChain是一个令人印象深刻且免费提供的框架,精心设计用来赋予开发者在创建应用程序时利用语言模型、尤其是大型语言模型(LLMs)的强大能力。

LangChain改变了各种应用程序的开发过程,包括聊天机器人、生成问答(GQA)和文本摘要。通过无缝地将来自多个模块的各个组件链结在一起,LangChain实现了基于LLMs强大能力的卓越应用程序的创建。

动机?




在本文中,我将演示从零开始创建自己的文档助手的过程,利用LLaMA 7b和Langchain,这是一个专门为与LLMs无缝集成而开发的开源库。

下面是介绍过程的细节:

1. 设置虚拟环境和创建文件结构

2. 在本地计算机上使用LLM

3. 将LLM与LangChain集成并自定义PromptTemplate

4. 文档检索和答案生成

5. 使用Streamlit构建应用程序

第1部分:设置虚拟环境和创建文件结构


设置虚拟环境为运行应用程序提供了一个受控且隔离的环境,确保其依赖项与系统范围的其他软件包分离。这种方法简化了依赖项管理,并有助于在不同环境之间保持一致性。

为了为此应用程序设置虚拟环境,我将在我的GitHub存储库中提供pip文件。首先,让我们按照图上所示创建必要的文件结构。或者,你也可以直接克隆存储库以获取所需的文件。


文件结构


在models文件夹中,我们将存储我们下载的LLMs,而pip文件将位于根目录中。

要在虚拟环境中创建并安装所有依赖项,我们可以在相同目录中使用pipenv install命令,或者直接运行setup_env.bat批处理文件。它将从pipfile中安装所有依赖项。这将确保在虚拟环境中安装所有必要的包和库。一旦成功安装了依赖项,我们可以继续下一步,即下载所需的模型。

第2部分: 在本地计算机上获取LLaMA


LLaMA是什么?

LLaMA是由Meta AI设计的新一代大型语言模型,Meta AI是Facebook的母公司。LLaMA拥有从70亿到650亿参数的多样化的模型系列,因此成为最全面的语言模型之一。2023年2月24日,Meta将LLaMA模型向公众发布,展示了他们对开放科学的承诺。



考虑到LLaMA的卓越能力,我们选择利用这个强大的语言模型来实现我们的目标。具体而言,我们将使用LLaMA的最小版本,即LLaMA 7B。即使在这个较小的情况下,LLaMA 7B仍具有显著的语言处理能力,使我们能够高效有效地实现我们的预期结果。

要在本地CPU上执行LLM,我们需要一个本地的GGML格式模型。有几种方法可以实现这一点,但最简单的方法是直接从Hugging Face Models存储库下载bin文件。当前情况下,我们将下载Llama 7B模型。这些模型是开源的,可以免费下载。

什么是GGML?为什么是GGML?如何GGML?LLaMA CPP??

GGML是一种用于机器学习的 Tensor 库,它只是一个C++库,可以让你在CPU或CPU+GPU上运行LLMs。它定义了一种用于分发大型语言模型(LLMs)的二进制格式。GGML利用一种称为量化的技术,使得大型语言模型可以在消费级硬件上运行。

那么什么是量化呢?

LLM的权重是浮点数(小数)。就像表示一个大整数(例如1000)需要更多的空间,而表示一个小整数(例如1)需要较少的空间一样,表示高精度浮点数(例如0.0001)需要更多的空间,而表示低精度浮点数(例如0.1)需要较少的空间。将大型语言模型量化的过程涉及减少权重表示的精度,以减少使用该模型所需的资源。GGML支持多种不同的量化策略(例如4位、5位和8位量化),每种策略在效率和性能之间有不同的权衡。



为了有效地使用这些模型,考虑到内存和磁盘的需求是至关重要的。由于目前模型完全加载到内存中,你需要足够的磁盘空间来存储它们,并且在执行期间还需要足够的内存来加载它们。对于65B模型,即使经过量化处理,建议至少有40GB 的可用内存。值得注意的是,目前内存和磁盘的需求是相等的。

量化在处理这些资源需求方面起着关键作用。通过降低模型参数的精度并优化内存使用,量化使得这些模型可以在更为普通的硬件配置上使用。这确保了在更广泛的设定上运行这些模型仍然可行和高效。

如果它是一个c++库,我们如何在Python中使用它?

这就是Python绑定的作用。绑定指的是在两种语言之间创建桥接或接口的过程,对于我们来说是Python和C++。我们将使用llama-cpp-python,这是一个用于llama.cpp的Python绑定,它充当纯C/C++中的LLaMA模型推理引擎。llama.cpp的主要目标是使用4位整数量化来运行LLaMA模型。这种集成使我们能够有效地利用LLaMA模型,充分发挥C/C++实现的优势和4位整数量化的好处。


llama.cpp 支持的模型


准备好了GGML模型并安装了所有的依赖项,是时候开始我们与LangChain的探索之旅了。但在深入LangChain这个之前,让我们先按照惯例进行一次“Hello World”- 这是我们在探索新语言或框架时遵循的传统之一,毕竟LLM也是一种语言模型。



我们成功地在CPU上执行了我们的第一个LLM,完全离线并以完全随机的方式(你可以调整温度超参数)进行操作。

为了实现这个里程碑,我们现在准备开始我们的主要目标:使用LangChain框架对自定义文本进行问答。

第3部分:入门LLM - LangChain集成


前面,我们使用llama cpp初始化了LLM。现在,让我们利用LangChain框架来开发使用LLMs的应用程序。你可以通过文本与它们进行交互,这是主要的接口。一种过度简化的说法是,很多模型都是“文本输入,文本输出”。因此,LangChain中的很多接口都围绕着文本展开。

提示工程的兴起

在不断发展的编程领域中,出现了一个引人入胜的新范式:提示(Prompting)。提示是指向语言模型提供特定的输入以引出所需的响应。这种创新的方法使我们能够根据我们提供的输入来塑造模型的输出。

令人惊讶的是,我们表达提示的方式中微妙的差别可以极大地影响模型的响应的性质和内容。根据措辞的不同,结果可能会有根本性的差异,凸显了在制定提示时需要仔细考虑的重要性。

为了与LLMs进行无缝交互,LangChain提供了几个类和函数,使用提示模板使构建和处理提示变得简单。提示模板是一种可复现的生成提示的方式。它包含一个文本字符串模板,可以从最终用户那里获取一组参数并生成一个提示。让我们看几个例子。


没有输入变量的提示



使用一个数额变量进行提示



提示多个输入变量


希望之前的解释已经让你更清楚地理解了提示的概念。现在,让我们继续进行提示LLM的步骤。


通过 Langchain LLM 进行提示


这种方法运行良好,但这并不是对LangChain的最佳利用方式。到目前为止,我们使用的是单独的组件。我们拿到了格式化的提示模板,然后拿到了LLM,然后将这些参数传递到LLM中以生成答案。单独使用LLM对于简单的应用程序来说是可以的,但更复杂的应用程序需要将LLM进行链接 - 要么与其他LLMs进行链接,要么与其他组件进行链接。

LangChain提供了Chain接口,用于这种链式应用程序。我们非常通用地将链定义为对组件的一系列调用,其中可以包括其他链。链允许我们将多个组件组合在一起,创建一个单一而一致的应用程序。例如,我们可以创建一个链,接收用户输入,并使用提示模板进行格式化,然后将格式化后的响应传递给LLM。通过将多个链组合在一起或将链与其他组件组合,我们可以构建更复杂的链式结构。

为了理解它,让我们创建一个非常简单的链式结构,它将接收用户输入,使用它来格式化提示,然后使用之前创建的各个独立组件将其发送到LLM。


LangChain 中的链接


在处理多个变量时,你可以选择使用字典将它们一起输入。下面我们将外部文本作为问题回答器的检索器加入进来。

第4部分:生成用于问题回答的嵌入和向量存储


在许多LLM应用中,需要使用者特定的数据,而这些数据在模型的训练集中并不包含。LangChain提供了必要的组件,用于加载、转换、存储和查询你的数据。


LangChain中的数据连接


这五个阶段是:

1. 文档加载器(Document Loader):用于加载数据作为文档。

2. 文档转换器(Document Transformer):将文档分割为较小的块。

3. 嵌入(Embeddings):将块转换为向量表示,也称为嵌入(embedding)。

4. 向量存储(Vector Stores):用于将以上块向量存储在向量数据库中。

5. 检索器(Retrievers):用于检索一组或多组与查询结果在相同潜在空间中嵌入的向量最相似的向量。


文档检索/问答循环


现在,我们将逐步讲解每个步骤,执行检索操作,找到与查询最相似的文档块。在找到文档块之后,我们可以根据其生成答案,如所提供的图像所示。

然而,在继续之前,我们需要准备一段文本来执行上述任务。为了这个虚构的测试,我复制了一段关于一些受欢迎的DC超级英雄的维基百科文本。以下是这段文本的内容:


用于测试的原始文本


加载和转换文档

首先,让我们创建一个文档对象。在这个例子中,我们将使用文本加载器(text loader)。但是,LangChain支持多个文档来源,因此根据你的具体文档,你可以使用不同的加载器。接下来,我们将使用load方法从预配置的来源中检索数据并将其加载为文档。

文档加载完成后,我们就可以继续进行转换过程,将其分割成较小的块。为了实现这一点,我们将使用TextSplitter。默认情况下,分隔符是'\n\n'。但是,如果你将分隔符设为null,并定义一个特定的块大小,那么每个块的长度将为指定的大小。结果列表的长度将等于文档长度除以块大小的结果。简而言之,它将类似于:list length = length of doc / chunk size

加载和转换文档


旅程的一部分是嵌入!

这是最重要的一步。嵌入(Embeddings)生成了文本内容的矢量表示。这具有实际意义,因为它使我们能够在矢量空间中概念化文本。

词嵌入(Word embeddings)只是单词的矢量表示,其中矢量包含实数。由于语言通常包含至少数万个单词,简单的二进制单词向量由于高维度而变得不实用。词嵌入通过在低维向量空间中提供单词的密集表示来解决这个问题。

当我们谈论检索时,我们指的是检索一组向量,这些向量与嵌入在相同的潜在空间中的查询向量最相似。

LangChain中的基础嵌入(Embeddings)类提供了两种方法:一个用于嵌入文档,另一个用于嵌入查询。前者采用多个文本作为输入,而后者采用单个文本作为输入。



创建矢量存储和检索文档

矢量存储有效地管理嵌入数据的存储,并代表你执行向量搜索操作。嵌入和存储嵌入矢量是存储和搜索非结构化数据的常用方法。在查询时,非结构化的查询也被嵌入,并检索与嵌入查询最相似的嵌入向量。这种方法使得可以从矢量存储中有效地检索相关信息。

在这里,我们将使用Chroma,一个专门设计用于简化嵌入式AI应用程序开发的嵌入式数据库和矢量存储。它提供了一套全面的内置工具和功能,以促进你的初始设置,而所有这些都可以通过执行简单的pip install chromadb命令方便地安装在本地机器上。


创建矢量存储


到目前为止,我们已经见证了在广泛的文档集合中使用嵌入和向量存储来检索相关块的卓越能力。现在,我们就来将检索到的块作为上下文与查询一起呈现给LLM。

然而,强调构建良好结构的提示的重要性是至关重要的。通过制定精心设计的提示,我们可以减少LLM的产生幻觉的潜在可能性 - 当面临不确定性时,它可能会捏造事实。

现在让我们进入最后阶段,看看我们的LLM是否能够生成令人信服的答案。


与文档的问答


我们做到了,我们刚刚构建了自己的问题回答机器人,利用的是在本地运行的LLM模型。

第5部分:使用Streamlit进行链路可视化


这部分完全是可选的,因为它并不是Streamlit的全面指南。我不会深入探讨这一部分;相反,我将呈现一个基本的应用程序,允许用户上传任何文本文档。他们将可以通过文本输入提出问题。

然而,在使用Streamlit进行文件上传时有一个注意事项。为了防止潜在的内存错误,特别是考虑到LLM的内存密集型特性,我将简单地将文档读取并写入到我们文件结构中的临时文件夹中,命名为raw.txt。这样,无论文档原来的名称是什么,Textloader将可以无缝处理它。

目前,该应用程序设计用于文本文件,但你可以根据需要适应PDF、CSV或其他格式。基本概念保持不变,因为LLM主要设计用于文本输入和输出。此外,你还可以尝试使用Llama C++绑定支持的不同LLM模型。

在进一步深入细节之前,我向你呈现应用程序的代码。请随意根据你特定的用例进行自定义。
# Bring in deps
import streamlit as st
from langchain.llms import LlamaCpp
from langchain.embeddings import LlamaCppEmbeddings
from langchain.prompts import PromptTemplate
from langchain.chains import LLMChain
from langchain.document_loaders import TextLoader
from langchain.text_splitter import CharacterTextSplitter
from langchain.vectorstores import Chroma


# Customize the layout
st.set_page_config(page_title="DOCAI", page_icon="", layout="wide", )
st.markdown(f"""

""", unsafe_allow_html=True)


# function for writing uploaded file in temp
def write_text_file(content, file_path):
try:
with open(file_path, 'w') as file:
file.write(content)
return True
except Exception as e:
print(f"Error occurred while writing the file: {e}")
return False

# set prompt template
prompt_template = """Use the following pieces of context to answer the question at the end. If you don't know the answer, just say that you don't know, don't try to make up an answer.

{context}

Question: {question}
Answer:"""
prompt = PromptTemplate(template=prompt_template, input_variables=["context", "question"])

# initialize hte LLM & Embeddings
llm = LlamaCpp(model_path="./models/llama-7b.ggmlv3.q4_0.bin")
embeddings = LlamaCppEmbeddings(model_path="models/llama-7b.ggmlv3.q4_0.bin")
llm_chain = LLMChain(llm=llm, prompt=prompt)

st.title(" Document Conversation ")
uploaded_file = st.file_uploader("Upload an article", type="txt")

if uploaded_file is not None:
content = uploaded_file.read().decode('utf-8')
# st.write(content)
file_path = "temp/file.txt"
write_text_file(content, file_path)

loader = TextLoader(file_path)
docs = loader.load()
text_splitter = CharacterTextSplitter(chunk_size=100, chunk_overlap=0)
texts = text_splitter.split_documents(docs)
db = Chroma.from_documents(texts, embeddings)
st.success("File Loaded Successfully!!")

# Query through LLM
question = st.text_input("Ask something from the file", placeholder="Find something similar to: ....this.... in the text?", disabled=not uploaded_file,)
if question:
similar_doc = db.similarity_search(question, k=1)
context = similar_doc[0].page_content
query_llm = LLMChain(llm=llm, prompt=prompt)
response = query_llm.run({"context": context, "question": question})
st.write(response)

这就是streamlit应用程序的外观示例:



这一次,我输入了从维基百科复制的《黑暗骑士》的情节,并问:“谁的脸被严重烧伤了?”LLM的回答是——哈维·登特(Harvey Dent)。



 

来源:https://ai.plainenglish.io/%EF%B8%8F-langchain-streamlit-llama-bringing-conversational-ai-to-your-local-machine-a1736252b172
欢迎关注ATYUN官方公众号
商务合作及内容投稿请联系邮箱:bd@atyun.com
评论 登录
写评论取消
回复取消