LangChain大模型应用开发指南-封装自己的LLM
  GnFX9Na5BZpa 2023年12月07日 34 0

在之前的课程中,我带领小伙伴们使用开源项目实现了将星火模型的OpenAI-API接口适配转换封装,没有看过的小伙伴可以点击链接查看: AI课程合集

但是这种做法的局限性也很强,只能使用开源项目适配过的大模型,并且由于多了一层适配代理,接口的性能也存在一定损耗。今天,我将给大家介绍一个更加通用的方案,基于LangChain平台提供的LLM基础模型,完成任意模型接口的封装。

LangChain与大模型交互的核心模型-LLM

LLM(语言逻辑模型)是LangChain平台与各种大模型进行交互的核心模型,它是一个抽象的概念,可以理解为一个能够处理语言输入和输出的黑盒。

LLM的输入是一个字符串,表示用户的请求或问题,LLM的输出也是一个字符串,表示模型的回答或结果。LLM可以根据不同的输入,调用不同的大模型,来完成不同的语言任务,如文本生成、文本理解、文本翻译等。

LLM的优势在于,它可以让开发者无需关心大模型的细节和复杂性,只需要关注语言的逻辑和意义,就可以利用大模型的能力来构建自己的应用。LLM也可以让开发者灵活地选择和切换不同的大模型,而无需修改代码或适配接口。LLM还可以让开发者自己封装自己的LLM,来实现自己的语言逻辑和功能。

如何自己封装一个LLM

要自己封装一个LLM,只需要实现以下两个必要的方法:

  • 一个_call方法,它接受一个字符串作为输入,表示用户的请求或问题,还可以接受一些可选的停用词,用于过滤无关的词汇,它返回一个字符串作为输出,表示模型的回答或结果。
  • 一个_identifying_params属性,它用于帮助打印这个类的信息,它返回一个字典,包含一些描述这个类的参数。

下面我们来实现一个非常简单的自定义LLM,它返回输入的前n个字符。

# 导入LangChain的LLM基类
from langchain.llm import LLM

# 定义一个自定义的LLM类,继承自LLM基类
class CustomLLM(LLM):
    # 初始化方法,接受一个参数n,表示返回的字符数
    def __init__(self, n):
        # 调用父类的初始化方法
        super().__init__()
        # 将n赋值给self.n
        self.n = n

    # 实现_call方法,接受一个字符串input,和一些可选的停用词stop_words
    def _call(self, input, stop_words=None):
        # 如果有停用词,就过滤掉输入中的停用词
        if stop_words:
            # 将输入分割成单词列表
            words = input.split()
            # 创建一个空列表,用于存放过滤后的单词
            filtered_words = []
            # 遍历单词列表
            for word in words:
                # 如果单词不在停用词列表中,就将其添加到过滤后的单词列表中
                if word not in stop_words:
                    filtered_words.append(word)
            # 将过滤后的单词列表重新拼接成字符串
            input = " ".join(filtered_words)
        # 返回输入的前self.n个字符
        return input[:self.n]

    # 实现_identifying_params属性,返回一个字典,包含n的值
    @property
    def _identifying_params(self):
        return {"n": self.n}

基于讯飞星火api封装LLM实例

讯飞星火是一款基于人工智能的语音、图像、自然语言处理等领域的开放平台,提供了多种api接口,让开发者可以轻松地使用讯飞的技术能力。我们可以基于讯飞星火的api封装一个LLM,来实现一些语言任务,如文本翻译、文本摘要、文本分类等。

下面我们以星火大模型为例,来展示如何基于讯飞星火api封装一个LLM。我们需要先注册一个讯飞星火的账号,然后创建一个应用,获取应用的appid和appsecret,这两个参数是调用api的必要条件。我们还需要安装requests库,用于发送http请求。

官方提供的示例代码,SparkApi如下可直接使用

import _thread as thread
import base64
import datetime
import hashlib
import hmac
import json
from urllib.parse import urlparse
import ssl
from datetime import datetime
from time import mktime
from urllib.parse import urlencode
from wsgiref.handlers import format_date_time

import websocket  # 使用websocket_client
answer = ""

class Ws_Param(object):
    # 初始化
    def __init__(self, APPID, APIKey, APISecret, Spark_url):
        self.APPID = APPID
        self.APIKey = APIKey
        self.APISecret = APISecret
        self.host = urlparse(Spark_url).netloc
        self.path = urlparse(Spark_url).path
        self.Spark_url = Spark_url

    # 生成url
    def create_url(self):
        # 生成RFC1123格式的时间戳
        now = datetime.now()
        date = format_date_time(mktime(now.timetuple()))

        # 拼接字符串
        signature_origin = "host: " + self.host + "\n"
        signature_origin += "date: " + date + "\n"
        signature_origin += "GET " + self.path + " HTTP/1.1"

        # 进行hmac-sha256进行加密
        signature_sha = hmac.new(self.APISecret.encode('utf-8'), signature_origin.encode('utf-8'),
                                 digestmod=hashlib.sha256).digest()

        signature_sha_base64 = base64.b64encode(signature_sha).decode(encoding='utf-8')

        authorization_origin = f'api_key="{self.APIKey}", algorithm="hmac-sha256", headers="host date request-line", signature="{signature_sha_base64}"'

        authorization = base64.b64encode(authorization_origin.encode('utf-8')).decode(encoding='utf-8')

        # 将请求的鉴权参数组合为字典
        v = {
            "authorization": authorization,
            "date": date,
            "host": self.host
        }
        # 拼接鉴权参数,生成url
        url = self.Spark_url + '?' + urlencode(v)
        # 此处打印出建立连接时候的url,参考本demo的时候可取消上方打印的注释,比对相同参数时生成的url与自己代码生成的url是否一致
        return url


