当前位置: 首页 > news >正文

大模型应用:如何使用Langchain+Qwen部署一套Rag检索系统

一、TL;DR 

  1. 从0-1使用qwen chat model+ langchain的链式架构搭建一套rag系统
  2. 详细介绍了Langchain的工具链的调用流程
  3. 简单介绍了可能会出现什么问题

二、方法

参考开源链接:https://github.com/Aphasia0515/self_llm/

2.1 硬件和软件依赖

 类型需求备注
硬件      
  1. 显存 >= 24B

实测:

  1. Qwen7B >= 24B
  2. Qwen32B >= 60B 左右
  3. Qwen1B 大概在6B左右(记忆里)
软件
  1. ubuntu20.04
  2. cuda11.8
  3. py3.8
  1. 其实现在大多数的社区镜像基本满足要求,因为Qwen的镜像都是比较新的依赖
  2. 但是训练镜像尤其要注意,我之前就遇到了Qwen和InternVL的训练镜像有冲突的问题

2.2 模型下载和准备

注意:模型下载记得从mirror-huggingface上下载,国内打不开huggingface

模型作用 备注
QwenRag系统的生成器、chat模型
  1. 注意根据自己的显存选择合适的模型
  2. 注意:1B/4B/7B的差异还是比较明显的(从天梯图里可以看到)
 Sentence Transformer 对文本进行向量化

2.3 Rag的文本库准备

这一节就不仔细讲了,将所有你需要的参考文件知识库放在某个指定目录下,本文只做txt和markdown的文档拆分(用于后续的建库)

三、知识库提取和向量化

3.1 得到所有文档的纯本文内容

先遍历2.3节的指定目录,得到所有的markdown和txt文件,并存成list:

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

接下来对list里面的内容进行读取和加载,此处使用LangChain提供的FileLoader进行加载

  1. 注意:此处得到的其实还是纯文本loader,而非真正的文本块
from tqdm import tqdm
from langchain.document_loaders import UnstructuredFileLoader
from langchain.document_loaders import UnstructuredMarkdownLoaderdef 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:# 如果是不符合条件的文件,直接跳过continuedocs.extend(loader.load())return docs

3.2 对所有的文本进行分块并向量化

LangChain使用多种文本分块工具,示例代码使用的时字符串递归分割器,并选择分块大小时500,块重叠长度是150,分块代码如下所示:

注意:我上一篇博客所说的,分块的大小不同的模型有不同的选择,只有最合适的,没有固定的

from langchain.text_splitter import RecursiveCharacterTextSplittertext_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=150)
split_docs = text_splitter.split_documents(docs)

分块完后,使用2.2节的下载好的模型(sentence Transformer)此时进一步进行向量化,LangChain 提供了直接引入 HuggingFace 开源社区中的模型进行向量化的接口::

注意:这个是直接加载本地路径

from langchain.embeddings.huggingface import HuggingFaceEmbeddingsembeddings = HuggingFaceEmbeddings(model_name="/root/autodl-tmp/embedding_model")

加载玩模型后,使用 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-Chat Model

注意:本步骤接入任何llm模型其实都是可以的,主要还是看这个检索框架的整体实现。

4.1 QwenLM接入LangChain

本地部署Qwen llm模型,然后将QwenLM接入到Langchain框架里面,完成自定义 LLM 类之后,可以以完全一致的方式调用 LangChain 的接口,而无需考虑底层模型调用的不一致。

  1. 从 LangChain.llms.base.LLM 类继承一个子类,并重写构造函数与 _call 函数
from langchain.llms.base import LLM
from typing import Any, List, Optional
from langchain.callbacks.manager import CallbackManagerForLLMRun
from transformers import AutoTokenizer, AutoModelForCausalLM, GenerationConfigclass QwenLM(LLM):# 基于本地 Qwen 自定义 LLM 类tokenizer : AutoTokenizer = Nonemodel: AutoModelForCausalLM = Nonedef __init__(self, model_path :str):# model_path: Qwen 模型路径# 从本地初始化模型super().__init__()print("正在从本地加载模型...")model_dir = '/root/autodl-tmp/qwen/Qwen-7B-Chat'self.tokenizer = AutoTokenizer.from_pretrained(model_dir, trust_remote_code=True)self.model = AutoModelForCausalLM.from_pretrained(model_dir, device_map="auto", trust_remote_code=True).eval()# Specify hyperparameters for generationself.model.generation_config = GenerationConfig.from_pretrained(model_dir, trust_remote_code=True) # 可指定不同的生成长度、top_p等相关超参print("完成本地模型的加载")def _call(self, prompt : str, stop: Optional[List[str]] = None,run_manager: Optional[CallbackManagerForLLMRun] = None,**kwargs: Any):# 重写调用函数response, history = self.model.chat(self.tokenizer, prompt , history=[])return response@propertydef _llm_type(self) -> str:return "QwenLM"

上述代码在构造函数里直接加载了qwen模型,如果有其他的llm model也可以一并加载进去, _call 函数是 LLM 类的核心函数,LangChain 会调用该函数来调用 LLM来使用qwen的chat能力。

