指导如何基于开源 LLM 构建 AI 应用而非依赖 OpenAI API。适合想自建 AI 服务的开发者参考。
想象一下有一个个人助手,可以进行交互式对话,提供深刻的信息,帮助你浏览庞大的知识库。一个理解你的查询,并用相关且定制化的答案做出回应的伴侣。这正是我在最新项目中想要实现的目标。
作为一个拥有关于深度学习的丰富知识的人,这些知识精心整理在我的 Obsidian Vault 中,我想创建一个个人助手,可以作为与这个知识库交互的智能接口。我设想了一个无缝的体验,我可以进行自然语言对话,轻松地从我的大量信息库中检索有价值的见解。
在我之前的博客中,我探讨了使用 LangChain 和 OpenAI 创建你自己的 YouTube GPT 的令人兴奋的可能性。我们创建了一个系统,使我们能够以全新的方式与视频内容互动。OpenAI 虽然无疑很聪明,但并不像它的名字所暗示的那样开放。它需要付费,一道付费墙将好奇的人们与无限的可能性分隔开来。然而,这一次,我们走的是不同的道路——一条通往开源模型世界的路,在那里自由和可访问性至高无上。在这篇博客中,我们将深入探讨自由可用的开源 AI 模型领域,摆脱 OpenAI 等专有系统的限制。我们将利用由 Mosaic ML 开发、由 GPT4All 提供服务的 MPT-7B-Instruct 模型的功能,结合 LangChain,共同解锁 AI 的真正潜力。
MPT-7B 是由 Mosaic ML 的杰出人才创建的一个了不起的开源语言模型。这个模型真正了不起,它能够理解和生成感觉像人类对话的文本。MPT-7B 的与众不同之处在于它特别针对提供指导性回应进行了微调,使其成为构建你自己个性化 AI 伴侣的理想选择。
LangChain 是一个强大的工具,便于创建针对特定领域或语言定制的 AI 模型。它允许用户在自定义数据集上训练语言模型,使其非常适合开发专业化的 AI 应用。使用 LangChain,你可以微调模型以满足你的独特需求,实现更准确和上下文感知的回应。通过利用 LangChain 的功能,你可以提高 AI 系统的性能,创建一个更加个性化和有效的对话体验。
现在让我们开始编码……如往常一样,本文底部会提供 Git Repo 的链接。
创建一个新的 Python 项目并启动一个新的虚拟环境。创建一个名为 private_gpt.py 的 Python 文件。
为了开始构建你的个性化 AI 伴侣,需要安装几个依赖项。上面的代码块显示了需要安装的必要包,可以通过运行以下命令完成:
pip install -qU langchain tiktoken gpt4all streamlit-chat einops transformers accelerate chromadb
为了启动个性化 AI 助手的开发,我们首先导入必要的依赖项。这些库和模块将为我们的助手的功能提供基础,并使我们能够与各种组件无缝交互。
from langchain import ConversationChain,PromptTemplate
import torch
from langchain.embeddings import HuggingFaceEmbeddings
from langchain.indexes import VectorstoreIndexCreator
from langchain.document_loaders import TextLoader, DirectoryLoader
from langchain.llms import GPT4All
import os
from langchain.memory import VectorStoreRetrieverMemory
import streamlit as st
from streamlit_chat import message
loader = DirectoryLoader('D:/OneDrive/Documents/Obsidian/Projects/myVault/', glob="**/*.md",recursive=True, show_progress=True, use_multithreading=True, loader_cls=TextLoader)
docs = loader.load()
len(docs)
现在我们已经导入了必要的依赖项,让我们继续创建一个数据加载器。这个加载器将使我们能够从知识库中加载和处理文本文档,这将成为我们个性化 AI 助手的宝贵信息来源。
在上面的代码块中,我们使用来自 langchain.document_loaders 的 DirectoryLoader 类初始化数据加载器。我们将存储文本文档的目录路径作为第一个参数传递。在这个例子中,目录路径是 'D:/OneDrive/Documents/Obsidian/Projects/myVault/'。请随意用你自己的文本文档目录的路径替换它。
下一个参数 glob 允许我们指定我们要加载的文档的文件模式或扩展名。在这种情况下,我们使用 "**/.md" 来加载目录及其子目录中的所有 Markdown 文件(.md)。你可以修改这个模式以适应你的特定文件类型或命名约定。
设置 recursive=True 确保加载器探索指定目录中的子目录,如果必要,使我们能够从嵌套结构中加载文档。
show_progress 参数控制加载器在加载文档时是否显示进度条。将其设置为 True 可以提供对加载过程的可见性,特别是对于较大的知识库非常有用。
为了提高性能,我们可以通过设置 use_multithreading=True 来利用多线程。这通过并发加载多个文档来加快加载过程。
最后,我们将加载器类指定为 TextLoader,它指示数据加载器将每个文档视为文本文件。
设置好加载器后,我们继续使用 loader.load() 方法加载文档。这返回一个文档对象的列表。
为了验证文档加载是否成功,我们使用 len(docs) 打印 docs 列表的长度。这为我们提供了加载文档的计数,确保我们的数据加载器按预期工作。
embeddings = HuggingFaceEmbeddings(model_name="all-mpnet-base-v2")
llm = GPT4All(model="./ggml-mpt-7b-instruct.bin", top_p=0.5, top_k=0, temp=0.5, repeat_penalty=1.1, n_threads=12, n_batch=16, n_ctx=2048)
为了增强我们的个性化 AI 助手的语言理解和生成能力,我们需要实例化嵌入和语言模型(LLM)。这些组件将使我们的助手能够理解对话的上下文并生成连贯且相关的回应。
在提供的代码块中,我们从 langchain.embeddings 创建了 HuggingFaceEmbeddings 类的一个实例。这个类允许我们利用来自 Hugging Face 库的预训练词嵌入,这些是捕捉我们语言模型中单词含义和上下文的强大工具。我们将 model_name 参数指定为 "all-mpnet-base-v2",它对应于 Hugging Face 的模型库中的特定预训练模型。你可以探索其他可用模型或选择最适合你的需求的模型。
接下来,我们使用来自 langchain.llms 的 GPT4All 类实例化语言模型。我们传入 model 参数来指定我们预训练语言模型的路径。在这个例子中,模型位于 "./ggml-mpt-7b-instruct.bin"。请确保你提供了你自己的预训练模型的正确路径。
此外,我们设置了几个参数来微调我们的语言模型的行为。这些参数包括:
top_p=0.5:这个参数决定了模型在文本生成过程中采样的累积概率阈值。较低的值会产生更集中和确定性的回应。
top_k=0:这个参数设置了在文本生成过程中要考虑的最高概率 token 的数量。将其设置为 0 意味着考虑所有 token,允许更多样化的回应。
temp=0.5:temp 参数控制模型在采样过程中的 softmax 分布的温度。较高的值(例如 1.0)会导致生成的文本中有更多的随机性和创意。
repeat_penalty=1.1:这个参数不鼓励模型过度重复相同的短语或模式。增加该值会进一步减少重复的回应。
n_threads=12 和 n_batch=16:这些参数决定了在文本生成过程中并行处理使用的线程数和批大小。调整这些值可以优化我们语言模型的性能。
随意尝试这些值以获得合适的结果。或者你可以坚持这些值。
index = VectorstoreIndexCreator(embedding=embeddings).from_loaders([loader])
retriever = index.vectorstore.as_retriever(search_kwargs=dict(k=2))
memory = VectorStoreRetrieverMemory(retriever=retriever)
为了高效地检索信息,我们使用来自 langchain.indexes 的 VectorstoreIndexCreator 创建一个向量数据库。它利用我们的预训练词嵌入来有效地索引加载的文档。
使用 from_loaders 方法,我们使用我们选择的嵌入从加载的文档生成向量数据库。
接下来,我们使用向量数据库的 as_retriever 方法创建一个检索器。这允许我们基于查询搜索相关文档,为上下文感知的回应提供必要的信息。
最后,我们使用 VectorStoreRetrieverMemory 创建一个内存系统,它连接向量数据库和语言模型。它增强了助手在对话过程中回忆相关信息的能力,确保准确和上下文适当的回应。
MPT-7B Instruct 模型是在格式化为 dolly-15k 格式的数据上训练的,如下所示:
_DEFAULT_TEMPLATE = """
Below is an instruction that describes a task. Write a response that appropriately completes the request.
###Instruction: The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.
Do not make up answers and provide only information that you have.
Relevant pieces of previous conversation:
{history}
(You do not need to use these pieces of information if not relevant)
{input}
### Response:
"""
为了为我们的个性化 AI 助手结构化提示,我们使用了专门为 MPT-7B Instruct 模型选择的模板。这个模型是在 dolly-15k 格式的数据上训练的,这种格式以有效地生成指导性回应而闻名。
该模板包括一个指示部分,其中鼓励 AI 提供详细和特定于上下文的信息。它还强调 AI 应该在不知道答案时承认这一点。
以前对话的相关部分和提供的任何额外输入都被合并到提示模板中。这些元素有助于 AI 对对话上下文的理解。
模板的响应部分是空白的,允许 AI 基于给定的上下文和指示生成其响应。
PROMPT = PromptTemplate(
input_variables=[ "history", "input"], template=_DEFAULT_TEMPLATE
)
conversation_with_summary = ConversationChain(
llm=llm,
prompt=PROMPT,
# We set a very low max_token_limit for the purposes of testing.
memory = memory,
verbose=True
)
with torch.inference_mode():
conversation_with_summary.predict(input = "Make me a study plan to study deep learning")
现在我们已经设置了提示模板并为个性化 AI 助手创建了必要的组件,我们可以继续推理阶段。在这个阶段,我们利用对话链基于给定的输入和上下文生成回应。
在提供的代码块中,我们使用 PromptTemplate 类定义一个 PROMPT 对象。这个模板包含输入变量 history 和 input,以及我们之前讨论的默认模板 _DEFAULT_TEMPLATE。它作为我们对话的结构,指导 AI 的回应。
接下来,我们创建一个名为 conversation_with_summary 的 ConversationChain 对象。这个链利用我们的语言模型(llm)、提示模板(PROMPT)和内存系统(memory)来生成回应。我们还设置 verbose=True 以在对话过程中启用详细输出。
在 with torch.inference_mode() 块内,我们调用 conversation_with_summary 对象的 predict 方法。我们传入输入 "give me more details" 来启动对话。AI 将利用提示模板、对话历史中的上下文和内存系统来生成相关和信息丰富的回应。
在这个推理阶段,AI 将利用其知识和上下文理解来生成与对话流程和用户查询相一致的回应。对话链确保生成的回应是连贯的和上下文适当的。
st.set_page_config(
page_title="PrivateGPT",
page_icon=":robot:"
)
st.header("PrivateGPT")
if 'generated' not in st.session_state:
st.session_state['generated'] = []
if 'past' not in st.session_state:
st.session_state['past'] = []
def get_text():
input_text = st.text_input("You: ","Hello, how are you?", key="input")
return input_text
user_input = get_text()
def writeText(output):
st.session_state.generated.append(output)
if user_input:
with torch.inference_mode():
st.session_state.past.append(user_input)
st.session_state.generated.append(conversation_with_summary.predict(input = user_input))
if st.session_state['generated']:
for i in range(len(st.session_state['generated'])-1, -1, -1):
message(st.session_state["generated"][i], key=str(i))
message(st.session_state['past'][i], is_user=True, key=str(i) + '_user')
streamlit run private_gpt.py
Git Repository: https://github.com/akshayballal95/youtube_gpt.git
my website: http://www.akshaymakes.com/
linkedin: https://www.linkedin.com/in/akshay-ballal/
twitter: https://twitter.com/akshayballal95/