如何将官方大模型封装成本地api
由于讯飞星火认知大模型需要使用 websocket 连接来调用,而其他大模型直接使用 request 调用,例如百度文心、ChatGPT 等存在显著差异。不同调用方式的模型,不能统一调用,这个时候就需要在程序代码中增加很多复杂的业务逻辑、调用细节,增加了程序开发的工作量,也增加了出现 Bug 的概率。
可以使用 FastAPI,对不同的大模型 API 再进行一层封装,将其映射到本地接口上,从而通过统一的方式来调用本地接口实现不同大模型的调用。通过这样的手段,可以极大程度减少对于模型调用的工作量和复杂度。
在这里,以讯飞星火大模型 API 为例,讲解如何将通用大模型 API 封装成本地 API,从而实现同一方式的 API 调用。
要实现本地 API 封装,首先需要安装 fastapi 第三方库,pip安装即可 接下来导入第三方库,并创建一个 API 对象:app = FastAPI() # 创建 api 对象
, 本地 API 一般通过 POST 方式进行访问,即参数会附加在 POST 请求中,需要定义一个数据模型来接收 POST 请求中的数据:python使用class
python
# 定义一个数据模型,用于接收POST请求中的数据
class Item(BaseModel):
prompt : str # 用户
prompt temperature : float # 温度系数
max_tokens : int # token 上限
if_list :
bool = False # 是否多轮对话
- prompt:即用户输入的 Prompt。我们默认为单轮对话调用,因此 prompt 默认为一句输入;
- 如果将 if_list 设置为 True,那么就是多轮对话调用,
- prompt 应为一个已构造好(即有标准 role、content 格式)的列表字符串 ·
- temperature:温度系数
- max_tokens:回答的最大 token 上限
- if_list:是否多轮对话,默认为 False
创建一个post请求的api站点
- @app.post("/spark/")
- async def get_spark_response(item: Item): # 实现星火大模型调用的 API 端点
- response = get_spark(item) return responseST 请求的 API 端点:
定义一个函数来实现对星火 API 的调用:
ini
import SparkApiSelf
# 首先定义一个构造参数函数
def getText(role, content, text = []):
# role 是指定角色,content 是 prompt 内容
jsoncon = {}
jsoncon["role"] = role
jsoncon["content"] = content
text.append(jsoncon)
return text
def get_spark(item):
# 配置 spark 秘钥
#以下密钥信息从控制台获取
appid = "9f922c84" #填写控制台中获取的 APPID 信息
api_secret = "YjU0ODk4MWQ4NTgyNDU5MzNiNWQzZmZm" #填写控制台中获取的 APISecret 信息
api_key ="5d4e6e41f6453936ccc34dd524904324" #填写控制台中获取的 APIKey 信息
domain = "generalv2" # v2.0版本
Spark_url = "ws://spark-api.xf-yun.com/v2.1/chat" # v2.0环境的地址
# 构造请求参数
if item.if_list:
prompt = item.prompt
else:
prompt = getText("user", item.prompt)
response = SparkApiSelf.main(appid,api_key,api_secret,Spark_url,domain,prompt, item.temperature, item.max_tokens)
return response
代码基本都是官方的,应该是没有大问题,有啥的话可以私聊哦,作者最近也在跟着官方团队一起学习。
注意,由于星火给出的示例 SparkApi 中将 temperature、max_tokens 都进行了封装,我们需要对示例代码进行改写,暴露出这两个参数接口,我们实现了一个新的文件 SparkApiSelf,对其中的改动如下:
首先,我们对参数类中新增了 temperature、max_tokens 两个属性:
ini
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
# 自定义
self.temperature = 0
self.max_tokens = 2048
在生成请求参数的函数中,增加这两个参数并在构造请求数据时加入参数:
python
def gen_params(appid, domain,question, temperature, max_tokens):
"""
通过appid和用户的提问来生成请参数
"""
data = {
"header": {
"app_id": appid,
"uid": "1234"
},
"parameter": {
"chat": {
"domain": domain,
"random_threshold": 0.5,
"max_tokens": max_tokens,
"temperature" : temperature,
"auditing": "default"
}
},
"payload": {
"message": {
"text": question
}
}
}
return data
run 函数中调用生成参数时加入这两个参数:
scss
def run(ws, *args):
data = json.dumps(gen_params(appid=ws.appid, domain= ws.domain,question=ws.question, temperature = ws.temperature, max_tokens = ws.max_tokens))
ws.send(data)
最后,由于 WebSocket 是直接打印到终端,但我们需要将最后的结果返回给用户,我们需要修改 main 函数,使用一个队列来装填星火流式输出产生的结果,并最终集成返回给用户:
ini
def main(appid, api_key, api_secret, Spark_url,domain, question, temperature, max_tokens):
# print("星火:")
output_queue = queue.Queue()
def on_message(ws, 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='')
# 将输出值放入队列
output_queue.put(content)
if status == 2:
ws.close()
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.temperature = temperature
ws.max_tokens = max_tokens
ws.run_forever(sslopt={"cert_reqs": ssl.CERT_NONE})
return ''.join([output_queue.get() for _ in range(output_queue.qsize())])