我们的模型终于训好了,现在可以与模型对接了。
由于模型是在云服务器上跑的,因此模型的地址与后端的地址不同,需要特殊处理。
模型访问方式
对于模型的地址与后端的地址不同的问题,有两种解决方案:
- 中转:前端调用后端,后端再将请求转发至模型,后端收到模型的回复后传回给前端
- 直连:后端提供查询接口,返回模型的地址,前端直接调用这个地址
中转
后端使用requests重新发送请求给模型,再转发回前端:
@app.route('/chat',methods=['POST'])
def chat():
response=requests.request(method='POST',url='http://127.0.0.1:8800/v1/chat/completions',json=request.get_json())
return Response(response.content, content_type=response.headers['Content-Type'], status=response.status_code)
这个方法优势是前端不用区分后端与模型,直接调用后端方法即可,并且后端还可以对数据进行处理。
但是如果使用流式传输,这种方法会将模型的输出合并后再返回,造成了不必要的延迟,并且打破了数据的边界,打乱了返回数据的格式。
直连
后端提供一个方法查询模型地址:
@app.route('/chaturl',methods=['GET'])
def chaturl():
return jsonify({"url":"http://127.0.0.1:8800/v1/chat/completions","max_token":1024})
这种方法其实优势不大,并且如果前端是Web端,可能会出现跨域问题;但是它能直接获取到模型输出的数据,不经后端中转,性能上好一些。
最终使用了直连方式。
模型调用接口
我们使用了ChatGLM模型,调用对话接口时使用POST方法,传入如下json
{
"model": "chatglm3-6b",
"messages": [
{
"role": "system",
"content": "You are ChatGLM3, a large language model trained by Zhipu.AI. Follow the user’s instructions carefully. Respond using markdown."
},
{
"role": "user",
"content": "山东有什么地方推荐"
}
],
"stream": true,
"max_tokens": 1024,
"temperature": 0.8,
"top_p": 0.8
}
其中最重要的是messages参数,这是一个列表,里面依次列出了历史对话,因为模型不保存历史对话,所以需要在传参时作为上下文传入。每一条对话有role和content两个属性,role表示这句话是谁说的,content是说的内容。messages的最后一项一定是用户说的,表示用户提问。当收到模型的回答后,需要将其附加到末尾。
stream参数控制是否使用流式传输。
流式传输
启用流式传输后,模型会一词一句地返回,为了流式接收,在flutter中需要在发送请求时添加header,options: Options(responseType: ResponseType.stream);启用流式传输后,可以通过response.data.stream流式获取内容,具体代码如下:
流式调用:
@override
Future<Stream<Uint8List>> stream(String msg) async{
message.add(_Message("user", msg));
log(jsonEncode(message));
var response=await dio.post(
"",
data:{
"messages":message,
"model": "chatglm3-6b",
"stream": true,
"max_tokens": max_token,
"temperature": 0.8,
"top_p": 0.8
},
options: Options(responseType: ResponseType.stream)
);
Stream<Uint8List> stream=response.data.stream;
return response.data.stream;
}
字节流使用:
await for(final msg in stream){
final res=utf8.decode(msg.toList());
// print("${res.substring(6)} ${res.substring(6).length}");
if(!res.substring(6).startsWith("[")){
String mstr=jsonDecode(res.substring(6))["choices"][0]["delta"]["content"];
if(str==""&&mstr.startsWith("\n "))mstr=mstr.substring(2);
str="$str$mstr";
if(LLMAPIBack.tag.contains(mstr)&&!c.api.visitTag.contains(mstr)){
chater.taged(mstr);
c.api.visitTag.add(mstr);
}else chater.gen(mstr);
}
}