【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, Optionalclass CustomLLM1:def __call__(self, prompt: str) -> str:# 这里是调用自定义模型或API接口的逻辑messages = [{"role": "user", "content": prompt}# 如果需要,可以在这里添加更多的消息历史]response = self.llama_completion(messages)return responsedef 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 torchclass ChatGLM3(LLM):def __init__(self):super().__init__()print("construct ChatGLM3")self.tokenizer = Noneself.model = Nonedef 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_tokensclass ChatGLM4(LLM):def __init__(self):super().__init__()print("construct ChatGLM4")def glm4_completion(self, message):from zhipuai import ZhipuAIclient = 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.contentdef _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, Listclass LlamaChat(ChatOpenAI):# max_token: int = 512temperature: float = 0.1history = []api_secret:str = ""def __init__(self, api_secret: str):super().__init__()self.api_secret = api_secretprint("construct LlamaChat")@propertydef _llm_type(self) -> str:return "LlamaChat"def llama_completion(self, messages):print("llama_completion")import requestsimport jsonurl = "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 inferencedef _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 responsedef __call__(self, prompt: str, stop: Optional[List[str]] = None) -> str:return self._call(prompt, stop)

结语

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

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【多模态大模型】AI对视频内容解析问答

文章目录 1. 项目背景2. 直接对视频进行解析进行AI问答:MiniGPT4-Video2.1 MiniGPT4-Video效果 3. 对视频抽帧为图片再进行AI问答3.1 视频抽帧3.2 图片AI问答3.2.1 阿里通义千问大模型 Qwen-vl-plus3.2.2 Moonshot 1. 项目背景 最近在做一个项目,需要使用AI技术对视…

【图论】图论基础

图论不同地方讲的不太一样,本文仅限作者的理解 定义 图一般由点集 V V V 和边集 E E E 组成。 对于 v ∈ V v\in V v∈V,称 v v v 为该图的一个节点。 对于 e ∈ E e\in E e∈E,一般用二元组 ( u , v ) (u,v) (u,v) 表示 e e e&…

如何批量修改文件的时间属性?修改创建时间,修改时间和访问时间

一,前言 在Excel中,修改文件的访问时间、创建时间和修改时间通常不是一个直接的功能。但是,我们可以通过一些间接的方法和工具来实现这一目标。请注意,直接修改这些时间戳可能会影响文件的完整性和安全性,因此在进行任…

交通运输智慧监管平台---强化物流安全与效率的新举措

一、建设背景 随着社会对于交通安全和环境保护的要求不断提高,对卡车运输的监管和合规性要求也逐渐加强。为了满足快速发展的物流需求,提高供应链协同和可追溯性、解决安全问题、提高运输效率和降低成本,我们利用现代技术和信息化手段着力建设…

Systemback Ubuntu14.04 制作自定义系统ISO镜像

工作需要,要基于ubuntu自定义一些编译环境并将自己配置好的ubuntu做成镜像。 硬件准备 ​ 为保证能够顺利完成系统iso镜像的制作与系统还原,推荐准备一个较大容量的U盘或者移动固态硬盘,同时确保自己的Ubuntu系统还有比较大的可用空间。 1 S…

Javascript基础(三)

Javascript基础(一) Javascript基础(二) 引用数据类型 在之前的文章中,我们提及了与基本数据类型并列的引用数据类型,当时提到引用数据类型大致分为三类:数组Array,函数Function&a…

c#数据库: 9.删除和添加新字段/数据更新

先把原来数据表的sexy字段删除,然后重新在添加字段sexy,如果添加成功,sexy列的随机内容会更新.原数据表如下: using System; using System.Collections.Generic; using System.Data; using System.Data.Common; using System.Data.SqlClient; using System.Linq; using System.…

基于单片机公交语音报站系统设计 含源码,Proteus仿真原理图

资料下载地址:基于单片机公交语音报站系统设计 含源码,Proteus仿真原理图 1、前言 系统的总体设计需要充分运用STC单片机作为主控控制芯片,完成主控控制电路的基本设计,辅助控制电路是语音控制电路、12864显示电路、按键控制电路、…

Apache POI 在java中处理excel

介绍: Apache POI 是一个处理Miscrosoft Office各种文件格式的开源项目。简单来说就是&#xff0c;我们可以使用 POI 在 Java 程序中对Miscrosoft Office各种文件进行读写操作。 一般情况下&#xff0c;POI 都是用于操作 Excel 文件。 如何使用: 1.maven坐标引入 <depend…

java-常量池

public class ConstantTest {public static final String s1 "我爱北京天安门";public static final String s2 "我爱北京天安门";public static void main(String[] args) {ConstantTest constantTest new ConstantTest();} }

Kubernetes 弃用Docker后 Kubelet切换到Containerd

containerd 是一个高级容器运行时&#xff0c;又名 容器管理器。简单来说&#xff0c;它是一个守护进程&#xff0c;在单个主机上管理完整的容器生命周期&#xff1a;创建、启动、停止容器、拉取和存储镜像、配置挂载、网络等。 containerd 旨在轻松嵌入到更大的系统中。Docke…

stable diffusion controlnet前处理中的图像resize

在SD controlnet应用中&#xff0c;一般都要先安装controlnet_aux&#xff0c;并在项目代码中import相关前处理模块&#xff0c;如下所示。 在对control image进行前处理&#xff08;比如找边缘&#xff0c;人体特征点&#xff09;之前&#xff0c;往往会图像进行resize&#x…