# 收到websocket错误的处理
def on_error(ws, error):
    print("### error:", error)


# 收到websocket关闭的处理
def on_close(ws,one,two):
    print(" ")


# 收到websocket连接建立的处理
def on_open(ws):
    thread.start_new_thread(run, (ws,))


def run(ws, *args):
    data = json.dumps(gen_params(appid=ws.appid, domain= ws.domain,question=ws.question))
    ws.send(data)


# 收到websocket消息的处理
def on_message(ws, message):
    # print(message)
    data = json.loads(message)
    code = data['header']['code']
    if code != 0:
        print(f'请求错误: {code}, {data}')
        ws.close()
    else:
        choices = data["payload"]["choices"]
        status = choices["status"]
        content = choices["text"][0]["content"]
        print(content,end ="")
        global answer
        answer += content
        # print(1)
        if status == 2:
            ws.close()


def gen_params(appid, domain,question):
    """
    通过appid和用户的提问来生成请参数
    """
    data = {
        "header": {
            "app_id": appid,
            "uid": "1234"
        },
        "parameter": {
            "chat": {
                "domain": domain,
                "temperature": 0.5,
                "max_tokens": 2048
            }
        },
        "payload": {
            "message": {
                "text": question
            }
        }
    }
    return data


def main(appid, api_key, api_secret, Spark_url,domain, question):
    # print("星火:")
    wsParam = Ws_Param(appid, api_key, api_secret, Spark_url)
    websocket.enableTrace(False)
    wsUrl = wsParam.create_url()
    ws = websocket.WebSocketApp(wsUrl, on_message=on_message, on_error=on_error, on_close=on_close, on_open=on_open)
    ws.appid = appid
    ws.question = question
    ws.domain = domain
    ws.run_forever(sslopt={"cert_reqs": ssl.CERT_NONE})
    

我们可以基于这个api来进行我们自定义的LLM封装如下:

from llm.adaptor import SparkApi
from typing import Any, List, Mapping, Optional

from langchain.callbacks.manager import CallbackManagerForLLMRun
from langchain.llms.base import LLM

#用于配置大模型版本,默认“general/generalv2”
# domain = "general"   # v1.5版本
#domain = "generalv2"    # v2.0版本
#domain = "generalv3"    # v3.0版本
#云端环境的服务地址
# Spark_url = "ws://spark-api.xf-yun.com/v1.1/chat"  # v1.5环境的地址
#Spark_url = "ws://spark-api.xf-yun.com/v2.1/chat"  # v2.0环境的地址
#Spark_url = "ws://spark-api.xf-yun.com/v3.1/chat"  # v3.0环境的地址
modal_dict={"general":"ws://spark-api.xf-yun.com/v1.1/chat",
            "generalv2":"ws://spark-api.xf-yun.com/v2.1/chat",
            "generalv3":"ws://spark-api.xf-yun.com/v3.1/chat"
            }


def getText(role,content):
    text =[]
    jsoncon = {}
    jsoncon["role"] = role
    jsoncon["content"] = content
    text.append(jsoncon)
    return text

class SparkLLM(LLM):
    appid: Optional[str] = None
    api_secret: Optional[str] = None
    api_key: Optional[str] = None
    model: Optional[str] = None

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

    def _call(
        self,
        prompt: str,
        stop: Optional[List[str]] = None,
        run_manager: Optional[CallbackManagerForLLMRun] = None,
        **kwargs: Any,
    ) -> str:
        Spark_url = modal_dict[self.model]
        domain = self.model
        question = getText("user",prompt)
        SparkApi.answer = ""
        SparkApi.main(self.appid,self.api_key,self.api_secret,Spark_url,domain,question)
        return SparkApi.answer

    @property
    def _identifying_params(self) -> Mapping[str, Any]:
        """Get the identifying parameters."""
        return {"appid": self.appid}

这样,我们就完成了基于讯飞星火api封装一个LLM的实例,我们可以在LangChain平台上使用这个LLM。

xh_app_id = ""

xh_api_secret = ""

xh_api_key = ""

modal = "generalv3"
llm = SparkLLM(appid=xh_app_id,api_secret=xh_api_secret,api_key=xh_api_key,model=model)

当然,同样的封装方法也适用于其他大语言模型,例如百度提供了文心一言erniebot的sdk,我们的封装会比以上的示例更加简单。

总结

这篇文章就到这里结束了,希望你能够通过这篇文章,了解到如何使用LangChain平台开发基于大模型的应用,以及如何自己封装一个LLM。如果你对LangChain平台或LLM有任何疑问或建议,欢迎随时与我交流。😊

【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年12月07日 0

暂无评论

推荐阅读
GnFX9Na5BZpa