refactor: enhance bot chat history and error handling

This commit is contained in:
renzhiyuan 2025-12-16 21:13:24 +08:00
parent 33b6363233
commit 9d95106b03
4 changed files with 90 additions and 17 deletions

View File

@ -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=falsechat提醒用户适用工具'请问您要查询订单还是商品?'"],"参数提取":["从用户输入提取parameters中明确提及的参数","必须参数仅用用户直接提及的缺失时is_match=falsechat提醒补充'需补充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\": \"京东月卡\"}"`
}

View File

@ -13,7 +13,8 @@ const TableNameAiBotChatHi = "ai_bot_chat_his"
// AiBotChatHi mapped from table <ai_bot_chat_his>
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"`

View File

@ -13,6 +13,7 @@ type RequireDataDingTalkBot struct {
Match *Match
Req *chatbot.BotCallbackDataModel
Ch chan Response
ID int32
}
type DingTalkBot struct {

View File

@ -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 泄漏")