实践 | 即时通信IM如何接入AI服务,搭建聊天机器人
随着ChatGPT在全球范围的爆火,AI已成为当下开发者最为关注的焦点,国内各大厂商也纷纷跟进,推出了各自的大模型应用与产品。很多应用都在尝试与AI结合,寻找新的发力点。而新一代大语言模型的强大对话交流能力与各类即时通信场景天然契合,这为IM与AI结合带来了广阔的想象空间。
那即时通信IM该如何接入AI服务呢?本文将拆解接入AI服务的各个步骤,为您详细介绍如何通过腾讯云即时通信IM第三方回调功能,将AI服务能力引入到IM应用中,创建一个可以智能聊天的AI机器人,为用户提供真人般对话体验,实现智能客服、创意辅助、工作助手等功能。(文中的实践步骤以接入MiniMax中文大语言模型为例,类ChatGPT服务均可通过文中介绍的方法实现接入)
准备工作
注册腾讯云账号
注册并登录腾讯云账号,进入即时通信IM控制台,创建应用,并获取应用的SDKAppID和密钥(以下称为 IM Key),并创建一个管理员账号administrator。
注册对应AI服务商账号
注册并登录计划接入的AI服务商的相应账号并获取API密钥(以下称为AI_SECRET_KEY)。
创建腾讯云IM机器人账号
通过REST API创建一个腾讯云IM机器人账号。腾讯云机器人是一种特殊的用户,UserID以@RBT#开头。
curl -d '{"UserID":"@RBT#001","Nick":"MyRobot"}'
"https://console.tim.qq.com/v4/openim_robot_http_svc/create_robot?sdkappid= {}&identifier=administrator&usersig={}&random=123456789&contenttype=json"
将上述命令的sdkappid={}和usersig={}替换成您的SDKAppID和使用IM Key生成的Usersig。Usersig的生成可以参考官网文档(https://cloud.tencent.com/document/product/269/32688)。在Linux环境运行上述命令后,云服务器返回:
{"ActionStatus": "OK", "ErrorCode": 0, "ErrorInfo": ""}
表示成功创建了一个昵称为MyRobot的机器人@RBT#001。
配置腾讯云IM第三方回调
即时通信 IM 第三方回调即云IM后台会在某一事件发生之前或者之后,向 App 的后台服务器发送请求,App 后台可以据此进行必要的数据同步,或者干预事件的后续处理流程。我们将使用“机器人事件回调”监听用户发消息给机器人,或者在群聊中@机器人的事件,并对其做出反应。在腾讯云IM控制台中找到“机器人事件回调”,点击开启并保存。
编写APP后台服务
以单聊为例,总体上的工作流程如下:
- 用户user1发消息“hello”给机器人@RBT#001;
- 云IM后台发送第三方回调将事件通知App后台;
- App后台收到事件通知,通知内容包含发送方user1,接收方@RBT#001,消息内容hello以及其他信息;
- App后台调用AI服务接口(即MiniMax API),并得到响应回复内容,如nice to meet you;
- App后台调用云IM REST API接口(单聊为sendmsg接口,群聊为send_group_msg接口),将回复内容以@RBT#001的身份发送给user1。
以Golang为例,App后台的关键代码大概如下(请注意,本代码仅作为展示用途,省略了大量异常处理代码,不可直接用于生产环境):
分发处理回调命令
我们创建一个监听在80端口的http服务,注册一个url为/im的处理函数handler,所有发送给http://<your.domain.com>/im的请求都会被handler处理。所有云IM发送的回调请求都带有CallbackCommand参数,不同的值代表不同的回调命令。在handler中,根据云IM设置的参数CallbackCommand进行对应的处理。
func handler(w http.ResponseWriter, r *http.Request) {
command := r.URL.Query().Get("CallbackCommand")
reqbody, _ := io.ReadAll(r.Body)
var rspbody []byte
switch command {
case "Bot.OnC2CMessage": // 机器人C2C回调命令字
dealC2c(context.Background(), reqbody)
rspbody = []byte("{\"ActionStatus\": \"OK\", \"ErrorCode\": 0, \"ErrorInfo\": \"\"}")
default:
rspbody = []byte("invalid CallbackCommand.")
}
w.Write(rspbody)
}
func main() {
// 注册一个handler,处理发送给App后台的回调命令
http.HandleFunc("/im", handler)
http.ListenAndServe(":80", nil)
}
处理机器人接收到单聊消息事件
处理单聊消息时,我们先检查发送方是不是机器人(一般不会出现这种机器人发送消息给机器人的情况),以防止无限的回调循环。接着,我们解析消息体,拿到用户发送给机器人的消息内容text,将发送方UserID保存到context中以方便后续调用REST API回复,最后调用askAI请求AI服务。
func dealC2c(ctx context.Context, reqbody []byte) error {
root, _ := simplejson.NewJson(reqbody)
jFromAccount := root.Get("From_Account")
fromAccount, _ = jFromAccount.String()
// 检查发送方ID,不处理机器人发送给机器人的请求,防止无限循环
if strings.HasPrefix(fromAccount, "@RBT#") {
return nil
}
jToAccount := root.Get("To_Account")
toAccount, _ := jToAccount.String()
msgBodyList, _ := root.Get("MsgBody").Array()
for _, m := range msgBodyList {
msgBody, _ := m.(map[string]interface{})
msgType, _ := msgBody["MsgType"].(string)
if msgType != "TIMTextElem" {
continue
}
msgContent, _ := msgBody["MsgContent"].(map[string]interface{})
text, _ := msgContent["Text"].(string)
ctx = context.WithValue(ctx, "from", fromAccount)
ctx = context.WithValue(ctx, "to", toAccount)
go askAI(ctx, text)
}
return nil
}
调用AI服务接口
在这一步我们使用第三方AI服务公司MiniMax实现智能聊天的功能,您可以将MiniMax服务替换成任意的其它AI服务。需要注意的是这里演示的是简单的completion接口,没有保存对话的上下文,其他接口可按需查阅MiniMax文档。
type MiniMaxRsp struct {
Reply string `json:"reply"`
}
// 请求MiniMax并得到回复
func askAI(ctx context.Context, prompt string) {
url := "https://api.minimax.chat/v1/text/completion"
var reqData = []byte(`{
"model": "abab5-completion",
"prompt": prompt
}`)
request, _ := http.NewRequest("POST", url, bytes.NewBuffer(reqData))
request.Header.Set("Content-Type", "application/json; charset=UTF-8
request.Header.Set("Authorization", API_SECRET_KEY)
client := &http.Client{}
response, _ := client.Do(request)
defer response.Body.Close()
body, _ := ioutil.ReadAll(response.Body)
rsp := &MiniMaxRsp{}
json.Unmarshal(body, rsp)
reply(ctx, rsp.Reply) // 将AI回复的内容发送给用户
}
将AI返回的结果返回给用户
从AI服务得到回复之后,我们只需调用云IM的REST API接口sendmsg,制定消息发送方为@RBT#001,接收方为user1,模拟机器人回复用户。
// 发送一个REST API请求
func doRestAPI(host string, sdkappid int, admin, usersig, command, body string) {
url := fmt.Sprintf("https://%s/v4/%s?sdkappid=%d&identifier=%s&usersig=%s&random=%d&contenttype=json",
host, command, sdkappid, admin, usersig, rand.Uint32())
req, _ := http.NewRequest("POST", url, bytes.NewBufferString(body))
req.Header.Set("Content-Type", "application/json")
cli := &http.Client{}
rsp, err := cli.Do(req)
if err != nil {
log.Printf("REST API failed. %s", err.Error())
return
}
defer rsp.Body.Close()
rsptext, _ := io.ReadAll(rsp.Body)
log.Printf("rsp:%s", rsptext)
}
// 调用腾讯云IM的REST API,回复用户
func reply(ctx context.Context, text string) {
rsp := make(map[string]interface{})
msgbody := []map[string]interface{}{{
"MsgType": "TIMTextElem",
"MsgContent": map[string]interface{}{"Text": text},
}}
// GenUserSig 的实现可以参考腾讯云文档
usersig, _ := GenUserSig(IM_SDKAPPID, IM_KEY, "administrator", 60)
rsp["From_Account"] = ctx.Value("to").(string) //"@RBT#001"
rsp["To_Account"] = ctx.Value("from").(string)
rsp["SyncOtherMachine"] = 2
rsp["MsgLifeTime"] = 60 * 60 * 24 * 7
rsp["MsgSeq"] = rand.Uint32()
rsp["MsgRandom"] = rand.Uint32()
rsp["MsgBody"] = msgbody
rspbody, _ := json.Marshal(rsp)
doRestAPI("console.tim.qq.com", IM_SDKAPPID, "administrator"