【langchain】快速封装替换自定义LLM(基于自定义API或本地模型)

1. 引言

你可能已经注意到,LLM时代下的许多项目(特别是Github上的论文项目、工程项目)都要求我们设置OpenAI的API Key,就像这样:

os.environ["OPENAI_API_KEY"] = "sk-"

from langchain_openai import ChatOpenAI
llm = ChatOpenAI(...)

这种方法虽然直接,但限制了我们自由使用本地模型或自定义API的能力。
而且,使用OpenAI的服务还得考虑网络问题,这无疑给本就复杂的开发流程又添了一堵墙。

但是如何在尽可能不修改原项目代码的情况下,不依赖OpenAI的API令牌,封装自定义的大语言模型(LLM)模块呢?

本文的目的就是带你一起探索如何利用langchain库,绕过这些限制,快速封装自定义的LLM。

适用于:

  • 想用本地的开源模型
  • 想调用网上的API接口
  • 想调自己实现的简单方法

2. 简化封装,直接使用__call__

在工程学中,大型语言模型(LLM)的核心可以简化为一个简单的文本输入输出函数。我们希望像调用函数一样使用模型。

custom_llm = CustomLLM()
print(custom_llm("How to play basketball?"))

为了实现这一点,我们需要在自定义模型类中重写__call__方法。

from typing import List, Optional

