LLM实战系列(1)—强强联合Langchain-Vicuna应用实战

news2024/11/25 11:06:08

背景

本文主要介绍一下,基于Langchain与Vicuna-13B的外挂OceanBase知识库项目实战以及QA使用,项目地址:

github.com/csunny/DB-G…

在开始之前,我们还是先看看效果~

在这里插入图片描述
在这里插入图片描述

自Meta发布LLaMA大模型以来, 围绕LLaMA微调的模型也是层出不穷。 从alpaca 到 vicuna,再到刚刚发布的中医垂直领域微调大模型华佗GPT, 可谓是风光无限。 但其中最出名、效果最好的当属vicuna-13B。如下图所示,当前在众多大模型当中,Vicuna-13B的效果非常接近ChatGPT,有其92%的效果。 这意味着什么呢? 意味着,我们基于开源的Vicuna-13B即可搞定决大多数的任务与需求。 当然什么外挂知识库QA这样的简单需求自然不在话下。

那Langchain又是什么呢?

毫无疑问,Langchain是目前大语言模型领域最炙手可热的LLM框架。

LangChain 是一个构建在LLM之上的应用开发框架。想让应用变得更强大,更加不同,单单通过调用大模型的API肯定是不够的, 还需要有以下特性:

  1. 数据思维: 连接大模型到其他的元数据上。
  2. 代理思维: 语言模型可以与环境交互。

以上就是Langchain的设计理念, It’s very simple, but enough nature. 是的,足够简单,但很贴近本质。我们也是被Langchain的理念深深的吸引。 所以,我们来了~

方案

既然是一个实战项目,那么在项目开始之前,我们有必要对项目整体的架构做一个清晰的梳理。

添加图片注释,不超过 140 字(可选)

如图所示,是我们整体的架构图。 从图中我们可以看到,左侧有一条线是知识库 -> Embedding -> 向量存储 -> 大模型(Vicuna-13B) -> Generate 的路径。 在我们本文中,就是依赖此路径外挂知识库进行推理、总结,以完成QA的工作。

所以我们整体将以上过程拆分为如下所示的四个步骤。

  1. 知识库准备: 如同所示中,因为我们是面向DB领域的GPT,所以我们准备了主流数据库的文档,并进行了分类。

  2. Embedding: embedding这一步是需要将文本转换成向量进行存储,当然了,存储媒介是向量数据库,关于向量数据库的了解,大家可以从这里了解向量数据库

  3. Embedding之后的知识,会存储在向量数据库当中,用于后面的检索。

  4. 利用大模型的能力,通过ICL(In-Context-Learning) 让大模型实现基于现有知识的推理、总结。

  5. 这样我们就可以实现一个基于现有知识库QA的项目了。

整个知识库的处理过程,也可以参考Langchain-ChatGLM项目中的一张图。

图片来自Langchain-ChatGLM

代码说明

既然是实战,那肯定少不了代码,毕竟我们一贯坚持的是:

Talk is cheap, show me the code.

模型加载

目前基本主流的模型都是基于HuggingFace的标准,所以模型加载代码其实就变得很简单了。 如下所示,为模型加载类,所需要的参数只需要传一个model_path, 在这个类当中,我们实现了一个方法,loader方法,通过这个方法我们可以获得两个对象。 1. tokenizer 2. model, 根据这两个对象,我们就得到一个模型了,后面的事情,关注使用就可以啦。

class ModelLoader:
    """Model loader is a class for model load
    
      Args: model_path
     
    """

    kwargs = {}

    def __init__(self, 
                 model_path) -> None:

                     self.device = "cuda" if torch.cuda.is_available() else "cpu"
                     self.model_path = model_path 
                     self.kwargs = {
                         "torch_dtype": torch.float16,
                         "device_map": "auto",
                     }

    def loader(self, num_gpus, load_8bit=False, debug=False):
        if self.device == "cpu":
            kwargs = {}
        elif self.device == "cuda":
            kwargs = {"torch_dtype": torch.float16}
            if num_gpus == "auto":
                kwargs["device_map"] = "auto"
            else:
                num_gpus = int(num_gpus)
                if num_gpus != 1:
                    kwargs.update({
                        "device_map": "auto",
                        "max_memory": {i: "13GiB" for i in range(num_gpus)},
                    })
        else:
            raise ValueError(f"Invalid device: {self.device}")

        if "chatglm" in self.model_path:
            tokenizer = AutoTokenizer.from_pretrained(self.model_path, trust_remote_code=True)
            model = AutoModel.from_pretrained(self.model_path, trust_remote_code=True).half().cuda()
        else:
            tokenizer = AutoTokenizer.from_pretrained(self.model_path, use_fast=False)
            model = AutoModelForCausalLM.from_pretrained(self.model_path,
                                                         low_cpu_mem_usage=True, **kwargs)

        if load_8bit:
            compress_module(model, self.device)

        if (self.device == "cuda" and num_gpus == 1):
            model.to(self.device)

        if debug:
            print(model)

        return model, tokenizer

知识库准备

准备知识库,没什么特别需要讲的,可以是pdf、txt、md等等的吧。 在这里,我们准备的是一个md文档,知识库是基于开源的OceanBase官方文档。 预备好的知识库地址: OceanBase文档。

注:这里特别说明一下,为什么没有直接下载pdf。 两个原因 1. OB pdf文档有很多的格式,这些格式在向量处理的过程中也会保存下来, 默认处理后的知识没有压扁平,不利于后续的大模型使用。 2. pdf文档相对比较大,在本地跑,通过模型抽向量的过程会比较长,因此我们准备了一个简单的MarkDown文件来做演示。

知识转向量并存储到向量数据库

这里我们实现了一个Knownledge2Vector的类。这个类顾命思意,就是把知识库转换为向量。 当然我们转换成向量之后会持久化到数据库存储。 (问题1: 类名没有体现存数据库,是不是应该在斟酌一下?KnownLedge2VectorStore会更好? 🤔)

class KnownLedge2Vector:

    """KnownLedge2Vector class is order to load document to vector 
    and persist to vector store.
        
        Args: 
           - model_name

        Usage:
            k2v = KnownLedge2Vector()
            persist_dir = os.path.join(VECTORE_PATH, ".vectordb") 
            print(persist_dir)
            for s, dc in k2v.query("what is oceanbase?"):
                print(s, dc.page_content, dc.metadata)

    """
    embeddings: object = None 
    model_name = LLM_MODEL_CONFIG["sentence-transforms"]
    top_k: int = VECTOR_SEARCH_TOP_K

    def __init__(self, model_name=None) -> None:
        if not model_name:
            # use default embedding model
            self.embeddings = HuggingFaceEmbeddings(model_name=self.model_name) 
        
    def init_vector_store(self):
        persist_dir = os.path.join(VECTORE_PATH, ".vectordb")
        print("向量数据库持久化地址: ", persist_dir)
        if os.path.exists(persist_dir):
            # 从本地持久化文件中Load
            print("从本地向量加载数据...")
            vector_store = Chroma(persist_directory=persist_dir, embedding_function=self.embeddings)
            # vector_store.add_documents(documents=documents)
        else:
            documents = self.load_knownlege()
            # 重新初始化
            vector_store = Chroma.from_documents(documents=documents, 
                                                 embedding=self.embeddings,
                                                 persist_directory=persist_dir)
            vector_store.persist()
        return vector_store 

    def load_knownlege(self):
        docments = []
        for root, _, files in os.walk(DATASETS_DIR, topdown=False):
            for file in files:
                filename = os.path.join(root, file)
                docs = self._load_file(filename)
                # 更新metadata数据
                new_docs = [] 
                for doc in docs:
                    doc.metadata = {"source": doc.metadata["source"].replace(DATASETS_DIR, "")} 
                    print("文档2向量初始化中, 请稍等...", doc.metadata)
                    new_docs.append(doc)
                docments += new_docs
        return docments

    def _load_file(self, filename):
        # 加载文件
        if filename.lower().endswith(".pdf"):
            loader = UnstructuredFileLoader(filename) 
            text_splitor = CharacterTextSplitter()
            docs = loader.load_and_split(text_splitor)
        else:
            loader = UnstructuredFileLoader(filename, mode="elements")
            text_splitor = CharacterTextSplitter()
            docs = loader.load_and_split(text_splitor)
        return docs

    def _load_from_url(self, url):
        """Load data from url address"""
        pass

    
    def query(self, q):
        """Query similar doc from Vector """
        vector_store = self.init_vector_store()
        docs = vector_store.similarity_search_with_score(q, k=self.top_k)
        for doc in docs:
            dc, s = doc
            yield s, dc

