LLM探索:为ChatGLM2的gRPC后端增加连续对话功能
前言
之前我做 AIHub 的时候通过 gRPC 的方式接入了 ChatGLM 等开源大模型,对于大模型这块我搞了个 StarAI 框架,相当于简化版的 langchain ,可以比较方便的把各种大模型和相关配套组合在一起使用。
主要思路还是用的 OpenAI 接口的那套,降低学习成本,但之前为了快速开发,就只搞了个简单的 gRPC 接口,还差个多轮对话功能没有实现,这次就来完善一下这个功能。
简述
系统分为LLM后端和客户端两部分,LLM后端使用 gRPC 提供接口,客户端就是我用 Blazor 开发的 AIHub
所以这次涉及到这几个地方的修改
- proto
- 客户端 - C# 代码
- AIHub页面 - Blazor 的 razor 代码
- gRPC 服务端 - Python 代码
修改 proto
来改造一下 proto 文件
\syntax = "proto3";
import "google/protobuf/wrappers.proto";
option csharp_namespace = "AIHub.RPC";
package aihub;
service ChatHub {
rpc Chat (ChatRequest) returns (ChatReply);
rpc StreamingChat (ChatRequest) returns (stream ChatReply);
}
message ChatRequest {
string prompt = 1;
repeated Message history = 2;
int32 max_length = 3;
float top_p = 4;
float temperature = 5;
}
message Message {
string role = 1;
string content = 2;
}
message ChatReply {
string response = 1;
}
增加了 Message
类型,在 ChatRequest
聊天请求中增加了 history
字段作为对话历史。
修改 C# 的 gRPC 客户端代码
上面的 proto
写完之后编译项目,会重新生成客户端的 C# 代码,现在来修改一下我们的调用代码
可以看到 ChatRequest
多了个 RepeatedField<Message>
类型的 history
属性,这个属性是只读的,所以每次聊天的时候传入对话历史只能使用添加的方式。
为了方便使用,我封装了以下方法来创建 ChatRequest
对象
private ChatRequest GetRequest(string prompt, List<Message>? history = null) {
var request = new ChatRequest {
Prompt = prompt,
MaxLength = 2048,
TopP = 0.75f,
Temperature = 0.95f
};
if (history != null) {
request.History.AddRange(history);
}
return request;
}
继续改写两个聊天的方法,增加个一个 history
参数
public async Task<string> Chat(string prompt, List<Message>? history = null) {
var resp = await _client.ChatAsync(GetRequest(prompt, history));
return RenderText(resp.Response);
}
public async IAsyncEnumerable<string> StreamingChat(string prompt, List<Message>? history = null) {
using var call = _client.StreamingChat(GetRequest(prompt, history));
await foreach (var resp in call.ResponseStream.ReadAllAsync()) {
yield return RenderText(resp.Response);
}
}
搞定。
修改 gRPC 服务端的 Python 代码
先来看看 ChatGLM2 是如何传入对话的
对官方提供的 demo 进行调试,发现传入模型的 history
是列表里面包着一个个元组,表示一个个对话,奇奇怪怪的格式。
history = [('问题1', '回答1'), ('问题2', '回答2')]
但是 AIHub 的对话是按照 OpenAI 的思路来做的,是这样的格式:
history = [
{'role': 'user', 'content': '问题1'},
{'role': 'assistant', 'content': '回答1'},
{'role': 'user', 'content': '问题2'},
{'role': 'assistant', 'content': '回答2'},
]
现在需要把 OpenAI 对话格式转换为 ChatGLM 的格式
直接上代码吧
def messages_to_tuple_history(messages: List[chat_pb2.Message]):
"""把聊天记录列表转换成 ChatGLM 需要的 list 嵌套 tuple 形式"""
history = []
current_completion = ['', '']
is_enter_completion = False
for item in messages:
if not is_enter_completion and item.role == 'user':
is_enter_completion = True
if is_enter_completion:
if item.role == 'user':
if len(current_completion[0]) > 0:
current_completion[0] = f"{current_completion[0]}\n\n{item.content}"
else:
current_completion[0] = item.content
if item.role == 'assistant':
if len(current_completion[1]) > 0:
current_completion[1] = f"{current_completion[1]}\n\n{item.content}"
else:
current_completion[1] = item.content
is_enter_completion = False
history.append((current_completion[0], current_completion[1]))
current_completion = ['', '']
return history
目前只处理了 user 和 assistant 两种角色,其实 OpenAI 还有 system 和 function ,system 比较好处理,可以做成以下形式
[('system prompt1', ''), ('system prompt2', '')]
不过我还没测试,暂时也用不上这个东西,所以就不写在代码里了。
接着继续修改两个对话的方法
class ChatService(chat_pb2_grpc.ChatHubServicer):
def Chat(self, request: chat_pb2.ChatRequest, context):
response, history = model.chat(
tokenizer,
request.prompt,
history=messages_to_tuple_history(request.history),
max_length=request.max_length,
top_p=request.top_p,
temperature=request.temperature)
torch_gc()
return chat_pb2.ChatReply(response=response)
def StreamingChat(self, request: chat_pb2.ChatRequest, context):
current_length = 0
for response, history in model.stream_chat(
tokenizer,
request.prompt,
history=messages_to_tuple_history(request.history),
max_length=request.max_length,
top_p=request.top_p,
temperature=request.temperature,
return_past_key_values=False):
print(response[current_length:], end="", flush=True)
yield chat_pb2.ChatReply(response=response)
current_length = len(response)
torch_gc()
对了,每次对话完成记得回收显存
def torch_gc():
if torch.cuda.is_available():
with torch.cuda.device(CUDA_DEVICE):
torch.cuda.empty_cache()
torch.cuda.ipc_collect()
这样就搞定了。
PS: Python 日志组件可以用 loguru ,很好用,我最近刚发现的。
小结
gRPC 方式调用开发起来还是有点麻烦的,主要是调试比较麻烦,我正在考虑是否改成统一 OpenAI 接口方式的调用,GitHub 上有人贡献了 ChatGLM 的 OpenAI 兼容接口,后续可以看看。
不过在视觉这块,还是得继续搞 gRPC ,传输效率比较好。大模型可以使用 HTTP 的 EventSource 是因为数据量比较小,次要原因是对话是单向的,即:用户向模型提问,模型不会主动向用户发送信息。
LLM探索:为ChatGLM2的gRPC后端增加连续对话功能的更多相关文章
- Java代码生成器多表配置优化,增加自定义实体功能
目录 前言 多表配置优化 自定义实体 杂谈 结语 前言 最近利用零碎的时间对代码生成器做了进一步更新:优化多表配置模块,增加自定义实体功能,美化单表和多表配置的UI界面,修复用户反馈的若干bug, ...
- .NET跨平台之旅:增加文件日志功能遇到的挫折
在将我们的ASP.NET 5示例站点(about.cnblogs.com)升级至ASP.NET 5 RC1的时候,我们增加了控制台日志功能. 在ASP.NET 5添加日志功能很简单,只需在projec ...
- mysql下一个版本应该且实现并不复杂增加的常用功能
1.innodb的auto_increment应该在参考oracle的实现方式,定期持久化: 我们目前遇到个问题,出于性能考虑,我们每天会把当天处理完成的数据归到另外一张历史表,并清空,同时有可能会重 ...
- 仿酷狗音乐播放器开发日志二十七 用ole为窗体增加文件拖动功能(附源码)
转载请说明原出处,谢谢~~ 中秋到了,出去玩了几天.今天把仿酷狗程序做了收尾,已经开发完成了,下一篇博客把完结的情况说一下.在这篇博客里说一下使用OLE为窗体增加文件拖拽的功能.使用播放器,我更喜欢直 ...
- discuz3.2x增加邮箱验证功能
为防止垃圾用户多次注册,为disczu增加邮箱验证功能. 大致分为二步: 1.申请邮箱,这里推荐使用腾讯免费企业邮箱:https://exmail.qq.com/portal/introducefre ...
- Nginx 增加 Image 缩略图 功能
Nginx 增加 Image 缩略图功能,需要使用Nginx Image 缩略图 模块 官网地址:https://github.com/3078825/ngx_image_th ...
- BAT-给文件右击菜单增加7-ZIP浏览功能
Reg给文件右击菜单增加7-ZIP浏览功能 Windows Registry Editor Version 5.00 [HKEY_CLASSES_ROOT\*\shell\用7-ZIP浏览(ZJQ)] ...
- django 增加验证邮箱功能
在user文件夹下新建python包,utils 在包内新建文件email_send.py,其中包括验证字符串随机码的产生,数据库的存储和email的发送 # -*- coding: utf-8 -* ...
- 怎么WordPress增加在线投稿功能
现在很多个人博客为了增加博客的内容,都会提供投稿通道,大部分都是以邮箱的形式进行投稿,不过这样一来,也很费人力,要拷贝复制,然后编辑等.如果给博客加个在线投稿功能,那就方便多了.稍微审核下文章内容就可 ...
- 【转】mysql利用init-connect增加访问审计功能
mysql的连接首先都是要通过init-connect初始化,然后连接到实例. 我们利用这一点,通过在init-connect的时候记录下用户的thread_id,用户名和用户地址实现db的访问审计功 ...
随机推荐
- asp登录认证,记录最后一次登录时间,写入数据库代码
最近开发了一个船员招聘网的程序,但是由于部分功能需要配合ASP代码才能使用,所以就发现以前写的这个asp登录认证代码,今天就将他公布一下. <!--#include file="con ...
- Android BottomNavigation底部导航栏使用
原文地址: Android BottomNavigation底部导航栏使用 - Stars-One的杂货小窝 基本使用 本文侧重点记录一些特殊的样式设置,所以基本使用这里就简单概述一下,详细图文可以去 ...
- Flutter ListView 不满屏 无法滚动
场景: flutter ListView 在做上拉加载的时候,未list添加controller后,数据不满屏,无法滚动 解决: 未listview 设置: physics: const Always ...
- 【Spring boot】 @Value注解
一.不通过配置文件的注入属性 1.1 注入普通字符串 直接附在属性名上,在 Bean 初始化时,会赋初始值 @Value("normal") private String norm ...
- BUUCTF-MISC-九连环(steghide隐写+伪加密)
开局一张图 丢入winhex里,尾部有其他文件名 kali中binwalk -e x.jpg 得到的zip中 图片打不开 丢入winhex,发现jpg处是伪加密 修改之后,解压可打开图片,另一个压缩包 ...
- Spring Data MongoDB 使用
本文为博主原创,转载请注明出处: Spring Data MongoDB 是 Spring Data 系列的一部分,它提供了与 MongoDB 的集成和操作支持,类似于 JPA 对关系型数据库的支持. ...
- 策略模式+Spring配置类优化多if..else思路
图示 1. 现状 场景: 假设设备上报不同类型的消息,我们要对不同类型的消息做不同的处理.如果我们通过if..else的方式处理的话会显得比较冗余. 例如: if("alarmEvent&q ...
- SwiftUI的认识与使用
SwiftUI简介 SwiftUI是苹果推出的一个新的UI框架,它使用了声明的方式,通过视图,基础控件和布局控件来进行页面的开发. SwiftUI具有跨平台性,一份SwiftUI代码可以同时跑在i ...
- asp.net core之HttpClient
本文介绍了ASP.NET Core中的HttpClient和HttpClientFactory的作用.用法以及最佳实践.通过示例代码的展示,读者可以了解如何使用HttpClient发送HTTP请求并处 ...
- 原来ES7~12分别增加了这些属性呀
ES6也称为ES2015,于2015年发布,此后每年都有新增一些属性,分别命名为ES7~12,发布的年份分别对应2016年到2021年 ES7 includes方法 数组中新增了includes方法, ...