347 lines
11 KiB
Go
347 lines
11 KiB
Go
package biz
|
||
|
||
import (
|
||
"ai_scheduler/internal/biz/do"
|
||
"ai_scheduler/internal/biz/handle/dingtalk"
|
||
"ai_scheduler/internal/biz/tools_regis"
|
||
"ai_scheduler/internal/data/constants"
|
||
"ai_scheduler/internal/data/impl"
|
||
"ai_scheduler/internal/data/model"
|
||
"ai_scheduler/internal/entitys"
|
||
"ai_scheduler/internal/tools"
|
||
|
||
"context"
|
||
"database/sql"
|
||
"encoding/json"
|
||
"errors"
|
||
"fmt"
|
||
"strings"
|
||
|
||
"github.com/gofiber/fiber/v2/log"
|
||
"github.com/open-dingtalk/dingtalk-stream-sdk-go/chatbot"
|
||
|
||
"xorm.io/builder"
|
||
)
|
||
|
||
// AiRouterBiz 智能路由服务
|
||
type DingTalkBotBiz struct {
|
||
do *do.Do
|
||
handle *do.Handle
|
||
botConfigImpl *impl.BotConfigImpl
|
||
replier *chatbot.ChatbotReplier
|
||
log log.Logger
|
||
dingTalkUser *dingtalk.User
|
||
botTools []model.AiBotTool
|
||
botGroupImpl *impl.BotGroupImpl
|
||
toolManager *tools.Manager
|
||
}
|
||
|
||
// NewDingTalkBotBiz
|
||
func NewDingTalkBotBiz(
|
||
do *do.Do,
|
||
handle *do.Handle,
|
||
botConfigImpl *impl.BotConfigImpl,
|
||
botGroupImpl *impl.BotGroupImpl,
|
||
dingTalkUser *dingtalk.User,
|
||
tools *tools_regis.ToolRegis,
|
||
toolManager *tools.Manager,
|
||
) *DingTalkBotBiz {
|
||
return &DingTalkBotBiz{
|
||
do: do,
|
||
handle: handle,
|
||
botConfigImpl: botConfigImpl,
|
||
replier: chatbot.NewChatbotReplier(),
|
||
dingTalkUser: dingTalkUser,
|
||
botTools: tools.BootTools,
|
||
botGroupImpl: botGroupImpl,
|
||
toolManager: toolManager,
|
||
}
|
||
}
|
||
|
||
func (d *DingTalkBotBiz) GetDingTalkBotCfgList() (dingBotList []entitys.DingTalkBot, err error) {
|
||
botConfig := make([]model.AiBotConfig, 0)
|
||
|
||
cond := builder.NewCond()
|
||
cond = cond.And(builder.Eq{"status": constants.Enable})
|
||
cond = cond.And(builder.Eq{"bot_type": constants.BotTypeDingTalk})
|
||
err = d.botConfigImpl.GetRangeToMapStruct(&cond, &botConfig)
|
||
for _, v := range botConfig {
|
||
var config entitys.DingTalkBot
|
||
err = json.Unmarshal([]byte(v.BotConfig), &config)
|
||
if err != nil {
|
||
d.log.Info("初始化“%s”失败:%s", v.BotName, err.Error())
|
||
}
|
||
config.BotIndex = v.BotIndex
|
||
dingBotList = append(dingBotList, config)
|
||
}
|
||
return
|
||
|
||
}
|
||
|
||
func (d *DingTalkBotBiz) InitRequire(ctx context.Context, data *chatbot.BotCallbackDataModel) (requireData *entitys.RequireDataDingTalkBot, err error) {
|
||
requireData = &entitys.RequireDataDingTalkBot{
|
||
Req: data,
|
||
Ch: make(chan entitys.Response, 2),
|
||
}
|
||
|
||
return
|
||
}
|
||
|
||
func (d *DingTalkBotBiz) Do(ctx context.Context, requireData *entitys.RequireDataDingTalkBot) (err error) {
|
||
entitys.ResText(requireData.Ch, "", "收到消息,正在处理中,请稍等")
|
||
defer close(requireData.Ch)
|
||
switch constants.ConversationType(requireData.Req.ConversationType) {
|
||
case constants.ConversationTypeSingle:
|
||
err = d.handleSingleChat(ctx, requireData)
|
||
case constants.ConversationTypeGroup:
|
||
err = d.handleGroupChat(ctx, requireData)
|
||
default:
|
||
err = errors.New("未知的聊天类型:" + requireData.Req.ConversationType)
|
||
}
|
||
return
|
||
}
|
||
|
||
func (d *DingTalkBotBiz) handleSingleChat(ctx context.Context, requireData *entitys.RequireDataDingTalkBot) (err error) {
|
||
entitys.ResLog(requireData.Ch, "", "个人聊天暂未开启,请期待后续更新")
|
||
return
|
||
//requireData.UserInfo, err = d.dingTalkUser.GetUserInfoFromBot(ctx, requireData.Req.SenderStaffId, dingtalk.WithId(1))
|
||
//if err != nil {
|
||
// return
|
||
//}
|
||
////如果不是管理或者不是老板,则进行权限判断
|
||
//if requireData.UserInfo.IsSenior == constants.IsSeniorFalse && requireData.UserInfo.IsBoss == constants.IsBossFalse {
|
||
//
|
||
//}
|
||
//return
|
||
}
|
||
|
||
func (d *DingTalkBotBiz) handleGroupChat(ctx context.Context, requireData *entitys.RequireDataDingTalkBot) (err error) {
|
||
group, err := d.initGroup(ctx, requireData.Req.ConversationId, requireData.Req.ConversationTitle)
|
||
if err != nil {
|
||
return
|
||
}
|
||
groupTools, err := d.getGroupTools(ctx, group)
|
||
if err != nil {
|
||
return
|
||
}
|
||
rec, err := d.recognize(ctx, requireData, groupTools)
|
||
if err != nil {
|
||
return
|
||
}
|
||
|
||
return d.handleMatch(ctx, rec)
|
||
}
|
||
|
||
func (d *DingTalkBotBiz) initGroup(ctx context.Context, conversationId string, conversationTitle string) (group *model.AiBotGroup, err error) {
|
||
group, err = d.botGroupImpl.GetByConversationId(conversationId)
|
||
if err != nil {
|
||
if !errors.Is(err, sql.ErrNoRows) {
|
||
|
||
return
|
||
}
|
||
}
|
||
|
||
if group.GroupID == 0 {
|
||
group = &model.AiBotGroup{
|
||
ConversationID: conversationId,
|
||
Title: conversationTitle,
|
||
ToolList: "",
|
||
}
|
||
//如果不存在则创建
|
||
d.botGroupImpl.Add(group)
|
||
}
|
||
return
|
||
}
|
||
|
||
func (d *DingTalkBotBiz) getGroupTools(ctx context.Context, group *model.AiBotGroup) (tools []model.AiBotTool, err error) {
|
||
if len(d.botTools) == 0 {
|
||
return
|
||
}
|
||
var (
|
||
groupRegisTools map[string]struct{}
|
||
)
|
||
if group.ToolList != "" {
|
||
groupList := strings.Split(group.ToolList, ",")
|
||
for _, tool := range groupList {
|
||
groupRegisTools[tool] = struct{}{}
|
||
}
|
||
}
|
||
|
||
for _, v := range d.botTools {
|
||
if v.PermissionType == constants.PermissionTypeNone {
|
||
tools = append(tools, v)
|
||
continue
|
||
}
|
||
if _, ex := groupRegisTools[v.Index]; ex {
|
||
tools = append(tools, v)
|
||
}
|
||
}
|
||
return
|
||
}
|
||
func (d *DingTalkBotBiz) recognize(ctx context.Context, requireData *entitys.RequireDataDingTalkBot, tools []model.AiBotTool) (rec *entitys.Recognize, err error) {
|
||
|
||
userContent, err := d.getUserContent(requireData.Req.Msgtype, requireData.Req.Text.Content)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
rec = &entitys.Recognize{
|
||
Ch: requireData.Ch,
|
||
SystemPrompt: d.defaultPrompt(),
|
||
UserContent: userContent,
|
||
}
|
||
if len(tools) > 0 {
|
||
rec.Tasks = make([]entitys.RegistrationTask, 0, len(tools))
|
||
for _, task := range tools {
|
||
taskConfig := entitys.TaskConfigDetail{}
|
||
if err = json.Unmarshal([]byte(task.Config), &taskConfig); err != nil {
|
||
log.Errorf("解析任务配置失败: %s, 任务ID: %s", err.Error(), task.Index)
|
||
continue // 解析失败时跳过该任务,而不是直接返回错误
|
||
}
|
||
|
||
rec.Tasks = append(rec.Tasks, entitys.RegistrationTask{
|
||
Name: task.Index,
|
||
Desc: task.Desc,
|
||
TaskConfigDetail: taskConfig, // 直接使用解析后的配置,避免重复构建
|
||
})
|
||
}
|
||
}
|
||
err = d.handle.Recognize(ctx, rec, &do.WithDingTalkBot{})
|
||
return
|
||
}
|
||
|
||
func (d *DingTalkBotBiz) getUserContent(msgType string, msgContent interface{}) (content *entitys.RecognizeUserContent, err error) {
|
||
switch constants.BotMsgType(msgType) {
|
||
case constants.BotMsgTypeText:
|
||
content = &entitys.RecognizeUserContent{
|
||
Text: msgContent.(string),
|
||
}
|
||
default:
|
||
return nil, errors.New("未知的消息类型:" + msgType)
|
||
}
|
||
return
|
||
}
|
||
|
||
func (d *DingTalkBotBiz) defaultPrompt() string {
|
||
|
||
return `{"system":"智能路由系统,精准解析用户意图并路由至任务模块,遵循以下规则:","rule":{"返回格式":"{\\"index\\":\\"工具索引\\",\\"confidence\\":\\"0.0-1.0\\",\\"reasoning\\":\\"判断理由\\",\\"parameters\\":\\"转义JSON参数\\",\\"is_match\\":true|false,\\"chat\\":\\"追问内容\\"}","工具匹配":["用工具parameters匹配,区分必选(required)和可选(optional)参数","无法匹配时,is_match=false,chat提醒用户适用工具(例:'请问您要查询订单还是商品?')"],"参数提取":["从用户输入提取parameters中明确提及的参数","必须参数仅用用户直接提及的,缺失时is_match=false,chat提醒补充(例:'需补充XX信息')"],"格式要求":["所有字段值为字符串(含confidence)","parameters为转义JSON字符串(如\\"{\\\\"key\\\\":\\\\"value\\\\"}\\")"]}}`
|
||
}
|
||
|
||
func (d *DingTalkBotBiz) handleMatch(ctx context.Context, rec *entitys.Recognize) (err error) {
|
||
|
||
if !rec.Match.IsMatch {
|
||
if len(rec.Match.Chat) != 0 {
|
||
entitys.ResText(rec.Ch, "", rec.Match.Chat)
|
||
} else {
|
||
entitys.ResText(rec.Ch, "", rec.Match.Reasoning)
|
||
}
|
||
return
|
||
}
|
||
var pointTask *model.AiBotTool
|
||
for _, task := range d.botTools {
|
||
if task.Index == rec.Match.Index {
|
||
pointTask = &task
|
||
|
||
break
|
||
}
|
||
}
|
||
|
||
if pointTask == nil || pointTask.Index == "other" {
|
||
return d.otherTask(ctx, rec)
|
||
}
|
||
switch constants.TaskType(pointTask.Type) {
|
||
//case constants.TaskTypeApi:
|
||
//return d.handleApiTask(ctx, requireData, pointTask)
|
||
case constants.TaskTypeFunc:
|
||
return d.handleTask(ctx, rec, pointTask)
|
||
default:
|
||
return d.otherTask(ctx, rec)
|
||
}
|
||
return
|
||
}
|
||
|
||
func (d *DingTalkBotBiz) handleTask(ctx context.Context, rec *entitys.Recognize, task *model.AiBotTool) (err error) {
|
||
var configData entitys.ConfigDataTool
|
||
err = json.Unmarshal([]byte(task.Config), &configData)
|
||
if err != nil {
|
||
return
|
||
}
|
||
|
||
err = d.toolManager.ExecuteTool(ctx, configData.Tool, rec)
|
||
if err != nil {
|
||
return
|
||
}
|
||
|
||
return
|
||
}
|
||
|
||
func (d *DingTalkBotBiz) otherTask(ctx context.Context, rec *entitys.Recognize) (err error) {
|
||
entitys.ResText(rec.Ch, "", rec.Match.Reasoning)
|
||
return
|
||
}
|
||
func (d *DingTalkBotBiz) HandleRes(ctx context.Context, data *chatbot.BotCallbackDataModel, resp entitys.Response) error {
|
||
switch resp.Type {
|
||
case entitys.ResponseText:
|
||
return d.replyText(ctx, data.SessionWebhook, resp.Content)
|
||
case entitys.ResponseStream:
|
||
return d.replySteam(ctx, data.SessionWebhook, resp.Content)
|
||
case entitys.ResponseImg:
|
||
return d.replyImg(ctx, data.SessionWebhook, resp.Content)
|
||
case entitys.ResponseFile:
|
||
return d.replyFile(ctx, data.SessionWebhook, resp.Content)
|
||
case entitys.ResponseMarkdown:
|
||
return d.replyMarkdown(ctx, data.SessionWebhook, resp.Content)
|
||
case entitys.ResponseActionCard:
|
||
return d.replyActionCard(ctx, data.SessionWebhook, resp.Content)
|
||
default:
|
||
return nil
|
||
}
|
||
}
|
||
|
||
func (d *DingTalkBotBiz) replySteam(ctx context.Context, SessionWebhook string, content string, arg ...string) error {
|
||
msg := content
|
||
if len(arg) > 0 {
|
||
msg = fmt.Sprintf(content, arg)
|
||
}
|
||
return d.replier.SimpleReplyText(ctx, SessionWebhook, []byte(msg))
|
||
}
|
||
|
||
func (d *DingTalkBotBiz) replyText(ctx context.Context, SessionWebhook string, content string, arg ...string) error {
|
||
msg := content
|
||
if len(arg) > 0 {
|
||
msg = fmt.Sprintf(content, arg)
|
||
}
|
||
return d.replier.SimpleReplyText(ctx, SessionWebhook, []byte(msg))
|
||
}
|
||
|
||
func (d *DingTalkBotBiz) replyImg(ctx context.Context, SessionWebhook string, content string, arg ...string) error {
|
||
msg := content
|
||
if len(arg) > 0 {
|
||
msg = fmt.Sprintf(content, arg)
|
||
}
|
||
return d.replier.SimpleReplyText(ctx, SessionWebhook, []byte(msg))
|
||
}
|
||
|
||
func (d *DingTalkBotBiz) replyFile(ctx context.Context, SessionWebhook string, content string, arg ...string) error {
|
||
msg := content
|
||
if len(arg) > 0 {
|
||
msg = fmt.Sprintf(content, arg)
|
||
}
|
||
return d.replier.SimpleReplyText(ctx, SessionWebhook, []byte(msg))
|
||
}
|
||
|
||
func (d *DingTalkBotBiz) replyMarkdown(ctx context.Context, SessionWebhook string, content string, arg ...string) error {
|
||
msg := content
|
||
if len(arg) > 0 {
|
||
msg = fmt.Sprintf(content, arg)
|
||
}
|
||
return d.replier.SimpleReplyText(ctx, SessionWebhook, []byte(msg))
|
||
}
|
||
|
||
func (d *DingTalkBotBiz) replyActionCard(ctx context.Context, SessionWebhook string, content string, arg ...string) error {
|
||
msg := content
|
||
if len(arg) > 0 {
|
||
msg = fmt.Sprintf(content, arg)
|
||
}
|
||
return d.replier.SimpleReplyText(ctx, SessionWebhook, []byte(msg))
|
||
}
|