这个类的使用也非常简单, 首先实例化,参数也是只有一个model_name, 需要注意的是,这里的model_name 是转向量的模型,跟我们前面的大模型不是同一个,当然这里能不能是同一个,当然也是可以的。(问题2: 可以思考一下,这里我们为什么没有选择LLM抽向量?)

这个类里面我们干的事情其实也不多,总结一下就那么3件。 1. 读文件(_load_file) 2. 转向量+持久化存储(init_vector_store) 3. 查询(query), 代码整体比较简单,在进一步的细节我这里就不解读了,还是相对容易看明白的。

注: 特别说明一下,我们这里用的抽向量的模型是Sentence-Transformer, 它是Bert的一个变种模型,Bert想必大家是知道的。如果有不太熟悉的同学,可以转到我这边文章,来了解Bert的来龙去脉。Magic:LLM-GPT原理介绍与本地(M1)微调实战

# persist_dir = os.path.join(VECTORE_PATH, ".vectordb") 
# print(persist_dir)

k2v = KnownLedge2Vector()
for s, dc in k2v.query("what is oceanbase?"):
    print(s, dc.page_content, dc.metadata)

知识查询

通过上面的步骤,我们轻轻松松将知识转换为了向量。 那么接下来,我们就是根据Query查询相关知识了。

我们定义了一个KnownLedgeBaseQA, 这个类只有短短十几行代码, 所以看起来也不费劲。 核心的方法就一个,get_similar_answer, 这个方法只接收一个query字符串,根据这个query字符串,我们就可以在我们之前准备好的知识库当中,查询到相关知识。

class KnownLedgeBaseQA:

    def __init__(self) -> None:
        k2v = KnownLedge2Vector()
        self.vector_store = k2v.init_vector_store()
        self.llm = VicunaLLM()
    
    def get_similar_answer(self, query):
        
        prompt = PromptTemplate(
            template=conv_qa_prompt_template,
            input_variables=["context", "question"]
        )

        retriever = self.vector_store.as_retriever(search_kwargs={"k": VECTOR_SEARCH_TOP_K})
        docs = retriever.get_relevant_documents(query=query)

        context = [d.page_content for d in docs] 
        result = prompt.format(context="\n".join(context), question=query)
        return result

推理&QA

知识都查出来了,剩下的就交给大模型吧。 我们这里使用的是vicuna-13b的模型,具体的示例代码如下,

是的,这里也没什么难的,就是构造一个参数,然后发一个POST,也没啥特别好讲的。

def generate(query):

    template_name = "conv_one_shot"
    state = conv_templates[template_name].copy()

    pt = PromptTemplate(
        template=conv_qa_prompt_template,
        input_variables=["context", "question"]
    )

    result = pt.format(context="This page covers how to use the Chroma ecosystem within LangChain. It is broken into two parts: installation and setup, and then references to specific Chroma wrappers.",
              question=query)

    print(result)

    state.append_message(state.roles[0], result)
    state.append_message(state.roles[1], None)

    prompt = state.get_prompt()
    params = {
        "model": "vicuna-13b",
        "prompt": prompt,
        "temperature": 0.7,
        "max_new_tokens": 1024,
        "stop": "###"
    }

    response = requests.post(
        url=urljoin(VICUNA_MODEL_SERVER, vicuna_stream_path), data=json.dumps(params)
    )

    skip_echo_len = len(params["prompt"]) + 1 - params["prompt"].count("</s>") * 3
    for chunk in response.iter_lines(decode_unicode=False, delimiter=b"\0"):
        if chunk:
            data = json.loads(chunk.decode())
            if data["error_code"] == 0:
                output = data["text"][skip_echo_len:].strip()
                state.messages[-1][-1] = output + "▌"
                yield(output) 

最后,让我们看看知识问答的效果吧。如果觉得效果好,为我们点个赞吧👍

image.png

小结

综上所属,我们讲了当前开源主流的两个扛把子强强联合的应用实战。 Vicuna-13B与Langchain在整个AI的生态里面,做的是完全不同的事情。 一个是定义框架做标准跟链接, 一个是深入核心做技术跟效果。很显然,这两条路都获得了重大的成功。 整个发展的思路,我相信很值得我们借鉴,通过本文的介绍,希望能对大家有一些帮助。

最后,如果你觉得本教程里面的内容对你有帮助,并且想持续关注我们的项目,请帮忙在GitHub给我们的项目点个赞吧❤️💗💗😊😊😊。 项目地址: github.com/csunny/DB-G…

