refactor: enhance bot chat history and error handling
This commit is contained in:
parent
33b6363233
commit
9d95106b03
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"ai_scheduler/internal/data/model"
|
"ai_scheduler/internal/data/model"
|
||||||
"ai_scheduler/internal/entitys"
|
"ai_scheduler/internal/entitys"
|
||||||
"ai_scheduler/internal/tools"
|
"ai_scheduler/internal/tools"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
|
@ -34,6 +35,7 @@ type DingTalkBotBiz struct {
|
||||||
botTools []model.AiBotTool
|
botTools []model.AiBotTool
|
||||||
botGroupImpl *impl.BotGroupImpl
|
botGroupImpl *impl.BotGroupImpl
|
||||||
toolManager *tools.Manager
|
toolManager *tools.Manager
|
||||||
|
chatHis *impl.BotChatHisImpl
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDingTalkBotBiz
|
// NewDingTalkBotBiz
|
||||||
|
|
@ -44,6 +46,7 @@ func NewDingTalkBotBiz(
|
||||||
botGroupImpl *impl.BotGroupImpl,
|
botGroupImpl *impl.BotGroupImpl,
|
||||||
dingTalkUser *dingtalk.User,
|
dingTalkUser *dingtalk.User,
|
||||||
tools *tools_regis.ToolRegis,
|
tools *tools_regis.ToolRegis,
|
||||||
|
chatHis *impl.BotChatHisImpl,
|
||||||
toolManager *tools.Manager,
|
toolManager *tools.Manager,
|
||||||
) *DingTalkBotBiz {
|
) *DingTalkBotBiz {
|
||||||
return &DingTalkBotBiz{
|
return &DingTalkBotBiz{
|
||||||
|
|
@ -55,6 +58,7 @@ func NewDingTalkBotBiz(
|
||||||
botTools: tools.BootTools,
|
botTools: tools.BootTools,
|
||||||
botGroupImpl: botGroupImpl,
|
botGroupImpl: botGroupImpl,
|
||||||
toolManager: toolManager,
|
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) {
|
func (d *DingTalkBotBiz) Do(ctx context.Context, requireData *entitys.RequireDataDingTalkBot) (err error) {
|
||||||
entitys.ResText(requireData.Ch, "", "收到消息,正在处理中,请稍等")
|
entitys.ResLoading(requireData.Ch, "", "收到消息,正在处理中,请稍等")
|
||||||
defer close(requireData.Ch)
|
defer close(requireData.Ch)
|
||||||
switch constants.ConversationType(requireData.Req.ConversationType) {
|
switch constants.ConversationType(requireData.Req.ConversationType) {
|
||||||
case constants.ConversationTypeSingle:
|
case constants.ConversationTypeSingle:
|
||||||
|
|
@ -98,6 +102,9 @@ func (d *DingTalkBotBiz) Do(ctx context.Context, requireData *entitys.RequireDat
|
||||||
default:
|
default:
|
||||||
err = errors.New("未知的聊天类型:" + requireData.Req.ConversationType)
|
err = errors.New("未知的聊天类型:" + requireData.Req.ConversationType)
|
||||||
}
|
}
|
||||||
|
if err != nil {
|
||||||
|
entitys.ResText(requireData.Ch, "", err.Error())
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -108,6 +115,7 @@ func (d *DingTalkBotBiz) handleSingleChat(ctx context.Context, requireData *enti
|
||||||
//if err != nil {
|
//if err != nil {
|
||||||
// return
|
// return
|
||||||
//}
|
//}
|
||||||
|
//requireData.ID=requireData.UserInfo.UserID
|
||||||
////如果不是管理或者不是老板,则进行权限判断
|
////如果不是管理或者不是老板,则进行权限判断
|
||||||
//if requireData.UserInfo.IsSenior == constants.IsSeniorFalse && requireData.UserInfo.IsBoss == constants.IsBossFalse {
|
//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) {
|
func (d *DingTalkBotBiz) handleGroupChat(ctx context.Context, requireData *entitys.RequireDataDingTalkBot) (err error) {
|
||||||
group, err := d.initGroup(ctx, requireData.Req.ConversationId, requireData.Req.ConversationTitle)
|
group, err := d.initGroup(ctx, requireData.Req.ConversationId, requireData.Req.ConversationTitle)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
requireData.ID = group.GroupID
|
||||||
groupTools, err := d.getGroupTools(ctx, group)
|
groupTools, err := d.getGroupTools(ctx, group)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
|
@ -158,12 +168,19 @@ func (d *DingTalkBotBiz) getGroupTools(ctx context.Context, group *model.AiBotGr
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var (
|
var (
|
||||||
groupRegisTools map[string]struct{}
|
groupRegisTools = make(map[int]struct{})
|
||||||
)
|
)
|
||||||
if group.ToolList != "" {
|
if group.ToolList != "" {
|
||||||
groupList := strings.Split(group.ToolList, ",")
|
groupToolList := strings.Split(group.ToolList, ",")
|
||||||
for _, tool := range groupList {
|
for _, tool := range groupToolList {
|
||||||
groupRegisTools[tool] = struct{}{}
|
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)
|
tools = append(tools, v)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if _, ex := groupRegisTools[v.Index]; ex {
|
if _, ex := groupRegisTools[int(v.ToolID)]; ex {
|
||||||
tools = append(tools, v)
|
tools = append(tools, v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -221,11 +238,6 @@ func (d *DingTalkBotBiz) getUserContent(msgType string, msgContent interface{})
|
||||||
return
|
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) {
|
func (d *DingTalkBotBiz) handleMatch(ctx context.Context, rec *entitys.Recognize) (err error) {
|
||||||
|
|
||||||
if !rec.Match.IsMatch {
|
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 {
|
func (d *DingTalkBotBiz) replySteam(ctx context.Context, SessionWebhook string, content string, arg ...string) error {
|
||||||
msg := content
|
msg := content
|
||||||
if len(arg) > 0 {
|
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))
|
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\": \"京东月卡\"}")。`
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,8 @@ const TableNameAiBotChatHi = "ai_bot_chat_his"
|
||||||
// AiBotChatHi mapped from table <ai_bot_chat_his>
|
// AiBotChatHi mapped from table <ai_bot_chat_his>
|
||||||
type AiBotChatHi struct {
|
type AiBotChatHi struct {
|
||||||
HisID int64 `gorm:"column:his_id;primaryKey;autoIncrement:true" json:"his_id"`
|
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用户输入
|
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"`
|
Content string `gorm:"column:content;not null" json:"content"`
|
||||||
Files string `gorm:"column:files;not null" json:"files"`
|
Files string `gorm:"column:files;not null" json:"files"`
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ type RequireDataDingTalkBot struct {
|
||||||
Match *Match
|
Match *Match
|
||||||
Req *chatbot.BotCallbackDataModel
|
Req *chatbot.BotCallbackDataModel
|
||||||
Ch chan Response
|
Ch chan Response
|
||||||
|
ID int32
|
||||||
}
|
}
|
||||||
|
|
||||||
type DingTalkBot struct {
|
type DingTalkBot struct {
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
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)
|
requireData, err := d.dingTalkBotBiz.InitRequire(ctx, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// 使用 ctx.Done() 通知 Do 方法提前终止
|
// 使用 ctx.Done() 通知 Do 方法提前终止
|
||||||
subCtx, cancel := context.WithCancel(ctx)
|
subCtx, cancel := context.WithCancel(ctx)
|
||||||
defer cancel()
|
defer func() {
|
||||||
|
cancel()
|
||||||
|
_ = d.dingTalkBotBiz.SaveHis(ctx, requireData, chat)
|
||||||
|
|
||||||
|
}()
|
||||||
// 异步执行 Do 方法
|
// 异步执行 Do 方法
|
||||||
done := make(chan error, 1)
|
done := make(chan error, 1)
|
||||||
go func() {
|
go func() {
|
||||||
done <- d.dingTalkBotBiz.Do(subCtx, requireData)
|
done <- d.dingTalkBotBiz.Do(subCtx, requireData)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
var lastErr error
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
|
|
@ -53,6 +60,9 @@ func (d *DingBotService) OnChatBotMessageReceived(ctx context.Context, data *cha
|
||||||
if resp.Type == entitys.ResponseLog {
|
if resp.Type == entitys.ResponseLog {
|
||||||
continue
|
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 {
|
if err := d.dingTalkBotBiz.HandleRes(ctx, data, resp); err != nil {
|
||||||
log.Printf("HandleRes 失败: %v", err)
|
log.Printf("HandleRes 失败: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -60,9 +70,9 @@ func (d *DingBotService) OnChatBotMessageReceived(ctx context.Context, data *cha
|
||||||
}
|
}
|
||||||
cleanup:
|
cleanup:
|
||||||
select {
|
select {
|
||||||
case err := <-done:
|
case _err := <-done:
|
||||||
if err != nil {
|
if _err != nil {
|
||||||
log.Printf("Do 方法执行失败: %v", err)
|
panic(_err)
|
||||||
}
|
}
|
||||||
case <-time.After(1 * time.Second):
|
case <-time.After(1 * time.Second):
|
||||||
log.Println("警告:等待 Do 方法超时,可能发生 goroutine 泄漏")
|
log.Println("警告:等待 Do 方法超时,可能发生 goroutine 泄漏")
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue