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/domain/workflow/recharge" "ai_scheduler/internal/domain/workflow/runtime" "ai_scheduler/internal/entitys" "ai_scheduler/internal/pkg/l_request" "ai_scheduler/internal/pkg/utils_oss" "ai_scheduler/internal/tools" "ai_scheduler/internal/tools/bbxt" "ai_scheduler/tmpl/dataTemp" "io" "net/http" "strconv" "time" "unicode" "ai_scheduler/internal/config" "context" "database/sql" "encoding/json" "errors" "fmt" "strings" "gitea.cdlsxd.cn/self-tools/l-dingtalk-stream-sdk-go/chatbot" "github.com/coze-dev/coze-go" "github.com/gofiber/fiber/v2/log" "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 chatHis *impl.BotChatHisImpl conf *config.Config cardSend *dingtalk.SendCardClient ossClient *utils_oss.Client workflowManager *runtime.Registry } // NewDingTalkBotBiz func NewDingTalkBotBiz( do *do.Do, handle *do.Handle, botConfigImpl *impl.BotConfigImpl, botGroupImpl *impl.BotGroupImpl, dingTalkUser *dingtalk.User, tools *tools_regis.ToolRegis, chatHis *impl.BotChatHisImpl, toolManager *tools.Manager, conf *config.Config, cardSend *dingtalk.SendCardClient, ossClient *utils_oss.Client, workflowManager *runtime.Registry, ) *DingTalkBotBiz { return &DingTalkBotBiz{ do: do, handle: handle, botConfigImpl: botConfigImpl, replier: chatbot.NewChatbotReplier(), dingTalkUser: dingTalkUser, botTools: tools.BootTools, botGroupImpl: botGroupImpl, toolManager: toolManager, chatHis: chatHis, conf: conf, cardSend: cardSend, ossClient: ossClient, workflowManager: workflowManager, } } 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.RobotCode 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.ResLoading(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) } if err != nil { entitys.ResText(requireData.Ch, "", err.Error()) } 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 //} //requireData.ID=requireData.UserInfo.UserID ////如果不是管理或者不是老板,则进行权限判断 //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, requireData.Req.RobotCode) //宏 err, isFinal := d.Macro(ctx, requireData, group) if err != nil { return } if isFinal { return } requireData.ID = group.GroupID 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, group) } func (d *DingTalkBotBiz) Macro(ctx context.Context, requireData *entitys.RequireDataDingTalkBot, group *model.AiBotGroup) (err error, isFinish bool) { content := processString(requireData.Req.Text.Content) if strings.Contains(content, "[利润同比报表]商品修改:") { // 提取冒号后的内容 if parts := strings.SplitN(content, ":", 2); len(parts) == 2 { itemInfo := strings.TrimSpace(parts[1]) log.Infof("商品修改信息: %s", itemInfo) group.ProductName = itemInfo cond := builder.NewCond() cond = cond.And(builder.Eq{"group_id": group.GroupID}) err = d.botGroupImpl.UpdateByCond(&cond, group) if err != nil { entitys.ResText(requireData.Ch, "", fmt.Sprintf("修改失败:%v", err)) } entitys.ResText(requireData.Ch, "", "修改成功") isFinish = true return } } if strings.Contains(content, "[利润同比报表]商品列表") { // 提取冒号后的内容 if len(group.ProductName) == 0 { entitys.ResText(requireData.Ch, "", "暂未设置") } else { entitys.ResText(requireData.Ch, "", group.ProductName) isFinish = true } return } return } func processString(s string) string { // 1. 替换中文逗号为英文逗号 s = strings.ReplaceAll(s, ",", ",") // 2. 过滤控制字符(如 \n, \t, \r 等) var result []rune for _, char := range s { // 判断是否是控制字符(ASCII < 32 或 = 127) if !unicode.IsControl(char) { // 如果需要完全移除 \n 和 \t,可以改成: // if !unicode.IsControl(char) result = append(result, char) } } return string(result) } func (d *DingTalkBotBiz) initGroup(ctx context.Context, conversationId string, conversationTitle string, robotCode string) (group *model.AiBotGroup, err error) { group, err = d.botGroupImpl.GetByConversationIdAndRobotCode(conversationId, robotCode) if err != nil { if !errors.Is(err, sql.ErrNoRows) { return } } if group.GroupID == 0 { group = &model.AiBotGroup{ ConversationID: conversationId, Title: conversationTitle, RobotCode: robotCode, ToolList: "", } //如果不存在则创建 _, err = 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 = make(map[int]struct{}) ) if group.ToolList != "" { groupToolList := strings.Split(group.ToolList, ",") for _, tool := range groupToolList { if tool == "" { continue } num, _err := strconv.Atoi(tool) if _err != nil { continue } groupRegisTools[num] = struct{}{} } } for _, v := range d.botTools { if v.PermissionType == constants.PermissionTypeNone { tools = append(tools, v) continue } if _, ex := groupRegisTools[int(v.ToolID)]; 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 } rec = &entitys.Recognize{ Ch: requireData.Ch, SystemPrompt: d.defaultPrompt(), UserContent: userContent, } //历史记录 rec.ChatHis, err = d.getHis(ctx, constants.ConversationType(requireData.Req.ConversationType), requireData.ID) if err != nil { return } //工具注册 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) getHis(ctx context.Context, conversationType constants.ConversationType, Id int32) (content entitys.ChatHis, err error) { var ( his []model.AiBotChatHi ) cond := builder.NewCond() cond = cond.And(builder.Eq{"his_type": conversationType}) cond = cond.And(builder.Eq{"id": Id}) _, err = d.chatHis.GetListToStruct(&cond, &dataTemp.ReqPageBo{Limit: d.conf.Sys.SessionLen}, &his, "his_id desc") if err != nil { return } messages := make([]entitys.HisMessage, 0, len(his)) for _, v := range his { messages = append(messages, entitys.HisMessage{ Role: constants.Caller(v.Role), // 用户角色 Content: v.Content, // 用户输入内容 Timestamp: v.CreateAt.Format(time.DateTime), }) } return entitys.ChatHis{ SessionId: fmt.Sprintf("%s_%d", conversationType, Id), Messages: messages, Context: entitys.HisContext{ UserLanguage: constants.LangZhCN, // 默认中文 SystemMode: constants.SystemModeTechnicalSupport, // 默认技术支持模式 }, }, nil } 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) handleMatch(ctx context.Context, rec *entitys.Recognize, group *model.AiBotGroup) (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.TaskTypeFunc: return d.handleTask(ctx, rec, pointTask) case constants.TaskTypeReport: return d.handleReport(ctx, rec, pointTask, group) case constants.TaskTypeCozeWorkflow: return d.handleCozeWorkflow(ctx, rec, pointTask) default: return d.otherTask(ctx, rec) } return } func (d *DingTalkBotBiz) handleCozeWorkflow(ctx context.Context, rec *entitys.Recognize, task *model.AiBotTool) (err error) { entitys.ResLoading(rec.Ch, task.Index, "正在执行工作流(coze)\n") customClient := &http.Client{ Timeout: time.Minute * 30, } authCli := coze.NewTokenAuth(d.conf.Coze.ApiSecret) cozeCli := coze.NewCozeAPI( authCli, coze.WithBaseURL(d.conf.Coze.BaseURL), coze.WithHttpClient(customClient), ) // 从参数中获取workflowID type requestParams struct { Request l_request.Request `json:"request"` } var config requestParams err = json.Unmarshal([]byte(task.Config), &config) if err != nil { return err } workflowId, ok := config.Request.Json["workflow_id"].(string) if !ok { return fmt.Errorf("workflow_id不能为空") } // 提取参数 var data map[string]interface{} err = json.Unmarshal([]byte(rec.Match.Parameters), &data) req := &coze.RunWorkflowsReq{ WorkflowID: workflowId, Parameters: data, // IsAsync: true, } stream := config.Request.Json["stream"].(bool) entitys.ResLog(rec.Ch, task.Index, "工作流执行中...") if stream { streamResp, err := cozeCli.Workflows.Runs.Stream(ctx, req) if err != nil { return err } handleCozeWorkflowEvents(ctx, streamResp, cozeCli, workflowId, rec.Ch, task.Index) } else { resp, err := cozeCli.Workflows.Runs.Create(ctx, req) if err != nil { return err } entitys.ResJson(rec.Ch, task.Index, resp.Data) } return } // handleCozeWorkflowEvents 处理 coze 工作流事件 func handleCozeWorkflowEvents(ctx context.Context, resp coze.Stream[coze.WorkflowEvent], cozeCli coze.CozeAPI, workflowID string, ch chan entitys.Response, index string) { defer resp.Close() for { event, err := resp.Recv() if errors.Is(err, io.EOF) { fmt.Println("Stream finished") break } if err != nil { fmt.Println("Error receiving event:", err) break } switch event.Event { case coze.WorkflowEventTypeMessage: entitys.ResStream(ch, index, event.Message.Content) case coze.WorkflowEventTypeError: entitys.ResError(ch, index, fmt.Sprintf("工作流执行错误: %s", event.Error)) case coze.WorkflowEventTypeDone: entitys.ResEnd(ch, index, "工作流执行完成") case coze.WorkflowEventTypeInterrupt: resumeReq := &coze.ResumeRunWorkflowsReq{ WorkflowID: workflowID, EventID: event.Interrupt.InterruptData.EventID, ResumeData: "your data", InterruptType: event.Interrupt.InterruptData.Type, } newResp, err := cozeCli.Workflows.Runs.Resume(ctx, resumeReq) if err != nil { entitys.ResError(ch, index, fmt.Sprintf("工作流恢复执行错误: %s", err.Error())) return } entitys.ResLog(ch, index, "工作流恢复执行中...") handleCozeWorkflowEvents(ctx, newResp, cozeCli, workflowID, ch, index) } } fmt.Printf("done, log:%s\n", resp.Response().LogID()) } func (d *DingTalkBotBiz) handleReport(ctx context.Context, rec *entitys.Recognize, task *model.AiBotTool, group *model.AiBotGroup) error { var configData entitys.ConfigDataReport err := json.Unmarshal([]byte(rec.Match.Parameters), &configData) if err != nil { return err } t, err := time.Parse(time.DateTime, configData.Time) if err != nil { t, err = time.Parse("2006-01-02 15:04", configData.Time) if err != nil { t, err = time.Parse("2006-01-02", configData.Time) if err != nil { log.Infof("时间识别失败:%s", configData.Time) entitys.ResText(rec.Ch, "", "时间识别失败了!可以给我一份比较具体的时间吗,例如“2025-12-31 12:00,抱歉抱歉😀") } } } rep, err := bbxt.NewBbxtTools() uploader := bbxt.NewUploader(d.ossClient) if err != nil { return err } var reports []*bbxt.ReportRes switch rec.Match.Index { case "report_loss_analysis": repo, _err := rep.StatisOursProductLossSum(t) if _err != nil { return _err } reports = append(reports, repo...) case "report_sales_analysis": product := strings.Split(group.ProductName, ",") repo, _err := rep.GetStatisOfficialProductSum(t, product) if _err != nil { return _err } reports = append(reports, repo) case "report_ranking_of_distributors": repo, _err := rep.GetProfitRankingSum(t) if _err != nil { return _err } reports = append(reports, repo) case "report_daily": product := strings.Split(group.ProductName, ",") repo, _err := rep.DailyReport(t, bbxt.DownWardValue, product, bbxt.SumFilter, nil) if _err != nil { return _err } reports = append(reports, repo...) case "report_daily_recharge": product := strings.Split(group.ProductName, ",") repo, _err := d.rechargeDailyReport(ctx, t, product, nil) if _err != nil || len(repo) == 0 { return _err } reports = append(reports, repo...) case "report_sale_down_analysis": product := strings.Split(group.ProductName, ",") repo, _err := rep.GetStatisOfficialProductSumDecline(t, bbxt.DownWardValue, product, bbxt.SumFilter) if _err != nil { return _err } reports = append(reports, repo) default: return fmt.Errorf("未找到的报表:%s", rec.Match.Index) } for _, report := range reports { err = uploader.Run(report) if err != nil { log.Error(err) continue } entitys.ResText(rec.Ch, "", fmt.Sprintf("%s![图片](%s)", report.Title, report.Url)) //rec.Ch <- report.Title //reportChan <- fmt.Sprintf("![图片](%s)", report.Url) //err = d.SendReport(ctx, group, report) //if err != nil { // log.Error(err) // continue //} } return nil } 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, ch chan string) error { // switch resp.Type { // case entitys.ResponseText: // return d.replyText(ctx, data.SessionWebhook, resp.Content) // case entitys.ResponseStream: // // return d.replySteam(ctx, data, ch) // 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) HandleStreamRes(ctx context.Context, data *chatbot.BotCallbackDataModel, content chan string) (err error) { err = d.cardSend.NewCard(ctx, &dingtalk.CardSend{ RobotCode: data.RobotCode, ConversationType: constants.ConversationType(data.ConversationType), Template: constants.CardTempDefault, ContentChannel: content, // 指定内容通道 ConversationId: data.ConversationId, SenderStaffId: data.SenderStaffId, Title: data.Text.Content, }) return } func (d *DingTalkBotBiz) GetReportLists(ctx context.Context, group *model.AiBotGroup) (reports []*bbxt.ReportRes, err error) { var product []string if group.ProductName != "" { product = strings.Split(group.ProductName, ",") } reportList, err := bbxt.NewBbxtTools() if err != nil { return } reports, err = reportList.DailyReport(time.Now(), bbxt.DownWardValue, product, bbxt.SumFilter, d.ossClient) if err != nil { return } //product = []string{"优酷周卡", "优酷季卡", "优酷年卡", "爱奇艺黄金会员天卡"} //追加电商充值系统统计 - 返回统一使用[]*bbxt.ReportRes rechargeReports, err := d.rechargeDailyReport(ctx, time.Now(), nil, d.ossClient) if err != nil || len(rechargeReports) == 0 { return } reports = append(reports, rechargeReports...) return } // rechargeDailyReport 获取电商充值系统统计报告 func (d *DingTalkBotBiz) rechargeDailyReport(ctx context.Context, now time.Time, productNames []string, ossClient *utils_oss.Client) (reports []*bbxt.ReportRes, err error) { defer func() { if err := recover(); err != nil { log.Error(err) } }() workflowId := recharge.WorkflowIDStatisticsOursProduct args := &runtime.WorkflowArgs{ Args: map[string]any{ "product_names": productNames, "now": now, }, } res, err := d.workflowManager.Invoke(ctx, workflowId, args) if err != nil { return } log.Infof("imgUrl: %s", res["url"].(string)) reports = []*bbxt.ReportRes{ { ReportName: "我们的商品统计(电商充值系统)", Title: fmt.Sprintf("%s 电商充值系统我们的商品统计", now.Format("2006-01-02")), Path: res["path"].(string), Url: res["url"].(string), Data: res["data"].([][]string), Desc: res["desc"].(string), }, } return } func (d *DingTalkBotBiz) SendReport(ctx context.Context, groupInfo *model.AiBotGroup, report *bbxt.ReportRes) (err error) { reportChan := make(chan string, 10) defer close(reportChan) reportChan <- report.Title reportChan <- fmt.Sprintf("![图片](%s)", report.Url) err = d.HandleStreamRes(ctx, &chatbot.BotCallbackDataModel{ RobotCode: groupInfo.RobotCode, ConversationType: constants.ConversationTypeGroup, ConversationId: groupInfo.ConversationID, Text: chatbot.BotCallbackDataTextModel{ Content: report.ReportName, }, }, reportChan) return } func (d *DingTalkBotBiz) GetGroupInfo(ctx context.Context, groupId int) (group model.AiBotGroup, err error) { cond := builder.NewCond() cond = cond.And(builder.Eq{"group_id": groupId}) cond = cond.And(builder.Eq{"status": constants.Enable}) err = d.botGroupImpl.GetOneBySearchToStrut(&cond, &group) return } 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)) } 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) defaultPrompt() string { now := time.Now().Format(time.DateTime) 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\": \"京东月卡\"}")。 当前时间:` + now + `,所有的时间识别精确到秒` }