当然了,如你开篇所见,这仅仅是我们项目里面很小的一部分,同时这也会是一个系列教程。 如果关心我们的项目,或者对我们的工作感兴趣,欢迎持续关注我们。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1967590.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

为什么越来越多的IT青年转行网络安全?

目前&#xff0c;我国互联网已经从爆发增长期进入平稳发展阶段&#xff0c;同时每年大量计算机相关专业的毕业生涌入就业市场&#xff0c;导致IT行业逐渐趋于饱和状态&#xff0c;甚至出现裁员现象&#xff0c;去年很多大厂都有裁员&#xff0c;不少程序员再就业成了难题。 面…

大彩触摸屏与单片机通讯

目录&#xff1a; 一、概述 1、触摸屏简介 2、安装软件 1&#xff09;设置VSPD软件 2&#xff09;设置VisualTFT软件 3&#xff09;设置串口软件 二、单片机发送指令给触摸屏 1、发送文本 2、显示与隐藏控件 1&#xff09;通过指令助手生成指令 2&#xff09;隐藏…

IDEA启动springBoot项目,显示构建和正在启动XxxApplication之后无反应

今天拉其他项目组的代码&#xff0c;然后发现IDEA启动不了项目&#xff0c;点击启动一闪而过&#xff0c;啥提示也没有&#xff0c;因为之前有过类似IDEA出错的经验&#xff0c;所以知道怎么排查。 首先打开IDEA日志输出&#xff0c;然后看具体是什么错 帮助>Tail Log in Co…

Linux用户无法访问Github怎么办?

进入Steam官网:Watt Toolkit 1.点击下载 2.在点击授权并下载 3.尽量选择Nas分流&#xff08;德国&#xff09; 4.然后选择最新版本 5.点击Linux版本它会自动文件夹 6.双击,他会自动下载 7.下载完成后进行解压,解压后进入目录 8.右键在此打开终端,在终端输入,运行此脚本 …

揭秘!焦虑症不只是心理战,这些躯体化症状你中招了吗?

引言 在这个快节奏、高压力的时代&#xff0c;焦虑症已成为许多人难以言说的秘密。它不仅悄无声息地侵蚀着我们的心理健康&#xff0c;还可能以一系列令人意想不到的躯体化症状显现&#xff0c;让人误以为自己只是“身体出了点小毛病”。今天&#xff0c;就让我们一起揭开焦虑…

[工具推荐]前端加解密之Burp插件Galaxy

如果觉得该文章有帮助的&#xff0c;麻烦师傅们可以搜索下微信公众号&#xff1a;良月安全。点个关注&#xff0c;感谢师傅们的支持。 免责声明 本号所发布的所有内容&#xff0c;包括但不限于信息、工具、项目以及文章&#xff0c;均旨在提供学习与研究之用。所有工具安全性…

肖扬率团队到北京军区干休所与离退休老干部座谈

在中国人民解放军建军97周年到来之际&#xff0c;为弘扬拥军优属光荣传统&#xff0c;营造尊崇关爱军人的浓厚氛围&#xff0c;世界中医药联合会骨伤科专业委员会副会长肖扬教授率团队遵从上级部门安排于7月31日上午到北京军区干休所看望离退休的老干部和多位老将军的后代&…

【DRF性能优化】

一、背景 项目中有一个查询脚本的接口&#xff0c;查询20条数据需要5min&#xff0c;性能很差,需要优化 二、问题排查 查看代码发现&#xff0c;serializers中&#xff0c;发现了一个奇怪的查询 查询脚本时&#xff0c;关联的脚本版本的一些字段也需要查询出来&#xff0c;…

安卓单机游戏:世界盒子手机游戏,最新版,春秋MOD整合 下载

《世界盒子》&#xff08;WorldBox&#xff09;是一款由Maxim Karpenko制作的沙盒模拟类游戏。这款游戏允许玩家在游戏中扮演上帝的角色&#xff0c;使用神力来创造和改变像素世界。玩家可以利用水、沙子、土壤、森林、人类、种子、动物等元素&#xff0c;以及温度、降雨等环境…

【数据结构】了解哈希表,解决哈希冲突,用Java模拟实现哈希桶