开源仓库里面将上述代码封装为 LLM.py,后续将直接从该文件中引入自定义的 LLM 类,直接使用from LLM import QwenLM。

五、构建检索问答链

LangChain 通过提供检索问答链对象来实现对于 RAG 全流程的封装

  1. 我们可以调用一个 LangChain 提供的 RetrievalQA 对象,通过初始化时填入已构建的数据库和自定义 LLM 作为参数,来简便地完成检索增强问答的全流程,LangChain 会自动完成基于用户提问进行检索、获取相关文档、拼接为合适的 Prompt 并交给 LLM 问答的全部流程。

5.1 导入向量数据库

from langchain.vectorstores import Chroma
from langchain.embeddings.huggingface import HuggingFaceEmbeddings
import os# 定义 Embeddings
embeddings = HuggingFaceEmbeddings(model_name="/root/autodl-tmp/embedding_model")# 向量数据库持久化路径
persist_directory = 'data_base/vector_db/chroma'# 加载数据库
vectordb = Chroma(persist_directory=persist_directory, embedding_function=embeddings
)

注意:向量数据库里面存储的是分块的文档和对应的向量,是embedding和index的关系

5.2 实例化自定义的QwenLM对象

from LLM import QwenLM
llm = QwenLM(model_path = "/root/autodl-tmp/qwen")
llm.predict("你是谁")

5.3 构建Prompt Template

prompt Template的作用是通过将用户input、rag检索得到的知识片段组合成input:

  1. 是一个带变量的字符串
  2. 在检索之后,LangChain 会将检索到的相关文档片段填入到 Template 的变量中,从而实现带知识的 Prompt 构建。
from langchain.prompts import PromptTemplate# 我们所构造的 Prompt 模板
template = """使用以下上下文来回答最后的问题。如果你不知道答案,就说你不知道,不要试图编造答案。尽量使答案简明扼要。总是在回答的最后说“谢谢你的提问!”。
{context}
问题: {question}
有用的回答:"""# 调用 LangChain 的方法来实例化一个 Template 对象,该对象包含了 context 和 question 两个变量,在实际调用时,这两个变量会被检索到的文档片段和用户提问填充
QA_CHAIN_PROMPT = PromptTemplate(input_variables=["context","question"],template=template)

5.4 直接检索问答

最后,可以调用 LangChain 提供的检索问答链构造函数,基于我们的自定义 LLM、Prompt Template 和向量知识库来构建一个基于 Qwen 的检索问答链:

question = "什么是QwenLM"
result = qa_chain({"query": question})
print("检索问答链回答 question 的结果:")
print(result["result"])# 仅 LLM 回答效果
result_2 = llm(question)
print("大模型回答 question 的结果:")
print(result_2)

可以看到,使用检索问答链生成的答案更接近知识库里的内容。

六、部署WebDemo

七、可优化的点

上述用的是开源代码,如果实际工程中可以从哪里优化呢?

  1. 检索的文本是大文本或者超大文本(>2K文本):
    1. summary,对大文本进行 map-reduce summary
    2. k-means(BRV steps)
  2. 检索内容太多不准该怎么办?
  3. 等等 明天再写把
http://www.lryc.cn/news/573738.html

相关文章:

  • 【教程】不同架构(armv7l等)下载Miniconda安装包
  • RA4M2开发IOT(11)----ADC检测电压
  • 如何用AI开发完整的小程序<10>—总结
  • webRTC源码配置和编译 + Vscode Intelligence配置
  • 9大策略深度解析MySQL多表JOIN性能优化
  • Python-break、continue与else语句
  • 实战记录:minapp框架下跨机型接口调用顺序引发的兼容性问题
  • 如何仅用AI开发完整的小程序<6>—让AI对视觉效果进行升级
  • AAudio:Android 低延迟音频处理的核心组件
  • WEB3开启 Hardhat 自动验证有什么意义
  • 【设计模式】策略模式 在java中的应用
  • 排序算法-python实现
  • docker私有仓库部署配置学习
  • 深度解析云计算网络架构:VLAN+OVS+Bonding构建高可靠虚拟化平台
  • LINUX 622 SAMBA
  • Macbook M4芯片 MUMU模拟器安装使用burpsuit抓包教程APP
  • SpringCloudGateway(spel)漏洞复现 Spring + Swagger 接口泄露问题
  • 【DataWhale组队学习】AI办公实践与应用
  • 探索尝试-ai编程-01-使用ai编程处理单文件的特定文本内容筛选
  • 核心概念解析:AI、数据挖掘、机器学习与深度学习的关系
  • 从零理解鱼眼相机的标定与矫正(含 OpenCV 代码与原理讲解)
  • mp.set_start_method(“spawn“)
  • 可理解性输入:洗澡习惯
  • 时序数据库IoTDB的架构、安装启动方法与数据模式总结
  • Linux 服务器运维:磁盘管理与网络配置
  • 【HarmonyOS Next之旅】DevEco Studio使用指南(三十六) -> 配置构建(三)
  • 面试150 加油站
  • 7.4.1_1B树
  • 如何仅用AI开发完整的小程序<5>—让AI制作开始页面
  • 如何用AI开发完整的小程序<8>—让AI制作具体功能