当前位置: 首页 > 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. 等等 明天再写把

相关文章:

  • 【机器学习四大核心任务类型详解】分类、回归、聚类、降维都是什么?
  • OpenGL ES 中的材质
  • 分布式ID生成方式及优缺点详解
  • [特殊字符] AIGC工具深度实战:GPT与通义灵码如何彻底重构企业开发流程
  • 电脑商城--购物车
  • Camera Sensor接口协议全解析(三):移动霸主——MIPI CSI-2架构拆解
  • 【数据结构】_二叉树部分特征统计
  • rom定制系列------红米note11 5G版 MTK芯片强解bl锁修复bug 官方系统 面具root批量线刷版
  • React 新钩子useImperativeHandle
  • 华为OD机考-素数伴侣-逻辑分析(JAVA 2025B卷)
  • AWS 使用图形化界面创建 EKS 集群(零基础教程)
  • jenkins对接、jenkins-rest
  • 单例模式-Python示例
  • 如何仅用AI开发完整的小程序<4>—小程序页面创建与删除
  • 【Linux】进程间多种通信方式对比
  • Flink Sink函数深度解析:从原理到实践的全流程探索
  • vscode+react+ESLint解决不引入组件,vscode不会报错的问题
  • 【设计模式】策略模式 在java中的应用
  • 魂斗罗ost 游戏全合集8GB
  • 应急推进器和辅助推进器诊断函数封装
  • 如何做企业套模网站/百度网盘电脑版下载
  • 想做一个自己设计公司的网站怎么做的/互联网
  • 网站设计包括/免费推广的网站平台
  • 网站建设包括什么/西安百度推广代运营
  • 新乐网站建设/百度seo关键词排名优化软件
  • 网站开发与设计 信科/优化网站关键词排名软件