大模型实战营课程笔记 ③

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。

若没有这么操作,会出现:

正常情况,应该如下:

 

  • 24
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值