diff --git a/internal/biz/ding_talk_bot.go b/internal/biz/ding_talk_bot.go index ca7431b..d799796 100644 --- a/internal/biz/ding_talk_bot.go +++ b/internal/biz/ding_talk_bot.go @@ -9,6 +9,7 @@ import ( "ai_scheduler/internal/data/model" "ai_scheduler/internal/entitys" "ai_scheduler/internal/tools" + "strconv" "context" "database/sql" @@ -34,6 +35,7 @@ type DingTalkBotBiz struct { botTools []model.AiBotTool botGroupImpl *impl.BotGroupImpl toolManager *tools.Manager + chatHis *impl.BotChatHisImpl } // NewDingTalkBotBiz @@ -44,6 +46,7 @@ func NewDingTalkBotBiz( botGroupImpl *impl.BotGroupImpl, dingTalkUser *dingtalk.User, tools *tools_regis.ToolRegis, + chatHis *impl.BotChatHisImpl, toolManager *tools.Manager, ) *DingTalkBotBiz { return &DingTalkBotBiz{ @@ -55,6 +58,7 @@ func NewDingTalkBotBiz( botTools: tools.BootTools, botGroupImpl: botGroupImpl, toolManager: toolManager, + chatHis: chatHis, } } @@ -88,7 +92,7 @@ func (d *DingTalkBotBiz) InitRequire(ctx context.Context, data *chatbot.BotCallb } func (d *DingTalkBotBiz) Do(ctx context.Context, requireData *entitys.RequireDataDingTalkBot) (err error) { - entitys.ResText(requireData.Ch, "", "收到消息,正在处理中,请稍等") + entitys.ResLoading(requireData.Ch, "", "收到消息,正在处理中,请稍等") defer close(requireData.Ch) switch constants.ConversationType(requireData.Req.ConversationType) { case constants.ConversationTypeSingle: @@ -98,6 +102,9 @@ func (d *DingTalkBotBiz) Do(ctx context.Context, requireData *entitys.RequireDat default: err = errors.New("未知的聊天类型:" + requireData.Req.ConversationType) } + if err != nil { + entitys.ResText(requireData.Ch, "", err.Error()) + } return } @@ -108,6 +115,7 @@ func (d *DingTalkBotBiz) handleSingleChat(ctx context.Context, requireData *enti //if err != nil { // return //} + //requireData.ID=requireData.UserInfo.UserID ////如果不是管理或者不是老板,则进行权限判断 //if requireData.UserInfo.IsSenior == constants.IsSeniorFalse && requireData.UserInfo.IsBoss == constants.IsBossFalse { // @@ -117,9 +125,11 @@ func (d *DingTalkBotBiz) handleSingleChat(ctx context.Context, requireData *enti 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 } + requireData.ID = group.GroupID groupTools, err := d.getGroupTools(ctx, group) if err != nil { return @@ -158,12 +168,19 @@ func (d *DingTalkBotBiz) getGroupTools(ctx context.Context, group *model.AiBotGr return } var ( - groupRegisTools map[string]struct{} + groupRegisTools = make(map[int]struct{}) ) if group.ToolList != "" { - groupList := strings.Split(group.ToolList, ",") - for _, tool := range groupList { - groupRegisTools[tool] = struct{}{} + groupToolList := strings.Split(group.ToolList, ",") + for _, tool := range groupToolList { + if tool == "" { + continue + } + num, _err := strconv.Atoi(tool) + if _err != nil { + continue + } + groupRegisTools[num] = struct{}{} } } @@ -172,7 +189,7 @@ func (d *DingTalkBotBiz) getGroupTools(ctx context.Context, group *model.AiBotGr tools = append(tools, v) continue } - if _, ex := groupRegisTools[v.Index]; ex { + if _, ex := groupRegisTools[int(v.ToolID)]; ex { tools = append(tools, v) } } @@ -221,11 +238,6 @@ func (d *DingTalkBotBiz) getUserContent(msgType string, msgContent interface{}) 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 { @@ -297,6 +309,28 @@ func (d *DingTalkBotBiz) HandleRes(ctx context.Context, data *chatbot.BotCallbac } } +func (d *DingTalkBotBiz) SaveHis(ctx context.Context, requireData *entitys.RequireDataDingTalkBot, chat []string) (err error) { + if len(chat) == 0 { + return + } + his := []*model.AiBotChatHi{ + { + HisType: requireData.Req.ConversationType, + ID: requireData.ID, + Role: "user", + Content: requireData.Req.Text.Content, + }, + { + HisType: requireData.Req.ConversationType, + ID: requireData.ID, + Role: "system", + Content: strings.Join(chat, "\n"), + }, + } + _, err = d.chatHis.Add(his) + return err +} + func (d *DingTalkBotBiz) replySteam(ctx context.Context, SessionWebhook string, content string, arg ...string) error { msg := content if len(arg) > 0 { @@ -344,3 +378,30 @@ func (d *DingTalkBotBiz) replyActionCard(ctx context.Context, SessionWebhook str } return d.replier.SimpleReplyText(ctx, SessionWebhook, []byte(msg)) } + +func (d *DingTalkBotBiz) defaultPrompt() string { + + return `[system] 你是一个智能路由系统,核心职责是 **精准解析用户意图并路由至对应任务模块**。请严格遵循以下规则: +[rule] +1. **返回格式**: +仅输出以下 **严格格式化的 JSON 字符串**(禁用 Markdown): +{ "index": "工具索引index", "confidence": 0.0-1.0,"reasoning": "判断理由","parameters":"jsonstring |提取参数","is_match":true||false,"chat": "追问内容"} +关键规则(按优先级排序): + +2. **工具匹配**: + +- 若匹配到工具,使用工具的 parameters 作为模板做参数匹配 +- 注意区分 parameters 中的 必须参数(required) 和 可选参数(optional),按下述参数提取规则处理。 +- 若**完全无法匹配**,立即设置 is_match: false,并在 chat 中已第一人称的角度提醒用户需要适用何种工具(例:"请问您是要查询订单还是商品呢")。 + +1. **参数提取**: + +- 根据 parameters 字段列出的参数名,从用户输入中提取对应值。 +- **仅提取**明确提及的参数,忽略未列出的内容。 +- 必须参数仅使用用户直接提及的参数,不允许从上下文推断。 +- 若必须参数缺失,立即设置 is_match: false,并在 chat 中已第一人称的角度提醒用户提供缺少的参数追问(例:"需要您补充XX信息")。 + +4. 格式强制要求: +-所有字段值必须是**字符串**(包括 confidence)。 +-parameters 必须是 **转义后的 JSON 字符串**(如 "{\"product_name\": \"京东月卡\"}")。` +} diff --git a/internal/data/model/ai_bot_chat_his.gen.go b/internal/data/model/ai_bot_chat_his.gen.go index 1e4bfff..2285343 100644 --- a/internal/data/model/ai_bot_chat_his.gen.go +++ b/internal/data/model/ai_bot_chat_his.gen.go @@ -13,7 +13,8 @@ const TableNameAiBotChatHi = "ai_bot_chat_his" // AiBotChatHi mapped from table type AiBotChatHi struct { HisID int64 `gorm:"column:his_id;primaryKey;autoIncrement:true" json:"his_id"` - SessionID string `gorm:"column:session_id;not null" json:"session_id"` + HisType string `gorm:"column:his_type;not null;default:1;comment:1为个人,2为群聊" json:"his_type"` // 1为个人,2为群聊 + ID int32 `gorm:"column:id;not null;comment:对应的id" json:"id"` // 对应的id Role string `gorm:"column:role;not null;comment:system系统输出,assistant助手输出,user用户输入" json:"role"` // system系统输出,assistant助手输出,user用户输入 Content string `gorm:"column:content;not null" json:"content"` Files string `gorm:"column:files;not null" json:"files"` diff --git a/internal/entitys/bot.go b/internal/entitys/bot.go index 7085a37..46661ab 100644 --- a/internal/entitys/bot.go +++ b/internal/entitys/bot.go @@ -13,6 +13,7 @@ type RequireDataDingTalkBot struct { Match *Match Req *chatbot.BotCallbackDataModel Ch chan Response + ID int32 } type DingTalkBot struct { diff --git a/internal/services/dtalk_bot.go b/internal/services/dtalk_bot.go index 0bfe129..5c9c92b 100644 --- a/internal/services/dtalk_bot.go +++ b/internal/services/dtalk_bot.go @@ -27,20 +27,27 @@ func (d *DingBotService) GetServiceCfg() ([]entitys.DingTalkBot, error) { } func (d *DingBotService) OnChatBotMessageReceived(ctx context.Context, data *chatbot.BotCallbackDataModel) (content []byte, err error) { + var ( + lastErr error + chat []string + ) requireData, err := d.dingTalkBotBiz.InitRequire(ctx, data) if err != nil { return } // 使用 ctx.Done() 通知 Do 方法提前终止 subCtx, cancel := context.WithCancel(ctx) - defer cancel() + defer func() { + cancel() + _ = d.dingTalkBotBiz.SaveHis(ctx, requireData, chat) + + }() // 异步执行 Do 方法 done := make(chan error, 1) go func() { done <- d.dingTalkBotBiz.Do(subCtx, requireData) }() - var lastErr error for { select { case <-ctx.Done(): @@ -53,6 +60,9 @@ func (d *DingBotService) OnChatBotMessageReceived(ctx context.Context, data *cha if resp.Type == entitys.ResponseLog { continue } + if resp.Type == entitys.ResponseText || resp.Type == entitys.ResponseStream || resp.Type == entitys.ResponseJson { + chat = append(chat, resp.Content) + } if err := d.dingTalkBotBiz.HandleRes(ctx, data, resp); err != nil { log.Printf("HandleRes 失败: %v", err) } @@ -60,9 +70,9 @@ func (d *DingBotService) OnChatBotMessageReceived(ctx context.Context, data *cha } cleanup: select { - case err := <-done: - if err != nil { - log.Printf("Do 方法执行失败: %v", err) + case _err := <-done: + if _err != nil { + panic(_err) } case <-time.After(1 * time.Second): log.Println("警告:等待 Do 方法超时,可能发生 goroutine 泄漏")