Demo 练习——基于 InternLM 和 LangChain 搭建你的知识库:
一、环境配置
1.1 InternLM 模型部署
1、在 InternStudio 平台中选择A100(1/4)的配置,参考上一篇《大模型实战营课程笔记 ②》。进入开发机,打开终端,开始环境配置、模型下载和运行demo。
2、进入开发机,打开VScode-终端,并在终端输入bash命令,进入conda环境
bash
/root/share/install_conda_env_internlm_base.sh InternLM
conda activate InternLM
3、在环境中安装运行demo所需要的依赖
# 升级pip
python -m pip install --upgrade pip
pip install modelscope==1.9.5
pip install transformers==4.35.2
pip install streamlit==1.24.0
pip install sentencepiece==0.1.99
pip install accelerate==0.24.1
1.2 模型下载
在本地 /root/share/temp/model_repos/internlm-chat-7b 目录下已存储所需的模型文件参数,可以直接拷贝到个人目录的模型保存地址:
mkdir -p /root/data/model/Shanghai_AI_Laboratory
cp -r /root/share/temp/model_repos/internlm-chat-7b /root/data/model/Shanghai_AI_Laboratory/internlm-chat-7b
1.3 LangChain相关环境配置
1、还需要安装以下依赖包:
pip install langchain==0.0.292
pip install gradio==4.4.0
pip install chromadb==0.4.15
pip install sentence-transformers==2.2.2
pip install unstructured==0.10.30
pip install markdown==3.3.7
2、使用huggingface官方提供的huggingface-cli 命令行工具。安装依赖:
pip install -U huggingface_hub
3、在 /root/data目录下新建python文件download_hf.py,用huggingface镜像下载,填入以下代码:
import os
# 设置环境变量
os.environ['HF_ENDPOINT'] = 'https://hf-mirror.com'
# 下载模型
os.system('huggingface-cli download --resume-download sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2 --local-dir /root/data/model/sentence-transformer')
4、在 /root/data 目录下执行该脚本即可自动开始下载:
python download_hf.py
1.4 下载NLTK相关资源
在使用开源词向量模型构建开源词向量的时候,需要用到第三方库nltk的一些资源。因此,可以从国内仓库镜像地址上下载相关资源,保存到服务器上。
cd /root
git clone https://gitee.com/yzy0612/nltk_data.git --branch gh-pages
cd nltk_data
mv packages/* ./
cd tokenizers
unzip punkt.zip
cd ../taggers
unzip averaged_perceptron_tagger.zip
二、知识库搭建
1.1 数据收集
1、将上述远程开源仓库Clone到本地
# 进入到数据库盘
cd /root/data
# clone 上述开源仓库
git clone https://gitee.com/open-compass/opencompass.git
git clone https://gitee.com/InternLM/lmdeploy.git
git clone https://gitee.com/InternLM/xtuner.git
git clone https://gitee.com/InternLM/InternLM-XComposer.git
git clone https://gitee.com/InternLM/lagent.git
git clone https://gitee.com/InternLM/InternLM.git
2、在/root/data目录下新建文件夹demo,用于存放个人知识库代码。在该文件夹下,新建一个构建向量数据库的python代码(create_db.py)。
# 首先导入所需第三方库
from langchain.document_loaders import UnstructuredFileLoader
from langchain.document_loaders import UnstructuredMarkdownLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.vectorstores import Chroma
from langchain.embeddings.huggingface import HuggingFaceEmbeddings
from tqdm import tqdm
import os
# 获取文件路径函数
def get_files(dir_path):
# args:dir_path,目标文件夹路径
file_list = []
for filepath, dirnames, filenames in os.walk(dir_path):
# os.walk 函数将递归遍历指定文件夹
for filename in filenames:
# 通过后缀名判断文件类型是否满足要求
if filename.endswith(".md"):
# 如果满足要求,将其绝对路径加入到结果列表
file_list.append(os.path.join(filepath, filename))
elif filename.endswith(".txt"):
file_list.append(os.path.join(filepath, filename))
return file_list
# 加载文件函数
def get_text(dir_path):
# args:dir_path,目标文件夹路径
# 首先调用上文定义的函数得到目标文件路径列表
file_lst = get_files(dir_path)
# docs 存放加载之后的纯文本对象
docs = []
# 遍历所有目标文件
for one_file in tqdm(file_lst):
file_type = one_file.split('.')[-1]
if file_type == 'md':
loader = UnstructuredMarkdownLoader(one_file)
elif file_type == 'txt':
loader = UnstructuredFileLoader(one_file)
else:
# 如果是不符合条件的文件,直接跳过
continue
docs.extend(loader.load())
return docs
# 目标文件夹
tar_dir = [
"/root/data/InternLM",
"/root/data/InternLM-XComposer",
"/root/data/lagent",
"/root/data/lmdeploy",
"/root/data/opencompass",
"/root/data/xtuner"
]
# 加载目标文件
docs = []
for dir_path in tar_dir:
docs.extend(get_text(dir_path))
# 对文本进行分块
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500, chunk_overlap=150)
split_docs = text_splitter.split_documents(docs)
# 加载开源词向量模型
embeddings = HuggingFaceEmbeddings(model_name="/root/data/model/sentence-transformer")
# 构建向量数据库
# 定义持久化路径
persist_directory = 'data_base/vector_db/chroma'
# 加载数据库
vectordb = Chroma.from_documents(
documents=split_docs,
embedding=embeddings,
persist_directory=persist_directory # 允许我们将persist_directory目录保存到磁盘上
)
# 将加载的向量数据库持久化到磁盘上
vectordb.persist()
(① 最开始导入第三方库;
② 定义两个函数,第一个是get_files函数,其参数是dir_path,目标文件夹路径。调用os.walk函数,递归这个文件夹。通过后缀名判断文件类型是否满足要求。选用md文件和txt文件为语料库中的原文件,加入到file_list,目标列表中,将该列表返回出来。
③ 另外一个,定义了一个加载文件函数get_text函数,其参数是dir_path,对其进行判断,若文件为md格式,则用UnstructuredMarkdownLoader加载文件,若判断文件为txt格式,则用UnstructuredFileLoader加载文件,得到无格式字符串,放入docs,返回该列表。
④ 目标文件夹,是刚刚下载的各个语料库。
⑤ 加载目标文件,得到所有语料库输出的无格式纯文本字符串形成的列表文件docs。
⑥ 对文本进行分块,使用LangChain提供的组件RecursiveCharacterTextSplitter,“chunk_size”指每一个块的大小,500,每500个字符会自行分为一块。"chunk_overlap"指两个相邻块中间的重叠程度,150,指第二个块的前150个字符和前一个块的后150个字符是相同的。得到分块之后的文本列表split_docs
⑦ 加载开源词向量模型,sentence-transformer的模型传入HuggingFaceEmbeddings类中,这个类是LangChain提供的词向量组件,embedd对于sentence-transformer模型的词向量对象。
⑧ 构建向量数据库,定义向量数据库的存储路径,data_base/vector_db/chroma
⑨ 加载数据库,用函数Chroma.from_documents加载,该函数有三个参数“documents”为分块之后的目标文件列表;“embedding”为词向量对象;“persist_directory”是指定义的持久化路径。
⑩ 将加载的向量数据库持久化到磁盘上)
3、运行该脚本。
cd demo
python create_db.py
运行一次,会出现data_base的文件夹,在该文件夹下存放了构建的向量数据库的文件。
三、InternLM接入到LangChain
为了便捷构建LLM应用,需要基于本地部署的InternLM,继承LangChain的LLM类自定义一个InrernLM LLM子类,从而实现将InternLM接入到LangChain框架中。所以,只需要从LangChain.llms.base.LLM类继承一个子类,并重写构造函数与_call函数即可。
在/root/data/demo目录下,新建一个python文件,LLM.py。
from langchain.llms.base import LLM
from typing import Any, List, Optional
from langchain.callbacks.manager import CallbackManagerForLLMRun
from transformers import AutoTokenizer, AutoModelForCausalLM
import torch
class InternLM_LLM(LLM):
# 基于本地 InternLM 自定义 LLM 类
tokenizer : AutoTokenizer = None
model: AutoModelForCausalLM = None
def __init__(self, model_path :str):
# model_path: InternLM 模型路径
# 从本地初始化模型
super().__init__()
print("正在从本地加载模型...")
self.tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True)
self.model = AutoModelForCausalLM.from_pretrained(model_path, trust_remote_code=True).to(torch.bfloat16).cuda()
self.model = self.model.eval()
print("完成本地模型的加载")
def _call(self, prompt : str, stop: Optional[List[str]] = None,
run_manager: Optional[CallbackManagerForLLMRun] = None,
**kwargs: Any):
# 重写调用函数
system_prompt = """You are an AI assistant whose name is InternLM (书生·浦语).
- InternLM (书生·浦语) is a conversational language model that is developed by Shanghai AI Laboratory (上海人工智能实验室). It is designed to be helpful, honest, and harmless.
- InternLM (书生·浦语) can understand and communicate fluently in the language chosen by the user such as English and 中文.
"""
messages = [(system_prompt, '')]
response, history = self.model.chat(self.tokenizer, prompt , history=messages)
return response
@property
def _llm_type(self) -> str:
return "InternLM"
① 导入第三方库;
② 定义InternLM这个类,这个类继承自LangChain的LLM,有两个属性,tokenizer和model;
③ 从构造函数_init_中,定义从本地中加载模型;首先定义父类构造函数,分别加载本地模型的tokenizer和model。
④ 重写了call函数(LLM调用的主要函数,通过调用call函数实现大模型的问答)self.model为刚加载的模型;.chat为模型问答的方法。其中传入的“self.tokenizer”对象、“prompt”用户的输入、“history”模型的历史功能。
⑤ 定义一个“_llm_type”的方法,自定义类的标识为“InternLM”。
四、构建检索问答链
将InternLM接入到LangChain框架之后,基于已经构建的向量数据库对象,和已经定义的InterLM自定义类,来构建一个检索问答链,通过检索问答链完成RAG的一系列检索问答任务。
在/root/data/demo目录下,新建一个python文件。
from langchain.vectorstores import Chroma
from langchain.embeddings.huggingface import HuggingFaceEmbeddings
import os
from LLM import InternLM_LLM
from langchain.prompts import PromptTemplate
from langchain.chains import RetrievalQA
def load_chain():
# 加载问答链
# 定义 Embeddings
embeddings = HuggingFaceEmbeddings(model_name="/root/data/model/sentence-transformer")
# 向量数据库持久化路径
persist_directory = 'data_base/vector_db/chroma'
# 加载数据库
vectordb = Chroma(
persist_directory=persist_directory, # 允许我们将persist_directory目录保存到磁盘上
embedding_function=embeddings
)
# 加载自定义 LLM
llm = InternLM_LLM(model_path = "/root/data/model/Shanghai_AI_Laboratory/internlm-chat-7b")
# 定义一个 Prompt Template
template = """使用以下上下文来回答最后的问题。如果你不知道答案,就说你不知道,不要试图编造答
案。尽量使答案简明扼要。总是在回答的最后说“谢谢你的提问!”。
{context}
问题: {question}
有用的回答:"""
QA_CHAIN_PROMPT = PromptTemplate(input_variables=["context","question"],template=template)
# 运行 chain
qa_chain = RetrievalQA.from_chain_type(llm,retriever=vectordb.as_retriever(),return_source_documents=True,chain_type_kwargs={"prompt":QA_CHAIN_PROMPT})
return qa_chain
① 导入第三方库;
② 定义load_chain函数,该函数完成加载问答链的整个功能。首先加载了词向量模型,传入的参数是本地词向量模型存储地址。然后加载已经构建的向量数据库对象,用“Chroma”的方式,传入的数据分别是,本地向量持久化的路径和embeddings的对象。
③ 加载自定义LLM模型,传入的参数是本地存放InternLM的模型地址。
④ 定义一个 Prompt Template。每一次检索之后得到的相关文档片段和问题,一起组合成一个chroma。“以下上下文”为{context},“问题”为{question}
⑤ PromptTemplate为LangChain的组件,将context、question作为参数列表传入input_variables,作为参数和template一起传入组件。
⑥ RetrievalQA为LangChain的组件,去实现检索问答链。调用的是from_chain_type这个函数,其参数是:llm模型对象、构建的向量对象(as_retriever:返回的检索对象)、设定每一次检索时把相关的文档片段返回出来,在chain_type_kwargs中给出刚定义的QA_CHAIN_PROMPT。
五、部署Web Demo
1、在刚才的python文件中,接着定义一个类,该类负责加载并存储检索问答链,并响应 Web 界面里调用检索问答链进行回答的动作。
class Model_center():
"""
存储检索问答链的对象
"""
def __init__(self):
# 构造函数,加载检索问答链
self.chain = load_chain()
def qa_chain_self_answer(self, question: str, chat_history: list = []):
"""
调用问答链进行回答
"""
if question == None or len(question) < 1:
return "", chat_history
try:
chat_history.append(
(question, self.chain({"query": question})["result"]))
# 将问答结果直接附加到问答历史中,Gradio 会将其展示出来
return "", chat_history
except Exception as e:
return e, chat_history
① Model_center为实现整个系统功能的核心的类;
② 构造函数,加载检索问答链;
③ qa_chain_self_answer,调用问答链进行回答;该操作响应web demo中点击“发送(enter)”动作;首先判断用户在文本框中输入的内容是否为空,若长度为0,则说明用户什么都没有输入,返回“”,空字符串即可;若问题是有效的,则调用 self.chain检索问答链,得到的“result”是检索问答链的返回结果。将其和问题一起加入chat_history中,Gradio 会将其展示出来。
2、只需按照 Gradio 的框架使用方法,实例化一个 Web 界面并将点击动作绑定到上述类的回答方法即可。
import gradio as gr
# 实例化核心功能对象
model_center = Model_center()
# 创建一个 Web 界面
block = gr.Blocks()
with block as demo:
with gr.Row(equal_height=True):
with gr.Column(scale=15):
# 展示的页面标题
gr.Markdown("""<h1><center>InternLM</center></h1>
<center>书生浦语</center>
""")
with gr.Row():
with gr.Column(scale=4):
# 创建一个聊天机器人对象
chatbot = gr.Chatbot(height=450, show_copy_button=True)
# 创建一个文本框组件,用于输入 prompt。
msg = gr.Textbox(label="Prompt/问题")
with gr.Row():
# 创建提交按钮。
db_wo_his_btn = gr.Button("Chat")
with gr.Row():
# 创建一个清除按钮,用于清除聊天机器人组件的内容。
clear = gr.ClearButton(
components=[chatbot], value="Clear console")
# 设置按钮的点击事件。当点击时,调用上面定义的 qa_chain_self_answer 函数,并传入用户的消息和聊天历史记录,然后更新文本框和聊天机器人组件。
db_wo_his_btn.click(model_center.qa_chain_self_answer, inputs=[
msg, chatbot], outputs=[msg, chatbot])
gr.Markdown("""提醒:<br>
1. 初始化数据库时间可能较长,请耐心等待。
2. 使用中如果出现异常,将会在文本输入框进行展示,请不要惊慌。 <br>
""")
gr.close_all()
# 直接启动
demo.launch()
① 实例化核心功能对象model_center;
② 通过Gradio 的框架,创建一个Web界面 Block;书写其标题 Markdown;创建一个聊天机器人对象 Chatbot;创建一个文本框组件,用于输入prompt:Textbox;创建提交按钮:Button;设置按钮的点击事件,当点击时,调用上面定义的 qa_chain_self_answer 函数,并传入用户的消息和聊天历史记录,然后更新文本框和聊天机器人组件。
3、运行web_demo
python web_demo.py
六、参考《大模型实战营课程笔记 ②》的“五、web demo运行”
注意:端口号发生变化,在windows powershell需要输入的是:
7860:127.0.0.1:7860 root@ssh.intern-ai.org.cn -p *****
同时,需要用网页打开网址,http://127.0.0.1:7860。
若没有这么操作,会出现:
正常情况,应该如下: