基于 InternLM 和 Langchain 搭建你的知识库

理论

1 大模型开发范式

LLM 的局限性

  • 知识时效性受限:如何让LLM能够获取最新的知识
  • 专业能力有限:如何打造垂域大模型
  • 定制化成本高:如何打造个人专属的LLM应用

RAG(检索、增强生成)

核心思想:大模型外挂一个知识库(对于用户的提问会首先从知识库中匹配到提问对应回答的相关文档,然后将文档和提问一起交给大模型来生成回答,从而提高大模型的知识储备。)

  • 低成本
  • 可实时更新
  • 受基座模型影响大
  • 单次回答知识有限

RAG其核心优势在于成本低且可实时更新。RAG范式的应用无需对大模型进行重新训练,不需要GPU算力。对于新的知识只需组织加入到外挂知识库中即可。加入新知识成本极低,可以实时更新,但其能力受基座模型影响大。基座模型的能力上限极大程度决定了RAG应用的能力天花板。

Finetune(延承传统自然语言处理算法的微调)

核心思想:在一个新的较小的训练集上进行轻量级的训练微调,从而提升模型在这个新数据集上的能力。

  • 可个性化微调
  • 知识覆盖面广
  • 成本高昂
  • 无法实时更新

核心优势在于可个性化微调,且知识覆盖面广。应用将在个性化数据上微调

2 LangChain

LangChain 框架是一个开源工具,通过为各种LLM 提供通用接口来简化应用程序的开发流程,帮助开发者自由构建 LLM应用。

LangChain 的核心组成模块:

  • 链(Chains):将组件组合实现端到端应用,通过一个对象封装实现一系列LLM 操作
  • Eg.检索问答链,覆盖实现了RAG(检索增强生成)的全部流程

3 构建向量数据库

向量数据库是一种特殊的数据库,专门用于存储和管理向量数据。与传统的关系型数据库或文档数据库不同,向量数据库的主要目标是高效地处理和查询向量数据,以支持各种复杂的相似度搜索和机器学习任务。

向量数据库的设计和优化主要基于以下几个核心原则:

  1. 向量索引:向量数据库会使用特殊的索引结构来加速向量的相似度搜索。常见的索引结构包括k-d 树、LSH(局部敏感哈希)、球树等。这些索引结构可以在高维空间中快速定位与查询向量相似的数据点。

  2. 向量存储:向量数据库通常会采用高效的向量压缩和存储技术,以节省存储空间并提高查询性能。例如,可以使用量化(Quantization)方法将高维向量映射到低维度的离散码表示,从而减少存储需求。

  3. 高效查询:向量数据库会提供各种高效的查询算法和接口,以支持精确的相似度搜索和近邻查询。一些数据库还支持范围搜索、多条件组合查询等功能,以满足不同应用场景的需求。

  4. 扩展性和分布式计算:随着数据规模的增长,向量数据库需要具备良好的扩展性和分布式计算能力。一些向量数据库可以在多节点上进行数据分区和并行计算,以提高整体的查询性能和吞吐量。

  5. 支持多种数据类型:除了标准的向量数据,一些向量数据库还支持处理其他类型的数据,如文本、图像、音频等。这样可以在数据库内部进行特征提取和表示学习,从而更好地支持复杂的数据分析和机器学习任务。

向量数据库在许多领域都有广泛应用,包括相似图片搜索、推荐系统、聚类和分类任务、嵌入式表示学习等。它们可以提供高效的查询性能和灵活的数据处理能力,帮助用户快速发现和分析大规模的向量数据。

首先需要基于个人数据构建向量数据库。向量数据库的构建主要有加载原文档、文档分块和文档向量化三个部分。由于我们的个人数据可能有多种数据类型,例如TXT、markdown或者PDF等。我们首先需要确定原文档的类型,针对不同的类型的源文件选用不同的加载器。这些加载器的核心功能都是带格式的文本转化为无格式的字符串。我们后续构造向量数据库的操作中,输入都是无格式的纯文本。

然后由于大模型的输入上下文往往都是有限的,单个文档的长度往往会超过模型上下文的上限。我们需要对加载的文本进行切分。

最后为了使用向量数据库来支持语义检索。

4 搭建知识库助手

将 InternLM 接入 LangChain

  • LangChain 支持自定义LLM,可以直接接入到框架中
  • 我们只需将 InternLM 部署在本地,并封装一个自定义 LLM类,调用本地 InternLM 即可

构建检索问答链

  • LangChain 提供了检索问答链模版,可以自动实现知识检索、Prompt 嵌入、LLM问答的全部流程
  • 将基于InternLM的自定义LLM 和已构建的向量数据库接入到检索问答链的上游
  • 调用检索问答链,即可实现知识库助手的核心功能

RAG方案优化建议

  • 基于RAG的问答系统性能核心受限于:
  • 检索精度

o Prompt性能

一些可能的优化点:

检索方面:

  • 基于语义进行分割,保证每一个chunk的语义完整
  • 给每一个chunk生成概括性索引,检索时匹配索引

Prompt方面:

  • 迭代优化Prompt策略

实战

1 环境配置

1.1 InternLM 模型部署

在 InternStudio 平台中选择 A100(1/4) 的配置

进入 conda 环境之后,使用以下命令从本地一个已有的 pytorch 2.0.1 的环境,激活环境,在环境中安装运行 demo 所需要的依赖。

bash
conda create --name InternLM --clone=/root/share/conda_envs/internlm-base

conda activate InternLM

# 升级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

法2:

import torch
from modelscope import snapshot_download, AutoModel, AutoTokenizer
import os
model_dir = snapshot_download('Shanghai_AI_Laboratory/internlm-chat-7b', cache_dir='/root/data/model', revision='v1.0.3')