哈希表的概念 哈希表&#xff08;Hash Table&#xff09;是一种高效的数据结构&#xff0c;用于实现快速的数据存储和检索。它通过将数据映射到一个数组的索引位置&#xff0c;从而能够在平均情况下实现O(1)的时间复杂度进行查找、插入和删除操作。 哈希表的基本概念包括以下…

LLM应用-prompt提示:让大模型总结生成PPT

参考&#xff1a; https://mp.weixin.qq.com/s/frKOjf4hb6yec8LzSmvQ7A 思路&#xff1a;通过大模型生成markdown内容&#xff0c;通过markdown去生成PPT 技术&#xff1a;Marp&#xff08;https://marp.app/&#xff09;这里用的这个工具进行markdown转PPT 1、让大模型生成Ma…

川土微电子|高性能模拟芯片供应商

上海川土微电子有限公司&#xff0c;成立于2016年&#xff0c;总部位在上海&#xff0c;并于深圳、北京、杭州设有分支机构&#xff0c;产品涵盖隔离与接口、驱动与电源、高性能模拟三大产品线以及μMiC战略产品&#xff08; micro-Module in Chip&#xff09;。目前产品已广泛…

玩转大模型之五(测试FastGPT高级编排)

一、高级编排 FastGPT 从 V4 版本开始采用新的交互方式来构建 AI 应用。使用了 Flow 节点编排&#xff08;工作流&#xff09;的方式来实现复杂工作流&#xff0c;提高可玩性和扩展性。但同时也提高了上手的门槛&#xff0c;有一定开发背景的用户使用起来会比较容易。 编排方…

局域网内远程控制桌面软件推荐

在现代办公环境中&#xff0c;局域网&#xff08;LAN&#xff09;内的远程桌面连接已成为提升工作效率和促进团队协作的关键技术之一。无论是需要访问办公室内部服务器&#xff0c;还是在家工作时远程操作公司电脑&#xff0c;局域网内的远程桌面都能满足这一需求。本文将探讨在…

互联网家政小程序,为大众带来高效、便捷的服务

随着人口老龄化的严重和社会生活节奏的加快&#xff0c;大众对家政服务的需求日益增加&#xff0c;家政行业的市场规模逐渐扩大&#xff01; 在科技的推动下&#xff0c;家政行业开始向数字化发展&#xff0c;“互联网家政”的模式推动了市场的快速发展。互联网家政小程序借助…

安卓Intent

文章目录 Intent新建一个活动显示Intent隐式Intent同多隐式Intent用法向下一个活动传递数据向上一个活动返回数据 Intent Intent是Android程序中各组件之间进行交互的一种重要方式&#xff0c;它不仅可以指明当前组件想要执行的动作&#xff0c;还可以在不同组件之间传递数据。…

EPM 和 EPM-P 系列功率计

EPM 和 EPM-P 系列功率计 是德(KEYSIGHT)简述 EPM 功率计提供了 CW 和平均功率测量功能。EPM-P 功率计提供了峰值、均值、峰值均值比和时间选通功率测量功能。 EPM系列 功率计表 功能特点 EPM 和 EPM-P 系列提供高性能、可编程的功率计&#xff0c;以执行连续波、平均功率和…

Wasm(WebAssembly) 编译环境搭建、浏览器调用

参考:https://www.deanhan.cn/wasm.html 以下以Windows系统 c 语音编译为wasm为例说明: 安装npm访问Node.js官方网站:https://nodejs.org/ 点击“Download”按钮,选择Windows版本(32位或64位)。 下载完成后,运行安装程序。 安装过程中,确保选中了“Add Node.js to …

42 字典创建与删除

字典&#xff08;dict&#xff09;是包含 “键:值” 元素的无序可变序列&#xff0c;字典中的每个元素包含用冒号分隔开的 “键” 和 “值” 两部分&#xff0c;表示一种映射或对应关系&#xff0c;也称为关联数组。定义字典时&#xff0c;每个元素的 “键” 和 “值” 之间用冒…

嵌入式C++、ROS 、OpenCV、SLAM 算法和路径规划算法:自主导航的移动机器人流程设计(代码示例)

在当今科技迅速发展的背景下&#xff0c;嵌入式自主移动机器人以其广泛的应用前景和技术挑战吸引了越来越多的研究者和开发者。本文将详细介绍一个嵌入式自主移动机器人项目&#xff0c;涵盖其硬件与软件系统设计、代码实现及项目总结&#xff0c;并提供相关参考文献。 项目概…