class CustomLLM1:
    def __call__(self, prompt: str) -> str:
        # 这里是调用自定义模型或API接口的逻辑
        messages = [
            {"role": "user", "content": prompt}
            # 如果需要,可以在这里添加更多的消息历史
        ]

        response = self.llama_completion(messages)
        return response

    def llama_completion(self, messages: List[dict]) -> str:
        # 调用llama的接口,返回响应
        return "Hello from llama!"```

现在,我们可以通过创建CustomLLM1的实例,并直接调用它,来模拟获取答案的过程:

custom_llm = CustomLLM1()
print(custom_llm("How to play basketball?"))

这个简化的封装过程让我们能够快速地实现和测试自定义模型的调用逻辑,而无需深陷复杂的实现细节。

进一步完善,下面我们将 __call__方法简单地委托给 _call 方法,同时增加一些可选的参数(比如输出的停词逻辑)

# 不可运行,仅供思路参考
class CustomLLM2:
    def __call__(self, prompt: str, stop: Optional[List[str]] = None) -> str:
        #
        return self._call(prompt, stop)

    def _call(self, prompt: str, stop: Optional[List[str]] = None) -> str:
        messages = [
            {"role": "user", "content": prompt}
        ]
        response = self.llama_completion(messages)
        if stop is not None:
            response = self.enforce_stop_tokens(response, stop) 
        return response        

看上去似乎有点多余?不过这是langchain内LLM类构造的思想,这里我们了解其中意思即可。

3. 实际应用:封装ChatGLM3和ChatGLM4模型

听上去很简单,但是还是不知道应该怎么替换原项目中的相关代码。

如果是常规项目,我们其实直接替换对应的生成函数就行。

至于项目原本封装好的LLM,多半是langchain库下的LLM家族,哪怕是OpenAI的。

故我们可以继承langchain下的基础LLM类,替换其中特定的调用方法为自己的模型接口,其他方法保持不变即可。

现在,让我们通过两个实际的例子来展示如何封装自定义的LLM:使用本地部署的开源模型ChatGLM3通过API接口调用的ChatGLM4

3.1 封装ChatGLM3本地模型

首先,我们看看如何封装一个本地的ChatGLM3模型。这个模型使用transformers库加载,并利用其提供的AutoTokenizerAutoModel进行文本生成。

from langchain.llms.base import LLM
from langchain.llms.utils import enforce_stop_tokens
from transformers import AutoTokenizer, AutoModel
import torch

class ChatGLM3(LLM):
    def __init__(self):
        super().__init__()
        print("construct ChatGLM3")
        self.tokenizer = None
        self.model = None

    def load_model(self, model_path, quantize=False):
        # 加载模型和分词器
        self.tokenizer = AutoTokenizer.from_pretrained(model_path)
        self.model = AutoModel.from_pretrained(model_path).half()
        if quantize:
            self.model = self.model.quantize(8)
        # 将模型移动到适当的设备上
        device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
        self.model = self.model.to(device)
        self.model.eval()

    def _call(self, prompt, stop=None):
        # 使用模型生成文本
        response, _ = self.model.chat(
            self.tokenizer,
            prompt,
            history=self.history,
            max_length=8192,
            temperature=0.01,
            top_p=0.9
        )
        if stop is not None:
            response = enforce_stop_tokens(response, stop)
        return response

# 使用示例
if __name__ == "__main__":
    llm = ChatGLM3()
    model_path = "path_to_your_chatglm3_model"
    llm.load_model(model_path)
    print(llm("如何打好羽毛球?"))

3.2 封装ChatGLM4 API接口

接下来,我们看看如何封装一个通过API接口调用的ChatGLM4模型。这个例子中,我们假设已经下载了ZhipuAI的SDK,它允许我们发送请求并接收模型的响应。

from langchain.llms.base import LLM
from langchain.llms.utils import enforce_stop_tokens

class ChatGLM4(LLM):
    def __init__(self):
        super().__init__()
        print("construct ChatGLM4")

    def glm4_completion(self, message):
        from zhipuai import ZhipuAI
        client = ZhipuAI(api_key="your_api_key")  # 使用你的API密钥
        response = client.chat.completions.create(
            model="glm-4",
            messages=message,
            stream=False,
            temperature=0.1
        )
        return response.choices[0].message.content

    def _call(self, prompt, stop=None):
        messages = [{"role": "user", "content": prompt}]
        response = self.glm4_completion(messages)
        if stop is not None:
            response = enforce_stop_tokens(response, stop)
        return response

# 使用示例
if __name__ == "__main__":
    llm = ChatGLM4()
    print(llm("如何学好编程?"))

在这两个例子中,我们都利用了langchainLLM基类(其实也可以换成BaseLLM基类)来快速封装自定义模型。对于ChatGLM3,我们直接与本地模型交互;而对于ChatGLM4,我们通过API与远程模型交互。这样的封装方式不仅使得我们的应用程序更加灵活和模块化,而且也简化了模型切换和维护的过程。

4. 核心扩展思路

有了上述的实际实践,但是如ChatOpenAI类的直接替换仍旧存在一些问题。如果直接替换不能成功,提示说不存在某种方法,比如已经封装好的格式化输出方法。

这时候,我们当然不是copy源码拿去应用,只需要将继承的LLM类修改为对应的类,即可实现该方法的补全。

如下即为使用llama3的开源API实现的接口模型,用于替换ChatOpenAI。

# 创建一个自定义的接口模型
from langchain_openai import ChatOpenAI
from typing import Optional, List


class LlamaChat(ChatOpenAI):
    # max_token: int = 512
    temperature: float = 0.1
    history = []
    api_secret:str = ""

    def __init__(self, api_secret: str):
        super().__init__()
        self.api_secret = api_secret
        print("construct LlamaChat")

    @property
    def _llm_type(self) -> str:
        return "LlamaChat"

    def llama_completion(self, messages):
        print("llama_completion")
        import requests
        import json

        url = "https://api.atomecho.cn/v1/chat/completions"
        headers = {
            "Content-Type": "application/json",
            "Authorization": f"Bearer {self.api_secret}",
        }
        data = {
            "model": "Atom-7B-Chat",
            "messages": messages,
            "temperature": self.temperature,
            "stream": False,
        }

        response = requests.post(url, headers=headers, data=json.dumps(data))

        if response.status_code == 200:
            return response.json()["choices"][0]["message"]["content"]
        else:
            return None

    # Override _call method to use API for model inference
    def _call(self, prompt: str, stop: Optional[List[str]] = None) -> str:
        messages = [
            {"role": "user", "content": prompt}
            # Add more message history if needed
        ]

        response = self.llama_completion(messages)
        if stop is not None:
            response = self.enforce_stop_tokens(response, stop)
        return response

    def __call__(self, prompt: str, stop: Optional[List[str]] = None) -> str:
        return self._call(prompt, stop)

结语

本文旨在展示如何利用langchain快速封装自定义LLM,从而突破现有环境下对OpenAI API Key的依赖。通过langchain的LLM类或现有项目的类继承,再修改特定的回调方法即可实现更加个性化和灵活的LLM应用,推动项目快速进展。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SDKMAN!

概述 官网,SDKMAN是一款管理多版本SDK的工具,可以实现在多个版本间的快速切换。 其他特性: 易用:安装SDK不再需要去Google想安装的某个软件的官网的下载页,或找其他下载页面,然后下载安装包、解压、设置…

.NET C# ORM 瀚高数据库

SqlSugar ORM SqlSugar 是一款 老牌 .NET开源ORM框架,由果糖大数据科技团队维护和更新 ,开箱即用最易上手的ORM 优点 :【生态丰富】【高性能】【超简单】 【功能全面】 【多库兼容】【适合产品】 【SqlSugar视频教程】 支持 &#xff1a…

C语言指针和数组的一些笔试题

文章目录 前言一、一维数组二、字符数组-1三、字符数组-2总结 前言 C语言指针和数组的一些笔试题 一、一维数组 #include <stdio.h> int main() {int a[] { 1,2,3,4 };printf("%d\n", sizeof(a));printf("%d\n", sizeof(a 0));printf("%d\n…

Eclipse MAT工具分析内存溢出

1、通过dominator_tree可以查看哪些对象大 可以看到com.codex.terry.entity.User对象有57万个 2、打开thread_overview查看内存溢出的代码

TCP重传,滑动窗口,流量控制,拥塞控制

TCP重传&#xff0c;滑动窗口&#xff0c;流量控制&#xff0c;拥塞控制 TCP重传机制&#xff1a; 超时重传快速重传SACKD-SACK 通过序列号与确认应答判断是否要重传 超时重传&#xff1a; 超过指定时间没有收到确认应答报文&#xff0c;就会重发该数据 触发超时重传的情况…

(十四)Servlet教程——Servlet中HttpSession的使用

除了使用Cookie&#xff0c;Web应用程序中还经常使用Session来记录客户端状态。Session是服务器端使用的一种记录客户端状态的机制&#xff0c;相应地也增加了服务器的存储压力。 1. 什么是Session Session是另外一种记录客户端状态的机制&#xff0c;不同的是Cookie保存在客户…

2024年618哪些数码家电值得入手?热门家电好物抢先看!

618购物狂欢节即将来临&#xff0c;这是一年一度的大促销活动&#xff0c;家电和数码产品在这个时间段内通常都会有优惠和折扣。但随着产品的多样化&#xff0c;很多时候一款产品就有多款品牌&#xff0c;在这不同品牌又各自擅长不同的东西&#xff0c;看着眼花缭乱。今天我就给…

基于python+django网易新闻+评论的舆情热点分析平台

博主介绍&#xff1a; 大家好&#xff0c;本人精通Java、Python、C#、C、C编程语言&#xff0c;同时也熟练掌握微信小程序、Php和Android等技术&#xff0c;能够为大家提供全方位的技术支持和交流。 我有丰富的成品Java、Python、C#毕设项目经验&#xff0c;能够为学生提供各类…

Java中优雅实现泛型类型的强制转换

在Java中经常遇到将对象强制转换成泛型类的情况&#xff1a; Map<String, Object> data Map.of("name", "XiaoMing","age", 17,"scores", List.of(80, 90, 70) );List<Integer> scores (List<Integer>) data.get…

Docker容器添加修改端口映射的方法与详细步骤

1、先找到要修改的容器hash值&#xff1a; 2、然后退出docker Desktop服务 &#xff08;因为在线状态配置文件修改保存不了&#xff09; 3、资源管理器中打开最新安装的Docker的配置文件的路径&#xff1a; 4、打开后修改其中的 config.v2.json 和 hostconfig.json 5、启动…

【C++】哈希表的底层逻辑

目录 一、哈希概念 1、哈希冲突 2、哈希冲突的解决 a、闭散列 &#x1f7e2;插入 &#x1f7e2;查找 &#x1f7e2;删除 &#x1f7e2;其他类型的数据 &#x1f7e2;实现 b、 开散列 &#x1f7e2;插入 &#x1f7e2;查找 &#x1f7e2;删除 &#x1f7e2;析构 &a…

《霍格沃茨之遗》找不到emp.dll如何修复?分享5种亲测有效的方法

在我们享受电脑游戏带来的乐趣时&#xff0c;偶尔会遇到一些技术上问题&#xff0c;具体来说&#xff0c;当你启动一款游戏&#xff0c;系统却弹出一个提示“由于找不到emp.dll文件&#xff0c;因此无法继续执行代码”&#xff0c;这样的情况确实让人感到扫兴。这究竟是什么原因…

.net core ef 连表查询

Information和TypeInfo连表查询 类似&#xff1a; select st.Title1,si.* from [Star_Information] si left join Star_TypeInfo st on si.typeId2st.id 先在EfCoreDbContext.cs配置 protected override void OnModelCreating(ModelBuilder builder){base.OnModelCreating(b…

Jupyter Notebook 中使用虚拟环境的Python解释器

问题&#xff1a;创建虚拟环境&#xff0c;在pycharm中配置虚拟环境的Python解释器&#xff0c;然后在pycharm中打开ipynb&#xff0c;执行发现缺少包&#xff0c;但是虚拟环境中已经安装了 解决方式&#xff1a; 配置Jupyter Notebook 使用虚拟环境的Python解释器 1&#x…

ElasticSearch总结2

一、创建索引库&#xff1a;PUT ES中通过Restful请求操作索引库、文档。请求内容用DSL语句来表示。创建索引库和mapping的DSL语法如下&#xff1a; 整个jason 里边&#xff0c;它有一个叫mapping的属性&#xff0c;代表的是映射。映射里边有properties代表就是字段。可以看到这…

C++入门系列-缺省参数

&#x1f308;个人主页&#xff1a;羽晨同学 &#x1f4ab;个人格言:“成为自己未来的主人~” 缺省参数的概念 缺省参数是生命或者定义函数时为函数的参数指定一个缺省值&#xff0c;在调用该函数时&#xff0c;如果没有指定实参则采用该形参的缺省值&#xff0c;否则使用…

一单利润100+,不起眼的小生意,却能闷声发财!

今天&#xff0c;我想向大家介绍一个看似不太热门&#xff0c;但实际上需求很高的项目——酒店代订。这个项目其实很早以前就已经有人开始尝试了&#xff0c;但可能并没有被大众所熟知。简而言之&#xff0c;酒店代订就是帮助他人通过我们来预订他们想要入住的酒店。 当客户将…

ThinkPHP5 SQL注入漏洞敏感信息泄露漏洞

1 漏洞介绍 ThinkPHP是在中国使用极为广泛的PHP开发框架。在其版本5.0&#xff08;<5.1.23&#xff09;中,开启debug模式&#xff0c;传入的某参数在绑定编译指令的时候又没有安全处理&#xff0c;预编译的时候导致SQL异常报错。然而thinkphp5默认开启debug模式&#xff0c…

Allegro如何将铜皮复制到其他层

如何将铜皮复制到其他层 方法一&#xff1a; 第一步&#xff1a;选择需要复制的铜皮&#xff0c;然后按下CtrlC 第二步&#xff1a;选择要粘贴到的层面&#xff0c;然后按Ctrl V 将在所在层面创建一个新的铜皮&#xff0c;几何形状与原铜皮完全一样 方法二&#xff1a; 第一…

基于SSM的个人博客系统(二)

目录 第四章 系统设计 4.1 系统总流程 4.2 博主用例 4.3 游客用例 4.4 系统类 一、博客类 二、博客类型类 三&#xff0c;评论类&#xff1a; 四&#xff0e;友情链接类 4.5 E-R图 4.6 系统表设计 前面内容请移步 基于SSM的个人博客系统&#xff08;一&#xff09;…