1.3 LangChain 相关环境配置

在已完成 InternLM 的部署基础上,还需要安装以下依赖包:

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

需要使用到开源词向量模型 Sentence Transformer:(我们也可以选用别的开源词向量模型来进行 Embedding,目前选用这个模型是相对轻量、支持中文且效果较好的,同学们可以自由尝试别的开源词向量模型)

使用 huggingface 官方提供的 huggingface-cli 命令行工具。安装依赖:

pip install -U huggingface_hub

然后在和 /root/data 目录下新建python文件 download_hf.py,填入以下代码:

  • resume-download:断点续下
  • local-dir:本地存储路径。(linux环境下需要填写绝对路径)
import os

# 下载模型
os.system('huggingface-cli download --resume-download sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2 --local-dir /root/data/model/sentence-transformer')

镜像版

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')

在 \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.5 下载本项目代码

cd /root/data
git clone https://github.com/InternLM/tutorial

2 知识库搭建

2.1 数据收集

我们选择由上海人工智能实验室开源的一系列大模型工具开源仓库作为语料库来源,包括:

  • OpenCompass:面向大模型评测的一站式平台
  • IMDeploy:涵盖了 LLM 任务的全套轻量化、部署和服务解决方案的高效推理工具箱
  • XTuner:轻量级微调大语言模型的工具库
  • InternLM-XComposer:浦语·灵笔,基于书生·浦语大语言模型研发的视觉-语言大模型
  • Lagent:一个轻量级、开源的基于大语言模型的智能体(agent)框架
  • InternLM:一个开源的轻量级训练框架,旨在支持大模型训练而无需大量的依赖

首先我们需要将上述远程开源仓库 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
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

2.2 加载数据

from tqdm import tqdm
from langchain.document_loaders import UnstructuredFileLoader
from langchain.document_loaders import UnstructuredMarkdownLoader

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

2.3 构建向量数据库

LangChain 提供了多种文本分块工具,此处我们使用字符串递归分割器,并选择分块大小为 500,块重叠长度为 150

from langchain.text_splitter import RecursiveCharacterTextSplitter

text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=500, chunk_overlap=150)
split_docs = text_splitter.split_documents(docs)

文本向量化

from langchain.embeddings.huggingface import HuggingFaceEmbeddings

embeddings = HuggingFaceEmbeddings(model_name="/root/data/model/sentence-transformer")

选择 Chroma 作为向量数据库,基于上文分块后的文档以及加载的开源向量化模型,将语料加载到指定路径下的向量数据库:

from langchain.vectorstores import Chroma

# 定义持久化路径
persist_directory = 'data_base/vector_db/chroma'
# 加载数据库
vectordb = Chroma.from_documents(
    documents=split_docs,
    embedding=embeddings,
    persist_directory=persist_directory  # 允许我们将persist_directory目录保存到磁盘上
)
# 将加载的向量数据库持久化到磁盘上
vectordb.persist()

2.4 整体脚本

# 首先导入所需第三方库
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()

3 InternLM 接入 LangChain

为便捷构建 LLM 应用,我们需要基于本地部署的 InternLM,继承 LangChain 的 LLM 类自定义一个 InternLM LLM 子类,从而实现将 InternLM 接入到 LangChain 框架中。完成 LangChain 的自定义 LLM 子类之后,可以以完全一致的方式调用 LangChain 的接口,而无需考虑底层模型调用的不一致。

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"

4 构建检索问答链

LangChain 通过提供检索问答链对象来实现对于 RAG 全流程的封装。所谓检索问答链,即通过一个对象完成检索增强问答(即RAG)的全流程

4.1 加载向量数据库

from langchain.vectorstores import Chroma
from langchain.embeddings.huggingface import HuggingFaceEmbeddings
import os

# 定义 Embeddings
embeddings = HuggingFaceEmbeddings(model_name="/root/data/model/sentence-transformer")

# 向量数据库持久化路径
persist_directory = 'data_base/vector_db/chroma'

# 加载数据库
vectordb = Chroma(
    persist_directory=persist_directory, 
    embedding_function=embeddings
)

4.2 实例化自定义 LLM 与 Prompt Template

from LLM import InternLM_LLM
llm = InternLM_LLM(model_path = "/root/data/model/Shanghai_AI_Laboratory/internlm-chat-7b")
llm.predict("你是谁")
from langchain.prompts import PromptTemplate

# 我们所构造的 Prompt 模板
template = """使用以下上下文来回答用户的问题。如果你不知道答案,就说你不知道。总是使用中文回答。
问题: {question}
可参考的上下文:
···
{context}
···
如果给定的上下文无法让你做出回答,请回答你不知道。
有用的回答:"""

# 调用 LangChain 的方法来实例化一个 Template 对象,该对象包含了 context 和 question 两个变量,在实际调用时,这两个变量会被检索到的文档片段和用户提问填充
QA_CHAIN_PROMPT = PromptTemplate(input_variables=["context","question"],template=template)

4.3 构建检索问答链

from langchain.chains import RetrievalQA

qa_chain = RetrievalQA.from_chain_type(llm,retriever=vectordb.as_retriever(),return_source_documents=True,chain_type_kwargs={"prompt":QA_CHAIN_PROMPT})
# 检索问答链回答效果
question = "什么是InternLM"
result = qa_chain({"query": question})
print("检索问答链回答 question 的结果:")
print(result["result"])

# 仅 LLM 回答效果
result_2 = llm(question)
print("大模型回答 question 的结果:")
print(result_2)

5 部署 Web Demo


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
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()

启动速度需要等待,提前配置好SSH

​​​​​​​

  • 24
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值