Skip to content

Commit

Permalink
add bot engine
Browse files Browse the repository at this point in the history
  • Loading branch information
yuwnloyblog committed Dec 18, 2024
1 parent b6e25bc commit 8cfeb05
Show file tree
Hide file tree
Showing 14 changed files with 283 additions and 187 deletions.
15 changes: 15 additions & 0 deletions docs/jim.sql
Original file line number Diff line number Diff line change
Expand Up @@ -848,4 +848,19 @@ CREATE TABLE `grpapplications` (
KEY `idx_recipient` (`app_key`,`apply_type`,`recipient_id`,`apply_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT '群申请表';

DROP TABLE IF EXISTS `botconfs`;
CREATE TABLE `botconfs` (
`id` int NOT NULL AUTO_INCREMENT,
`bot_id` varchar(32) NULL,
`nickname` varchar(50) DEFAULT NULL,
`bot_portrait` varchar(200) DEFAULT NULL,
`bot_type` tinyint DEFAULT '0',
`bot_conf` varchar(2000) NULL,
`updated_time` datetime(3) DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
`created_time` datetime(3) DEFAULT CURRENT_TIMESTAMP(3),
`app_key` varchar(20) NULL,
PRIMARY KEY (`id`),
UNIQUE INDEX `uniq_botid` (`app_key`, `bot_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

INSERT IGNORE INTO `accounts`(`account`,`password`)VALUES('admin','7c4a8d09ca3762af61e59520943dc26494f8941b');
10 changes: 9 additions & 1 deletion services/appbusiness/apis/group.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,18 @@ func CreateGroup(ctx *httputils.HttpContext) {
ctx.ResponseErr(errs.IMErrorCode_APP_REQ_BODY_ILLEGAL)
return
}
memberIds := req.MemberIds
if len(memberIds) <= 0 && len(req.GrpMembers) > 0 {
ids := []string{}
for _, member := range req.GrpMembers {
ids = append(ids, member.UserId)
}
memberIds = ids
}
code, grpInfo := services.CreateGroup(ctx.ToRpcCtx(ctx.CurrentUserId), &pbobjs.GroupMembersReq{
GroupName: req.GroupName,
GroupPortrait: req.GroupPortrait,
MemberIds: req.MemberIds,
MemberIds: memberIds,
})
if code != errs.IMErrorCode_SUCCESS {
ctx.ResponseErr(code)
Expand Down
5 changes: 4 additions & 1 deletion services/appbusiness/services/friendservice.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,10 @@ func ApplyFriend(ctx context.Context, req *pbobjs.ApplyFriend) errs.IMErrorCode
userId := bases.GetRequesterIdFromCtx(ctx)
//check friend relation
if checkFriend(ctx, req.FriendId, userId) {
return errs.IMErrorCode_APP_FRIEND_APPLY_REPEATED
AppSyncRpcCall(ctx, "add_friends", userId, userId, &pbobjs.FriendIdsReq{
FriendIds: []string{req.FriendId},
}, nil)
return errs.IMErrorCode_SUCCESS
}
friendUserInfo := commonservices.GetTargetUserInfo(ctx, req.FriendId)
friendSettings := GetUserSettings(friendUserInfo)
Expand Down
14 changes: 14 additions & 0 deletions services/botmsg/services/botengines/botengine.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package botengines

import "context"

var DefaultBotEngine IBotEngine = &NilBotEngine{}

type IBotEngine interface {
StreamChat(ctx context.Context, senderId, converId string, question string, f func(answerPart string, isEnd bool))
}

type NilBotEngine struct{}

func (engine *NilBotEngine) StreamChat(ctx context.Context, senderId, converId string, question string, f func(answerPart string, isEnd bool)) {
}
90 changes: 90 additions & 0 deletions services/botmsg/services/botengines/difyengine.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package botengines

import (
"context"
"fmt"
"im-server/commons/tools"
"im-server/services/commonservices/logs"
"net/http"
)

type DifyBotEngine struct {
ApiKey string `json:"api_key"`
Url string `json:"url"`
}

func (engine *DifyBotEngine) StreamChat(ctx context.Context, senderId, converId string, question string, f func(answerPart string, isEnd bool)) {
req := &DifyChatMsgReq{
Inputs: map[string]string{},
Query: question,
ResponseMode: "streaming",
ConversationId: converId,
User: senderId,
}
body := tools.ToJson(req)
headers := map[string]string{}
headers["Authorization"] = fmt.Sprintf("Bearer %s", engine.ApiKey)
headers["Content-Type"] = "application/json"
stream, code, err := tools.CreateStream(http.MethodPost, engine.Url, headers, body)
if err != nil || code != http.StatusOK {
logs.WithContext(ctx).Errorf("call dify api failed. http_code:%d,err:%v", code, err)
return
}
for {
line, err := stream.Receive()
if err != nil {
f("", true)
return
}
item := DifyStreamRespItem{}
err = tools.JsonUnMarshal([]byte(line), &item)
if err != nil {
f("", true)
return
}
if item.Event == "message" {
f(item.Answer, false)
} else if item.Event == "message_end" {
f(item.Answer, true)
return
}
}
}

type DifyChatMsgReq struct {
Inputs map[string]string `json:"inputs"`
Query string `json:"query"`
ResponseMode string `json:"response_mode"`
ConversationId string `json:"conversation_id"`
User string `json:"user"`
}

type DifyStreamRespItem struct {
Event string `json:"event"`
ConversationId string `json:"conversation_id"`
MessageId string `json:"message_id"`
CreatedAt int64 `json:"created_at"`
TaskId string `json:"task_id"`
Id string `json:"id"`
Answer string `json:"answer"`

Audio string `json:"audio"`
}

type DifyMetaData struct {
Usage *DifyUsage `json:"usage"`
}

type DifyUsage struct {
PromptTokens int32 `json:"prompt_tokens"`
PromptUnitPrice string `json:"prompt_price_unit"`
PromptPrice string `json:"prompt_price"`
CompletionTokens int32 `json:"completion_tokens"`
CompletionUnitPrice string `json:"completion_unit_price"`
CompletionPriceUnit string `json:"completion_price_unit"`
CompletionPrice string `json:"completion_price"`
TotalTokens int32 `json:"total_tokens"`
TotalPrice string `json:"total_price"`
Currency string `json:"currency"`
Latency float64 `json:"latency"`
}
82 changes: 54 additions & 28 deletions services/botmsg/services/botservice.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package services

import (
"bytes"
"context"
"im-server/commons/bases"
"im-server/commons/caches"
"im-server/commons/pbdefines/pbobjs"
"im-server/commons/tools"
"im-server/services/botmsg/services/botengines"
"im-server/services/botmsg/storages"
"im-server/services/botmsg/storages/models"
"im-server/services/commonservices"
"im-server/services/commonservices/logs"
"strings"
Expand All @@ -21,13 +25,12 @@ func init() {
}

type BotInfo struct {
AppKey string
BotId string
Nickname string
Portrait string
ExtFields []*pbobjs.KvItem
Webhook string
BotType string
APIKey string
BotType models.BotType
BotEngine botengines.IBotEngine
}

func GetBotInfo(ctx context.Context, botId string) *BotInfo {
Expand All @@ -42,24 +45,27 @@ func GetBotInfo(ctx context.Context, botId string) *BotInfo {
if val, exist := botCache.Get(key); exist {
return val.(*BotInfo)
} else {
bInfo := commonservices.GetUserInfoFromRpcWithAttTypes(ctx, botId, []int32{int32(commonservices.AttItemType_Setting)})
botInfo := &BotInfo{
BotId: bInfo.UserId,
Nickname: bInfo.Nickname,
Portrait: bInfo.UserPortrait,
ExtFields: bInfo.ExtFields,
AppKey: appkey,
BotId: botId,
BotEngine: &botengines.NilBotEngine{},
}
if len(bInfo.Settings) > 0 {
settingMap := commonservices.Kvitems2Map(bInfo.Settings)
if webhook, exist := settingMap[string(commonservices.AttItemKey_Bot_WebHook)]; exist && webhook != "" {
botInfo.Webhook = webhook
}
if apiKey, exist := settingMap[string(commonservices.AttItemKey_Bot_ApiKey)]; exist && apiKey != "" {
botInfo.APIKey = apiKey
}
if botType, exist := settingMap[string(commonservices.AttItemKey_Bot_Type)]; exist && botType != "" {
botInfo.BotType = botType
storage := storages.NewBotConfStorage()
bot, err := storage.FindById(appkey, botId)
if err == nil {
botInfo.Nickname = bot.Nickname
botInfo.Portrait = bot.BotPortrait
botInfo.BotType = bot.BotType
switch botInfo.BotType {
case models.BotType_Dify:
difyBot := &botengines.DifyBotEngine{}
err = tools.JsonUnMarshal([]byte(bot.BotConf), difyBot)
if err == nil && difyBot.ApiKey != "" && difyBot.Url != "" {
botInfo.BotEngine = difyBot
}
}
} else {
botInfo.BotEngine = &botengines.NilBotEngine{}
}
botCache.Add(key, botInfo)
return botInfo
Expand All @@ -72,17 +78,37 @@ func getKey(appkey, botId string) string {
}

func HandleBotMsg(ctx context.Context, msg *pbobjs.DownMsg) {
if msg.MsgType != "jg:text" {
return
}
txtMsg := &commonservices.TextMsg{}
err := tools.JsonUnMarshal(msg.MsgContent, txtMsg)
if err != nil {
logs.WithContext(ctx).Errorf("text msg illigal. content:%s", string(msg.MsgContent))
return
}
botId := bases.GetTargetIdFromCtx(ctx)
botInfo := GetBotInfo(ctx, botId)
if botInfo.BotType == "dify" {
if botInfo.Webhook == "" || botInfo.APIKey == "" {
logs.WithContext(ctx).Infof("no webhook/apikey")
return
if botInfo.BotEngine != nil {
converId := ""
buf := bytes.NewBuffer([]byte{})
botInfo.BotEngine.StreamChat(ctx, msg.SenderId, converId, txtMsg.Content, func(answerPart string, isEnd bool) {
if !isEnd {
buf.WriteString(answerPart)
}
})
answer := buf.String()
if answer != "" {
answerMsg := &commonservices.TextMsg{
Content: answer,
}
flag := commonservices.SetStoreMsg(0)
flag = commonservices.SetCountMsg(flag)
commonservices.AsyncPrivateMsgOverUpstream(ctx, botId, msg.SenderId, &pbobjs.UpMsg{
MsgType: "jg:text",
MsgContent: []byte(tools.ToJson(answerMsg)),
Flags: flag,
})
}
//https://api.dify.ai/v1/chat-messages
//app-UD0yqEwQykpxA8hMbtzP0ktz
Chat2Dify(ctx, botId, msg, botInfo.Webhook, botInfo.APIKey)
} else {
SyncMsg2Bot(ctx, botId, msg)
}
}
116 changes: 0 additions & 116 deletions services/botmsg/services/difybotservice.go

This file was deleted.

Loading

0 comments on commit 8cfeb05

Please sign in to comment.