大模型具有简单的广度回答,但是在垂直领域的知识受限;
两种常用开发范式:RAG VS Finetune
即:检索增强生成 VS 算法微调;
RAG:
Finetune:
LangChain框架:是一个开源工具,为各种LLM提供通用接口来简化应用程序的开发流程,帮助开发者自由构建LLM应用;
LangChain的核心组成模块:
支持简易Web 部署的框架:Gradio、Streamlit等;
1.安装环境,激活环境,并安装依赖
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')
3.LangChain相关环境部署
4.下载NLTK相关资源
在使用开源词向量模型构建开源词向量的时候,需要用到第三方库 nltk 的一些资源。
开源使用方式
# 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
为语料处理方案,在这里只选用仓库中的MD、TXT文件作为示例语料库。
当然也可以选用其中的代码文件放入到知识库中,但是需要针对代码文件格式进行专门的额外处理。(代码文件对逻辑联系要求较高,且规范性较强,在分割时最好基于代码模块进行分割再加入向量数据库)。
创建函数:将仓库里所以满足条件的.md或.txt文件 的路径找出来,定义一个函数:将该函数递归指定文件路径,返回其中所有满足条件的文件路径;
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
在上一步将所有目标文件的路径找出来之后,使用LangChain提供的FileLoader对象来加载目标文件,得到由目标文件解析出的纯文本内容。
不同类型文件对应不同的FileLoader:首先判断目标文件类型,并针对性调用对应类型的FileLoader,即调用FileLoader对象的load方法来得到加载之后的纯文本对象。
Python 真是好东西,实现简洁且可读性超级强
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
在上一步得到纯文本对象的列表之后,将它引入到LangChain框架中构建向量数据库。由纯文本对象构建向量数据库,先对文本进行分块,接着对文本进行向量化。
1.#LangChain有多种文本分块工具,在这里使用字符串递归分割器
from langchain.text_splitter import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500, chunk_overlap=150)
split_docs = text_splitter.split_documents(docs)
2.选用开源词向量模型进行文本向量化
from langchain.embeddings.huggingface import HuggingFaceEmbeddings
embeddings = HuggingFaceEmbeddings(model_name="/root/data/model/sentence-transformer")
3.将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()
为方便构建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"
在上述类定义中,我们分别重写了构造函数和 _call 函数:对于构造函数,我们在对象实例化的一开始加载本地部署的 InternLM 模型,从而避免每一次调用都需要重新加载模型带来的时间过长;_call 函数是 LLM 类的核心函数,LangChain 会调用该函数来调用 LLM,在该函数中,我们调用已实例化模型的 chat 方法,从而实现对模型的调用并返回调用结果。
在Python中看到了类似C++中的用法
LangChain 通过提供检索问答链对象来实现对于 RAG 全流程的封装。所谓检索问答链,即通过一个对象完成检索增强问答(即RAG)的全流程。
原理:调用一个 LangChain 提供的 RetrievalQA 对象,通过初始化时填入已构建的数据库和自定义 LLM 作为参数,来简便地完成检索增强问答的全流程,LangChain 会自动完成基于用户提问进行检索、获取相关文档、拼接为合适的 Prompt 并交给 LLM 问答的全部流程。
将上文构建的向量数据库导入进来,直接通过Chroma以及上文定义的词向量模型来加载已构建的数据库:
vedtordb对象即为已经构建好的向量数据库对象,它可以针对用户的query进行语义向量检索,得到与用户提问相关的知识;
实例化一个基于InternLM自定义的LLM对象:
调用 LangChain 提供的检索问答链构造函数,基于我们的自定义 LLM、Prompt Template 和向量知识库来构建一个基于 InternLM 的检索问答链:
上面两个小节分别完成之后,搭建基于Gradio框架部署到网页,搭建一个小型的Demo,这样便于测试和调试;
将上面的代码封装成一个函数:用于返回 构建的检索问答链对象的函数。在启动Gradio的initial时调用函数得到检索问答链对象,后续直接使用该对象进行问答对话,避免重复加载模型;
定义一个类:负责加载并存储检索问答链,响应Web界面里调用该检索问答链进行回答的动作;
按照Gradio框架使用方法,实例化一个Web界面并将点击动作绑定到上述类的回答方法即可。
启动上面封装的脚本,默认会在7860端口运行,因此做好服务器端口与本地端口映射。
ssh -L 7860:127.0.0.1:7860 [email protected] -p 34825