Compare commits

..

No commits in common. "c4e2d30fc559b4e25a685151845823a7802748ed" and "c96ee6cc38f470627c00616a24d7f838111a8f8b" have entirely different histories.

37 changed files with 306 additions and 791 deletions

View File

@ -12,7 +12,6 @@ import (
func main() { func main() {
configPath := flag.String("config", "./config/config_test.yaml", "Path to configuration file") configPath := flag.String("config", "./config/config_test.yaml", "Path to configuration file")
onBot := flag.String("bot", "", "bot start") onBot := flag.String("bot", "", "bot start")
cron := flag.String("cron", "", "close")
flag.Parse() flag.Parse()
ctx := context.Background() ctx := context.Background()
bc, err := config.LoadConfig(*configPath) bc, err := config.LoadConfig(*configPath)
@ -30,7 +29,7 @@ func main() {
//钉钉机器人 //钉钉机器人
app.DingBotServer.Run(ctx, *onBot) app.DingBotServer.Run(ctx, *onBot)
//定时任务 - 测试环境不启用 //定时任务 - 测试环境不启用
if *cron == "start" { if configPath != nil && *configPath == "./config/config.yaml" {
app.Cron.Run(ctx) app.Cron.Run(ctx)
} }

View File

@ -47,7 +47,7 @@ redis:
host: 47.97.27.195:6379 host: 47.97.27.195:6379
type: node type: node
pass: lansexiongdi@666 pass: lansexiongdi@666
key: ai_scheduler_prov key: report-api-test
pollSize: 5 #连接池大小不配置或配置为0表示不启用连接池 pollSize: 5 #连接池大小不配置或配置为0表示不启用连接池
minIdleConns: 2 #最小空闲连接数 minIdleConns: 2 #最小空闲连接数
maxIdleTime: 30 #每个连接最大空闲时间,如果超过了这个时间会被关闭 maxIdleTime: 30 #每个连接最大空闲时间,如果超过了这个时间会被关闭
@ -108,8 +108,6 @@ tools:
base_url: "https://api.coze.cn" base_url: "https://api.coze.cn"
api_key: "7583905168607100978" api_key: "7583905168607100978"
api_secret: "pat_eEN0BdLNDughEtABjJJRYTW71olvDU0qUbfQUeaPc2NnYWO8HeyNoui5aR9z0sSZ" api_secret: "pat_eEN0BdLNDughEtABjJJRYTW71olvDU0qUbfQUeaPc2NnYWO8HeyNoui5aR9z0sSZ"
zltxResellerAuthProductToManagerAndDefaultLossReason:
base_url: "https://revcl.1688sup.com/api/admin/reseller/resellerAuthProduct/getManagerAndDefaultLossReason"
# eino tool 配置 # eino tool 配置
eino_tools: eino_tools:

View File

@ -29,7 +29,7 @@ lsxd:
phone: "ORlviZN7N06W2+WKLe76xg==" phone: "ORlviZN7N06W2+WKLe76xg=="
password: "V5Uh8C4bamEM6UQZh4TCeQ==" password: "V5Uh8C4bamEM6UQZh4TCeQ=="
check_token_url: "https://api.user.1688sup.com/v1/user/welcome" check_token_url: "https://api.user.1688sup.com/v1/user/welcome"
code: "456789"
sys: sys:
session_len: 6 session_len: 6

View File

@ -4,10 +4,10 @@ server:
host: "0.0.0.0" host: "0.0.0.0"
ollama: ollama:
base_url: "http://192.168.6.115:11434" base_url: "http://127.0.0.1:11434"
model: "qwen3:8b" model: "qwen3-coder:480b-cloud"
generate_model: "qwen3:8b" generate_model: "qwen3-coder:480b-cloud"
mapping_model: "qwen3:8b" mapping_model: "deepseek-v3.2:cloud"
vl_model: "gemini-3-pro-preview" vl_model: "gemini-3-pro-preview"
timeout: "120s" timeout: "120s"
level: "info" level: "info"
@ -32,6 +32,8 @@ lsxd:
code: "123456" code: "123456"
check_token_url: "http://api.test.user.1688sup.com/v1/user/welcome" check_token_url: "http://api.test.user.1688sup.com/v1/user/welcome"
zltx:
req_url: "https://gateway.dev.cdlsxd.cn/zltx_api"
sys: sys:
session_len: 6 session_len: 6
@ -43,7 +45,7 @@ redis:
host: 47.97.27.195:6379 host: 47.97.27.195:6379
type: node type: node
pass: lansexiongdi@666 pass: lansexiongdi@666
key: ai_scheduler_test key: ai_scheduler-test
pollSize: 5 #连接池大小不配置或配置为0表示不启用连接池 pollSize: 5 #连接池大小不配置或配置为0表示不启用连接池
minIdleConns: 2 #最小空闲连接数 minIdleConns: 2 #最小空闲连接数
maxIdleTime: 30 #每个连接最大空闲时间,如果超过了这个时间会被关闭 maxIdleTime: 30 #每个连接最大空闲时间,如果超过了这个时间会被关闭
@ -104,8 +106,6 @@ tools:
base_url: "https://api.coze.cn" base_url: "https://api.coze.cn"
api_key: "7583905168607100978" api_key: "7583905168607100978"
api_secret: "pat_eEN0BdLNDughEtABjJJRYTW71olvDU0qUbfQUeaPc2NnYWO8HeyNoui5aR9z0sSZ" api_secret: "pat_eEN0BdLNDughEtABjJJRYTW71olvDU0qUbfQUeaPc2NnYWO8HeyNoui5aR9z0sSZ"
zltxResellerAuthProductToManagerAndDefaultLossReason:
base_url: "https://revcl.1688sup.com/api/admin/reseller/resellerAuthProduct/getManagerAndDefaultLossReason"
# eino tool 配置 # eino tool 配置
eino_tools: eino_tools:

View File

@ -16,12 +16,10 @@ fi
CONFIG_FILE="config/config.yaml" CONFIG_FILE="config/config.yaml"
BRANCH="master" BRANCH="master"
BOT="All" BOT="All"
CRON="start"
if [ "$MODE" = "dev" ]; then if [ "$MODE" = "dev" ]; then
CONFIG_FILE="config/config_test.yaml" CONFIG_FILE="config/config_test.yaml"
BOT="zltx" BOT="zltx"
BRANCH="test" BRANCH="test"
CRON="close"
fi fi
git fetch origin git fetch origin
@ -49,8 +47,6 @@ docker run -itd \
-v ./cache:/app/cache \ -v ./cache:/app/cache \
-v ./tmpl:/app/tmpl \ -v ./tmpl:/app/tmpl \
-v ./go.mod:/app/go.mod \ -v ./go.mod:/app/go.mod \
"${CONTAINER_NAME}" ./server \ "${CONTAINER_NAME}" ./server --config "./${CONFIG_FILE}" --bot "${BOT}"
--config "./${CONFIG_FILE}" --bot "${BOT}" --cron "${CRON}"
docker logs -f ${CONTAINER_NAME} docker logs -f ${CONTAINER_NAME}

View File

@ -9,7 +9,6 @@ import (
"ai_scheduler/internal/data/impl" "ai_scheduler/internal/data/impl"
"ai_scheduler/internal/data/model" "ai_scheduler/internal/data/model"
"ai_scheduler/internal/entitys" "ai_scheduler/internal/entitys"
"ai_scheduler/internal/pkg"
"ai_scheduler/internal/tools" "ai_scheduler/internal/tools"
"ai_scheduler/internal/tools/bbxt" "ai_scheduler/internal/tools/bbxt"
"ai_scheduler/tmpl/dataTemp" "ai_scheduler/tmpl/dataTemp"
@ -20,6 +19,7 @@ import (
"fmt" "fmt"
"strings" "strings"
"time" "time"
"unicode"
"gitea.cdlsxd.cn/self-tools/l-dingtalk-stream-sdk-go/chatbot" "gitea.cdlsxd.cn/self-tools/l-dingtalk-stream-sdk-go/chatbot"
@ -45,7 +45,6 @@ type DingTalkBotBiz struct {
qywxGroupHandle *qywx.Group qywxGroupHandle *qywx.Group
groupConfigBiz *GroupConfigBiz groupConfigBiz *GroupConfigBiz
reportDailyCacheImpl *impl.ReportDailyCacheImpl reportDailyCacheImpl *impl.ReportDailyCacheImpl
macro *do.Macro
} }
// NewDingTalkBotBiz // NewDingTalkBotBiz
@ -61,7 +60,6 @@ func NewDingTalkBotBiz(
conf *config.Config, conf *config.Config,
cardSend *dingtalk.SendCardClient, cardSend *dingtalk.SendCardClient,
groupConfigBiz *GroupConfigBiz, groupConfigBiz *GroupConfigBiz,
macro *do.Macro,
) *DingTalkBotBiz { ) *DingTalkBotBiz {
return &DingTalkBotBiz{ return &DingTalkBotBiz{
do: do, do: do,
@ -76,7 +74,6 @@ func NewDingTalkBotBiz(
conf: conf, conf: conf,
cardSend: cardSend, cardSend: cardSend,
reportDailyCacheImpl: reportDailyCacheImpl, reportDailyCacheImpl: reportDailyCacheImpl,
macro: macro,
} }
} }
@ -151,14 +148,10 @@ func (d *DingTalkBotBiz) handleGroupChat(ctx context.Context, requireData *entit
return return
} }
//宏 //宏
sucMsg, err, isFinal := d.macro.Router(ctx, requireData.Req.Text.Content, groupConfig) err, isFinal := d.Macro(ctx, requireData, groupConfig)
if err != nil { if err != nil {
entitys.ResText(requireData.Ch, "", err.Error())
return return
} }
if len(sucMsg) > 0 {
entitys.ResText(requireData.Ch, "", sucMsg)
}
if isFinal { if isFinal {
return return
} }
@ -175,6 +168,102 @@ func (d *DingTalkBotBiz) handleGroupChat(ctx context.Context, requireData *entit
return d.groupConfigBiz.handleMatch(ctx, rec, groupConfig) return d.groupConfigBiz.handleMatch(ctx, rec, groupConfig)
} }
func (d *DingTalkBotBiz) Macro(ctx context.Context, requireData *entitys.RequireDataDingTalkBot, groupConfig *model.AiBotGroupConfig) (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)
groupConfig.ProductName = itemInfo
cond := builder.NewCond()
cond = cond.And(builder.Eq{"config_id": groupConfig.ConfigID})
err = d.botGroupImpl.UpdateByCond(&cond, groupConfig)
if err != nil {
entitys.ResText(requireData.Ch, "", fmt.Sprintf("修改失败:%v", err))
}
entitys.ResText(requireData.Ch, "", "修改成功")
isFinish = true
return
}
}
if strings.Contains(content, "[利润同比报表]商品列表") {
// 提取冒号后的内容
if len(groupConfig.ProductName) == 0 {
entitys.ResText(requireData.Ch, "", "暂未设置")
} else {
entitys.ResText(requireData.Ch, "", groupConfig.ProductName)
isFinish = true
}
return
}
if strings.Contains(content, "[负利润分析]获取") {
var (
data model.AiReportDailyCache
value map[int32]*bbxt.ResellerLossSumProductRelation
)
cond := builder.NewCond()
cond = cond.And(builder.Eq{"`index`": bbxt.IndexLossSumDetail})
cond = cond.And(builder.Eq{"`key`": time.Now().Format(time.DateOnly)})
err = d.reportDailyCacheImpl.GetOneBySearchToStrut(&cond, &data)
if err != nil {
entitys.ResText(requireData.Ch, "", "获取失败")
return
}
err = json.Unmarshal([]byte(data.Value), &value)
if err != nil {
entitys.ResText(requireData.Ch, "", "获取失败,格式解析错误")
return
}
return
}
if strings.Contains(content, "[负利润分析]更新") {
// 提取冒号后的内容
if len(groupConfig.ProductName) == 0 {
entitys.ResText(requireData.Ch, "", "暂未设置")
} else {
entitys.ResText(requireData.Ch, "", groupConfig.ProductName)
isFinish = true
}
return
}
if strings.Contains(content, "[负利润分析]同步") {
// 提取冒号后的内容
if len(groupConfig.ProductName) == 0 {
entitys.ResText(requireData.Ch, "", "暂未设置")
} else {
entitys.ResText(requireData.Ch, "", groupConfig.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) { 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) group, err = d.botGroupImpl.GetByConversationIdAndRobotCode(conversationId, robotCode)
if err != nil { if err != nil {
@ -224,16 +313,11 @@ func (d *DingTalkBotBiz) recognize(ctx context.Context, requireData *entitys.Req
rec.Tasks = append(rec.Tasks, entitys.RegistrationTask{ rec.Tasks = append(rec.Tasks, entitys.RegistrationTask{
Name: task.Index, Name: task.Index,
Desc: task.TempPrompt, Desc: task.Desc,
TaskConfigDetail: taskConfig, // 直接使用解析后的配置,避免重复构建 TaskConfigDetail: taskConfig, // 直接使用解析后的配置,避免重复构建
}) })
} }
} }
rec.Ext = pkg.JsonByteIgonErr(&entitys.TaskExt{
UserName: requireData.Req.SenderNick,
})
err = d.handle.Recognize(ctx, rec, &do.WithDingTalkBot{}) err = d.handle.Recognize(ctx, rec, &do.WithDingTalkBot{})
return return
} }
@ -399,7 +483,7 @@ func (d *DingTalkBotBiz) defaultPrompt() string {
- 注意区分 parameters 中的 必须参数required 可选参数optional按下述参数提取规则处理 - 注意区分 parameters 中的 必须参数required 可选参数optional按下述参数提取规则处理
- **完全无法匹配**立即设置 is_match: false并在 chat 中已第一人称的角度提醒用户需要适用何种工具"请问您是要查询订单还是商品呢" - **完全无法匹配**立即设置 is_match: false并在 chat 中已第一人称的角度提醒用户需要适用何种工具"请问您是要查询订单还是商品呢"
3. **参数提取** 1. **参数提取**
- 根据 parameters 字段列出的参数名从用户输入中提取对应值 - 根据 parameters 字段列出的参数名从用户输入中提取对应值
- **仅提取**明确提及的参数忽略未列出的内容 - **仅提取**明确提及的参数忽略未列出的内容

View File

@ -133,9 +133,7 @@ func (r *Handle) HandleMatch(ctx context.Context, client *gateway.Client, rec *e
case constants.TaskTypeFunc: case constants.TaskTypeFunc:
return r.handleTask(ctx, rec, pointTask) return r.handleTask(ctx, rec, pointTask)
case constants.TaskTypeBot: case constants.TaskTypeBot:
return r.HandleBot(ctx, rec, &entitys.Task{ return r.handleBot(ctx, rec, pointTask)
Index: pointTask.Index,
})
case constants.TaskTypeEinoWorkflow: case constants.TaskTypeEinoWorkflow:
return r.handleEinoWorkflow(ctx, rec, pointTask) return r.handleEinoWorkflow(ctx, rec, pointTask)
case constants.TaskTypeCozeWorkflow: case constants.TaskTypeCozeWorkflow:
@ -249,34 +247,23 @@ func (r *Handle) handleKnowle(ctx context.Context, rec *entitys.Recognize, task
} }
// bot 临时实现,后续转到 eino 工作流 // bot 临时实现,后续转到 eino 工作流
func (r *Handle) HandleBot(ctx context.Context, rec *entitys.Recognize, task *entitys.Task) (err error) { func (r *Handle) handleBot(ctx context.Context, rec *entitys.Recognize, task *model.AiTask) (err error) {
if task.Index == "bug_optimization_submit" { if task.Index == "bug_optimization_submit" {
var unionId string entitys.ResLoading(rec.Ch, task.Index, "需求记录中...")
entitys.ResLoading(rec.Ch, task.Index, "需求记录中...\n")
// Ext 中获取 sessionId
sessionID := rec.GetSession()
// 获取dingtalk accessToken // 获取dingtalk accessToken
accessToken, _ := r.dingtalkOldClient.GetAccessToken() accessToken, _ := r.dingtalkOldClient.GetAccessToken()
// Ext 中获取 sessionId
taskExt := rec.GetTaskExt()
if taskExt == nil {
return errorcode.ParamErr("taskExt参数错误")
}
if len(taskExt.Session) > 0 {
// 获取创建者 dingtalk unionId // 获取创建者 dingtalk unionId
unionId = r.getUserDingtalkUnionId(ctx, accessToken, taskExt.Session) unionId := r.getUserDingtalkUnionId(ctx, accessToken, sessionID)
} else if len(taskExt.UserName) > 0 {
unionId = r.getUserDingtalkUnionIdWithUserName(ctx, accessToken, taskExt.UserName)
} else {
return errorcode.ParamErr("taskExt参数错误,重要参数缺失")
}
// 附件url // 附件url
var attachmentUrl string var attachmentUrl string
for _, file := range rec.UserContent.File { for _, file := range rec.UserContent.File {
attachmentUrl = file.FileUrl attachmentUrl = file.FileUrl
break break
} }
recordId, err := r.dingtalkNotableClient.InsertRecord(accessToken, &dingtalk.InsertRecordReq{
req := &dingtalk.InsertRecordReq{
BaseId: r.conf.Dingtalk.TableDemand.BaseId, BaseId: r.conf.Dingtalk.TableDemand.BaseId,
SheetIdOrName: r.conf.Dingtalk.TableDemand.SheetIdOrName, SheetIdOrName: r.conf.Dingtalk.TableDemand.SheetIdOrName,
// OperatorId: tool_callback.BotBugOptimizationSubmitAdminUnionId, // OperatorId: tool_callback.BotBugOptimizationSubmitAdminUnionId,
@ -284,9 +271,7 @@ func (r *Handle) HandleBot(ctx context.Context, rec *entitys.Recognize, task *en
CreatorUnionId: unionId, CreatorUnionId: unionId,
Content: rec.UserContent.Text, Content: rec.UserContent.Text,
AttachmentUrl: attachmentUrl, AttachmentUrl: attachmentUrl,
} })
recordId, err := r.dingtalkNotableClient.InsertRecord(accessToken, req)
if err != nil { if err != nil {
errCode := r.dingtalkNotableClient.GetHTTPStatus(err) errCode := r.dingtalkNotableClient.GetHTTPStatus(err)
// 权限不足 // 权限不足
@ -299,16 +284,12 @@ func (r *Handle) HandleBot(ctx context.Context, rec *entitys.Recognize, task *en
if recordId == "" { if recordId == "" {
return errors.NewBusinessErr(422, "创建记录失败,请联系管理员") return errors.NewBusinessErr(422, "创建记录失败,请联系管理员")
} }
var detailPage string
entitys.ResLog(rec.Ch, task.Index, "需求记录完成") entitys.ResLog(rec.Ch, task.Index, "需求记录完成")
switch task.OutPutFormat {
case entitys.OutPutFormatMarkdown:
// 构建跳转链接 // 构建跳转链接
detailPage = "[去查看](" + r.conf.Dingtalk.TableDemand.Url + ")" detailPage := util.BuildJumpLink(r.conf.Dingtalk.TableDemand.Url, "去查看")
default:
// 构建跳转链接
detailPage = util.BuildJumpLink(r.conf.Dingtalk.TableDemand.Url, "去查看")
}
entitys.ResText(rec.Ch, task.Index, fmt.Sprintf("需求已记录,正在分配相关人员处理,请您耐心等待处理结果。点击查看工单进度:%s", detailPage)) entitys.ResText(rec.Ch, task.Index, fmt.Sprintf("需求已记录,正在分配相关人员处理,请您耐心等待处理结果。点击查看工单进度:%s", detailPage))
return nil return nil
@ -319,21 +300,16 @@ func (r *Handle) HandleBot(ctx context.Context, rec *entitys.Recognize, task *en
// getUserDingtalkUnionId 获取用户的 dingtalk unionId // getUserDingtalkUnionId 获取用户的 dingtalk unionId
func (r *Handle) getUserDingtalkUnionId(ctx context.Context, accessToken, sessionID string) (unionId string) { func (r *Handle) getUserDingtalkUnionId(ctx context.Context, accessToken, sessionID string) (unionId string) {
if len(sessionID) == 0 {
// 查询用户名 // 查询用户名
return ""
}
session, has, err := r.sessionImpl.FindOne(r.sessionImpl.WithSessionId(sessionID)) session, has, err := r.sessionImpl.FindOne(r.sessionImpl.WithSessionId(sessionID))
if err != nil || !has { if err != nil || !has {
log.Warnf("session not found: %s", sessionID) log.Warnf("session not found: %s", sessionID)
return return
} }
return r.getUserDingtalkUnionIdWithUserName(ctx, accessToken, session.UserName) creatorName := session.UserName
}
func (r *Handle) getUserDingtalkUnionIdWithUserName(ctx context.Context, accessToken, userName string) (unionId string) {
// 获取创建者uid 用户名 -> dingtalk uid // 获取创建者uid 用户名 -> dingtalk uid
creatorId, err := r.dingtalkContactClient.SearchUserOne(accessToken, userName) creatorId, err := r.dingtalkContactClient.SearchUserOne(accessToken, creatorName)
if err != nil { if err != nil {
log.Warnf("search dingtalk user one failed: %v", err) log.Warnf("search dingtalk user one failed: %v", err)
return return

View File

@ -1,460 +0,0 @@
package do
import (
"ai_scheduler/internal/config"
"ai_scheduler/internal/data/impl"
"ai_scheduler/internal/data/model"
"ai_scheduler/internal/pkg"
"ai_scheduler/internal/pkg/lsxd"
"ai_scheduler/internal/tools/bbxt"
"ai_scheduler/utils"
"database/sql"
"errors"
"context"
"encoding/json"
"fmt"
"strconv"
"strings"
"time"
"unicode"
"github.com/gofiber/fiber/v2/log"
"xorm.io/builder"
)
type Macro struct {
botGroupImpl *impl.BotGroupImpl
reportDailyCacheImpl *impl.ReportDailyCacheImpl
config *config.Config
rdb *utils.Rdb
}
func NewMacro(
botGroupImpl *impl.BotGroupImpl,
reportDailyCacheImpl *impl.ReportDailyCacheImpl,
config *config.Config,
rdb *utils.Rdb,
) *Macro {
return &Macro{
botGroupImpl: botGroupImpl,
reportDailyCacheImpl: reportDailyCacheImpl,
config: config,
rdb: rdb,
}
}
type MacroFunc func(ctx context.Context, content string, groupConfig *model.AiBotGroupConfig, config *config.Config) (successMsg string, err error, isFinish bool)
func (m *Macro) Router(ctx context.Context, reqContent string, groupConfig *model.AiBotGroupConfig) (successMsg string, err error, isFinish bool) {
reqContent = strings.TrimSpace(reqContent)
switch {
case strings.HasPrefix(reqContent, "[利润同比报表]商品修改"):
return m.ProductModify(ctx, reqContent, groupConfig)
case strings.HasPrefix(reqContent, "[利润同比报表]商品列表"):
return m.ProductList(ctx, groupConfig)
case strings.HasPrefix(reqContent, "[负利润分析]导出"):
return m.NegativeProfitGet(ctx)
case strings.HasPrefix(reqContent, "[负利润分析]更新"):
return m.NegativeProfitUpdate(ctx, reqContent)
case strings.HasPrefix(reqContent, "[负利润分析]清理"):
return m.NegativeProfitClear()
default:
}
return
}
func (m *Macro) NegativeProfitClear() (successMsg string, err error, isFinish bool) {
dayDate := time.Now().Format(time.DateOnly)
cond := builder.NewCond()
cond = cond.And(builder.Eq{"cache_index": bbxt.IndexLossSumDetail})
cond = cond.And(builder.Eq{"cache_key": dayDate})
cond = cond.And(builder.Eq{"status": 1})
err = m.reportDailyCacheImpl.UpdateByCond(&cond, &model.AiReportDailyCache{
Status: 2,
})
if err != nil {
err = fmt.Errorf("解析失败:%v", err)
return
}
isFinish = true
successMsg = "清理成功"
return
}
func (m *Macro) NegativeProfitUpdate(ctx context.Context, content string) (successMsg string, err error, isFinish bool) {
//newContent := strings.ReplaceAll(strings.TrimSpace(content), "[负利润分析]更新:", "")
jsonData, err := ParseLossData(content)
b, err := bbxt.NewBbxtTools(m.config, lsxd.NewLogin(m.config, m.rdb))
if err != nil {
return
}
now := time.Now()
dayData := now.Format(time.DateOnly)
value, err := b.GetMapResellerLossSumProductRelation(ctx, now, m.GetReportCache)
if err != nil {
return
}
var setData = make(map[int32]*bbxt.ResellerLossSumProductRelation)
for k, v := range jsonData {
if _, ok := value[k]; !ok {
continue
}
for productId, product := range v.Products {
if _, ok := value[k].Products[productId]; !ok {
continue
}
if value[k].Products[productId].LossReason == product.LossReason {
continue
}
if _, ex := setData[k]; !ex {
setData[k] = &bbxt.ResellerLossSumProductRelation{
ResellerName: value[k].ResellerName,
AfterSaleName: value[k].AfterSaleName,
Products: make(map[int32]*bbxt.LossReason),
}
}
setData[k].Products[productId] = &bbxt.LossReason{
ProductName: product.ProductName,
LossReason: product.LossReason,
}
}
}
cond := builder.NewCond()
cond = cond.And(builder.Eq{"cache_index": bbxt.IndexLossSumDetail})
cond = cond.And(builder.Eq{"cache_key": dayData})
cond = cond.And(builder.Eq{"status": 1})
var cache model.AiReportDailyCache
err = m.reportDailyCacheImpl.GetOneBySearchToStrut(&cond, &cache)
if err != nil {
return
}
if cache.ID == 0 {
cache = model.AiReportDailyCache{
CacheKey: dayData,
CacheIndex: bbxt.IndexLossSumDetail,
Value: pkg.JsonStringIgonErr(setData),
}
_, err = m.reportDailyCacheImpl.Add(&cache)
if err != nil {
return
}
} else {
err = m.reportDailyCacheImpl.UpdateByCond(&cond, &model.AiReportDailyCache{
Value: pkg.JsonStringIgonErr(setData),
})
if err != nil {
return
}
}
isFinish = true
successMsg = "更新成功"
return
}
func (m *Macro) NegativeProfitGet(ctx context.Context) (successMsg string, err error, isFinish bool) {
b, err := bbxt.NewBbxtTools(m.config, lsxd.NewLogin(m.config, m.rdb))
if err != nil {
return
}
now := time.Now()
value, err := b.GetMapResellerLossSumProductRelation(ctx, now, m.GetReportCache)
if err != nil {
return
}
//将value转为string
//**[供应商id]供应商名称->商务名称**\n
//└──商品名称:亏损原因\n
var valueString strings.Builder
valueString.WriteString("[负利润分析]更新:\n")
for k, v := range value {
if len(v.AfterSaleName) == 0 {
v.AfterSaleName = "未查找到对应商务"
}
valueString.WriteString(fmt.Sprintf("**[%d]%s->%s**\n", k, v.ResellerName, v.AfterSaleName))
for kk, vv := range v.Products {
valueString.WriteString(fmt.Sprintf("└── [%d]%s:%s\n", kk, vv.ProductName, vv.LossReason))
}
}
successMsg = valueString.String()
isFinish = true
return
}
func (m *Macro) ProductModify(ctx context.Context, content string, groupConfig *model.AiBotGroupConfig) (successMsg string, err error, isFinish bool) {
content = processString(content)
if parts := strings.SplitN(content, "", 2); len(parts) == 2 {
itemInfo := strings.TrimSpace(parts[1])
log.Infof("商品修改信息: %s", itemInfo)
groupConfig.ProductName = itemInfo
cond := builder.NewCond()
cond = cond.And(builder.Eq{"config_id": groupConfig.ConfigID})
err = m.botGroupImpl.UpdateByCond(&cond, groupConfig)
if err != nil {
err = fmt.Errorf("修改失败:%v", err)
return
}
successMsg = "修改成功"
isFinish = true
return
}
return
}
func (m *Macro) ProductList(ctx context.Context, groupConfig *model.AiBotGroupConfig) (successMsg string, err error, isFinish bool) {
if len(groupConfig.ProductName) == 0 {
successMsg = "暂未设置"
} else {
successMsg = groupConfig.ProductName
isFinish = true
}
return
}
func processString(s string) string {
// 替换中文逗号为英文逗号
s = strings.ReplaceAll(s, "", ",")
return string(clearSpacialFormat(s))
}
func clearSpacialFormat(s string) (result []rune) {
//过滤控制字符(如 \n, \t, \r 等)
for _, char := range s {
// 判断是否是控制字符ASCII < 32 或 = 127
if !unicode.IsControl(char) {
// 如果需要完全移除 \n 和 \t可以改成
// if !unicode.IsControl(char)
result = append(result, char)
}
}
return
}
func ParseLossData(input string) (map[int32]*bbxt.ResellerLossSumProductRelation, error) {
result := make(map[int32]*bbxt.ResellerLossSumProductRelation)
// 按行分割
lines := strings.Split(input, "\n")
// 跳过第一行的标题
startIdx := 0
for i, line := range lines {
if strings.HasPrefix(line, "[") && strings.Contains(line, "]") && strings.Contains(line, "->") {
startIdx = i
break
}
}
var currentResellerID int32
var currentReseller *bbxt.ResellerLossSumProductRelation
for i := startIdx; i < len(lines); i++ {
line := strings.TrimSpace(lines[i])
if line == "" {
continue
}
// 检查是否是供应商行(以 [ 开头)
if strings.HasPrefix(line, "[") && strings.Contains(line, "]") {
// 解析供应商行:[25131]兴业银行-营销系统->未查找到对应商务
// 找到第一个 ] 的位置
bracketEnd := strings.Index(line, "]")
if bracketEnd == -1 {
continue
}
// 提取供应商ID
idStr := line[1:bracketEnd]
id, err := strconv.ParseInt(idStr, 10, 32)
if err != nil {
continue
}
currentResellerID = int32(id)
// 提取供应商名称和商务名称
remaining := strings.TrimSpace(line[bracketEnd+1:])
// 分割供应商名称和商务名称
var resellerName, afterSaleName string
arrowIdx := strings.Index(remaining, "->")
if arrowIdx != -1 {
resellerName = strings.TrimSpace(remaining[:arrowIdx])
afterSaleName = strings.TrimSpace(remaining[arrowIdx+2:])
} else {
resellerName = remaining
afterSaleName = ""
}
// 创建新的供应商对象
currentReseller = &bbxt.ResellerLossSumProductRelation{
AfterSaleName: afterSaleName,
ResellerName: resellerName,
Products: make(map[int32]*bbxt.LossReason),
}
result[currentResellerID] = currentReseller
} else if strings.Contains(line, "└──") {
// 解析商品行:└── [460]全国话费30元:未填写
if currentReseller == nil {
continue
}
// 移除└──前缀
productLine := strings.TrimPrefix(line, "└──")
productLine = strings.TrimSpace(productLine)
if !strings.HasPrefix(productLine, "[") {
continue
}
// 找到商品ID的结束位置
prodBracketEnd := strings.Index(productLine, "]")
if prodBracketEnd == -1 {
continue
}
// 提取商品ID
prodIDStr := productLine[1:prodBracketEnd]
prodID, err := strconv.ParseInt(prodIDStr, 10, 32)
if err != nil {
continue
}
// 提取商品名称和亏损原因
prodRemaining := strings.TrimSpace(productLine[prodBracketEnd+1:])
// 找到冒号分隔符
colonIdx := strings.Index(prodRemaining, ":")
var productName, lossReason string
if colonIdx != -1 {
productName = strings.TrimSpace(prodRemaining[:colonIdx])
lossReason = strings.TrimSpace(prodRemaining[colonIdx+1:])
} else {
productName = prodRemaining
lossReason = ""
}
// 添加到当前供应商的商品列表中
currentReseller.Products[int32(prodID)] = &bbxt.LossReason{
ProductName: productName,
LossReason: lossReason,
}
}
}
return result, nil
}
func (m *Macro) GetReportCache(ctx context.Context, day time.Time, totalDetail []*bbxt.ResellerLoss, bbxtObj *bbxt.BbxtTools) error {
dayDate := day.Format(time.DateOnly)
// 1. 从 API 获取数据并填充
apiRelations, err := bbxtObj.GetResellerLossMannagerAndLossReasonFromApi(ctx, totalDetail)
if err != nil {
return fmt.Errorf("get API data failed: %w", err)
}
// 使用 API 数据填充损失原因
fillLossReasonFromData(totalDetail, apiRelations, "未填写")
// 2. 从缓存获取数据并覆盖
cachedRelations, err := m.getCachedRelations(dayDate)
if err != nil {
return fmt.Errorf("get cache data failed: %w", err)
}
// 使用缓存数据覆盖损失原因
if cachedRelations != nil {
fillLossReasonFromData(totalDetail, cachedRelations, "")
}
return nil
}
// 从缓存获取关系数据
func (m *Macro) getCachedRelations(dayDate string) (map[int32]*bbxt.ResellerLossSumProductRelation, error) {
cond := builder.NewCond().
And(builder.Eq{"cache_index": bbxt.IndexLossSumDetail}).
And(builder.Eq{"cache_key": dayDate}).
And(builder.Eq{"status": 1})
var cache model.AiReportDailyCache
err := m.reportDailyCacheImpl.GetOneBySearchToStrut(&cond, &cache)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
// 缓存不存在是正常情况
return nil, nil
}
return nil, fmt.Errorf("query cache failed: %w", err)
}
if cache.ID == 0 {
return nil, nil
}
var relations map[int32]*bbxt.ResellerLossSumProductRelation
if err := json.Unmarshal([]byte(cache.Value), &relations); err != nil {
return nil, fmt.Errorf("unmarshal cache failed: %w", err)
}
return relations, nil
}
// 使用指定数据源填充损失原因
func fillLossReasonFromData(
totalDetail []*bbxt.ResellerLoss,
relations map[int32]*bbxt.ResellerLossSumProductRelation,
defaultReason string, // 当数据不存在时使用的默认值
) {
for _, detail := range totalDetail {
resellerRelation, exists := relations[detail.ResellerId]
if !exists {
continue
}
// 设置售后经理(只有在有值时才设置)
if resellerRelation.AfterSaleName != "" {
detail.Manager = resellerRelation.AfterSaleName
}
// 为每个产品设置损失原因
for _, product := range detail.ProductLoss {
setProductLossReason(product, resellerRelation, defaultReason)
}
}
}
// 设置单个产品的损失原因
func setProductLossReason(
product *bbxt.ProductLoss,
resellerRelation *bbxt.ResellerLossSumProductRelation,
defaultReason string,
) {
// 如果该经销商没有产品数据,跳过
if resellerRelation.Products == nil {
return
}
productRelation, exists := resellerRelation.Products[product.ProductId]
if !exists {
if defaultReason != "" {
product.LossReason = defaultReason
}
return
}
// 如果有损失原因则设置,否则使用默认值
if productRelation.LossReason != "" {
product.LossReason = productRelation.LossReason
} else if defaultReason != "" {
product.LossReason = defaultReason
}
}

View File

@ -166,7 +166,6 @@ func (f *WithDingTalkBot) getUserContent(ctx context.Context, rec *entitys.Recog
} }
if len(rec.ChatHis.Messages) > 0 { if len(rec.ChatHis.Messages) > 0 {
content.WriteString("\n")
content.WriteString("### 引用历史聊天记录:\n") content.WriteString("### 引用历史聊天记录:\n")
content.WriteString(pkg.JsonStringIgonErr(rec.ChatHis)) content.WriteString(pkg.JsonStringIgonErr(rec.ChatHis))
} }

View File

@ -1,7 +1,6 @@
package biz package biz
import ( import (
"ai_scheduler/internal/biz/do"
"ai_scheduler/internal/biz/tools_regis" "ai_scheduler/internal/biz/tools_regis"
"ai_scheduler/internal/config" "ai_scheduler/internal/config"
"ai_scheduler/internal/data/constants" "ai_scheduler/internal/data/constants"
@ -10,6 +9,7 @@ import (
"ai_scheduler/internal/domain/workflow/recharge" "ai_scheduler/internal/domain/workflow/recharge"
"ai_scheduler/internal/domain/workflow/runtime" "ai_scheduler/internal/domain/workflow/runtime"
"ai_scheduler/internal/entitys" "ai_scheduler/internal/entitys"
"ai_scheduler/internal/pkg"
"ai_scheduler/internal/pkg/l_request" "ai_scheduler/internal/pkg/l_request"
"ai_scheduler/internal/pkg/lsxd" "ai_scheduler/internal/pkg/lsxd"
"ai_scheduler/internal/pkg/utils_oss" "ai_scheduler/internal/pkg/utils_oss"
@ -17,6 +17,7 @@ import (
"ai_scheduler/internal/tools/bbxt" "ai_scheduler/internal/tools/bbxt"
"ai_scheduler/utils" "ai_scheduler/utils"
"context" "context"
"database/sql"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
@ -41,8 +42,6 @@ type GroupConfigBiz struct {
toolManager *tools.Manager toolManager *tools.Manager
conf *config.Config conf *config.Config
rdb *utils.Rdb rdb *utils.Rdb
macro *do.Macro
handle *do.Handle
} }
// NewDingTalkBotBiz // NewDingTalkBotBiz
@ -54,9 +53,6 @@ func NewGroupConfigBiz(
conf *config.Config, conf *config.Config,
reportDailyCacheImpl *impl.ReportDailyCacheImpl, reportDailyCacheImpl *impl.ReportDailyCacheImpl,
rdb *utils.Rdb, rdb *utils.Rdb,
macro *do.Macro,
toolManager *tools.Manager,
handle *do.Handle,
) *GroupConfigBiz { ) *GroupConfigBiz {
return &GroupConfigBiz{ return &GroupConfigBiz{
botTools: tools.BootTools, botTools: tools.BootTools,
@ -66,9 +62,6 @@ func NewGroupConfigBiz(
conf: conf, conf: conf,
reportDailyCacheImpl: reportDailyCacheImpl, reportDailyCacheImpl: reportDailyCacheImpl,
rdb: rdb, rdb: rdb,
macro: macro,
toolManager: toolManager,
handle: handle,
} }
} }
@ -94,7 +87,7 @@ func (g *GroupConfigBiz) GetReportLists(ctx context.Context, groupConfig *model.
return return
} }
reports, err = reportList.DailyReport(ctx, time.Now(), bbxt.DownWardValue, product, bbxt.SumFilter, g.ossClient, g.macro.GetReportCache) reports, err = reportList.DailyReport(ctx, time.Now(), bbxt.DownWardValue, product, bbxt.SumFilter, g.ossClient, g.GetReportCache)
if err != nil { if err != nil {
return return
} }
@ -171,7 +164,7 @@ func (g *GroupConfigBiz) handleReport(ctx context.Context, rec *entitys.Recogniz
var reports []*bbxt.ReportRes var reports []*bbxt.ReportRes
switch rec.Match.Index { switch rec.Match.Index {
case "report_loss_analysis": case "report_loss_analysis":
repo, _err := rep.StatisOursProductLossSum(ctx, t, g.macro.GetReportCache) repo, _err := rep.StatisOursProductLossSum(ctx, t, g.GetReportCache)
if _err != nil { if _err != nil {
return _err return _err
} }
@ -192,15 +185,15 @@ func (g *GroupConfigBiz) handleReport(ctx context.Context, rec *entitys.Recogniz
reports = append(reports, repo) reports = append(reports, repo)
case "report_daily": case "report_daily":
product := strings.Split(groupConfig.ProductName, ",") product := strings.Split(groupConfig.ProductName, ",")
repo, _err := rep.DailyReport(ctx, t, bbxt.DownWardValue, product, bbxt.SumFilter, nil, g.macro.GetReportCache) repo, _err := rep.DailyReport(ctx, t, bbxt.DownWardValue, product, bbxt.SumFilter, nil, g.GetReportCache)
if _err != nil {
return _err
}
rechargeReport, _err := g.rechargeDailyReport(ctx, t, product, nil)
if _err != nil { if _err != nil {
return _err return _err
} }
reports = append(reports, repo...) reports = append(reports, repo...)
rechargeReport, _err := g.rechargeDailyReport(ctx, t, product, nil)
if _err != nil || len(repo) == 0 {
return _err
}
reports = append(reports, rechargeReport...) reports = append(reports, rechargeReport...)
case "report_daily_recharge": case "report_daily_recharge":
product := strings.Split(groupConfig.ProductName, ",") product := strings.Split(groupConfig.ProductName, ",")
@ -231,6 +224,7 @@ func (g *GroupConfigBiz) handleReport(ctx context.Context, rec *entitys.Recogniz
} }
entitys.ResText(rec.Ch, "", fmt.Sprintf("%s![图片](%s)", report.Title, report.Url)) entitys.ResText(rec.Ch, "", fmt.Sprintf("%s![图片](%s)", report.Title, report.Url))
} }
return nil return nil
} }
@ -259,11 +253,6 @@ func (g *GroupConfigBiz) handleMatch(ctx context.Context, rec *entitys.Recognize
switch constants.TaskType(pointTask.Type) { switch constants.TaskType(pointTask.Type) {
case constants.TaskTypeFunc: case constants.TaskTypeFunc:
return g.handleTask(ctx, rec, pointTask) return g.handleTask(ctx, rec, pointTask)
case constants.TaskTypeBot:
return g.handle.HandleBot(ctx, rec, &entitys.Task{
Index: pointTask.Index,
OutPutFormat: entitys.OutPutFormatMarkdown,
})
case constants.TaskTypeReport: case constants.TaskTypeReport:
return g.handleReport(ctx, rec, pointTask, groupConfig) return g.handleReport(ctx, rec, pointTask, groupConfig)
case constants.TaskTypeCozeWorkflow: case constants.TaskTypeCozeWorkflow:
@ -313,6 +302,7 @@ func (q *GroupConfigBiz) handleTask(ctx context.Context, rec *entitys.Recognize,
if err != nil { if err != nil {
return return
} }
err = q.toolManager.ExecuteTool(ctx, configData.Tool, rec) err = q.toolManager.ExecuteTool(ctx, configData.Tool, rec)
if err != nil { if err != nil {
return return
@ -425,3 +415,47 @@ func (g *GroupConfigBiz) otherTask(ctx context.Context, rec *entitys.Recognize)
entitys.ResText(rec.Ch, "", rec.Match.Reasoning) entitys.ResText(rec.Ch, "", rec.Match.Reasoning)
return return
} }
func (g *GroupConfigBiz) GetReportCache(ctx context.Context, day time.Time, totalDetail []*bbxt.ResellerLoss, bbxtObj *bbxt.BbxtTools) error {
var ResellerProductRelation map[int32]*bbxt.ResellerLossSumProductRelation
dayDate := day.Format(time.DateOnly)
cond := builder.NewCond()
cond = cond.And(builder.Eq{"index": bbxt.IndexLossSumDetail})
cond = cond.And(builder.Eq{"key": dayDate})
var cache model.AiReportDailyCache
err := g.reportDailyCacheImpl.GetOneBySearchToStrut(&cond, &cache)
if err != nil {
if errors.Is(sql.ErrNoRows, err) {
ResellerProductRelation, err = bbxtObj.GetResellerLossMannagerAndLossReasonFromApi(ctx, totalDetail)
if err != nil {
return err
}
cache = model.AiReportDailyCache{
Key: dayDate,
Index: bbxt.IndexLossSumDetail,
Value: pkg.JsonStringIgonErr(ResellerProductRelation),
}
_, err = g.reportDailyCacheImpl.Add(&cache)
}
} else {
err = json.Unmarshal([]byte(cache.Value), &ResellerProductRelation)
}
if err != nil {
return err
}
for _, v := range totalDetail {
if _, ex := ResellerProductRelation[v.ResellerId]; !ex {
continue
}
v.Manager = ResellerProductRelation[v.ResellerId].AfterSaleName
for _, vv := range v.ProductLoss {
if _, ex := ResellerProductRelation[v.ResellerId].Products[vv.ProductId]; !ex {
continue
}
vv.LossReason = ResellerProductRelation[v.ResellerId].Products[vv.ProductId].LossReason
}
}
return nil
}

View File

@ -20,7 +20,7 @@ import (
) )
const DefaultInterval = 100 * time.Millisecond const DefaultInterval = 100 * time.Millisecond
const HeardBeatX = 100 const HeardBeatX = 50
type SendCardClient struct { type SendCardClient struct {
Auth *Auth Auth *Auth

View File

@ -20,5 +20,4 @@ var ProviderSetBiz = wire.NewSet(
NewDingTalkBotBiz, NewDingTalkBotBiz,
NewQywxAppBiz, NewQywxAppBiz,
NewGroupConfigBiz, NewGroupConfigBiz,
do.NewMacro,
) )

View File

@ -27,6 +27,7 @@ type Config struct {
LLM LLM `mapstructure:"llm"` LLM LLM `mapstructure:"llm"`
Dingtalk DingtalkConfig `mapstructure:"dingtalk"` Dingtalk DingtalkConfig `mapstructure:"dingtalk"`
Qywx QywxConfig `mapstructure:"qywx"` Qywx QywxConfig `mapstructure:"qywx"`
ZLTX ZLTX `mapstructure:"zltx"`
} }
type ZLTX struct { type ZLTX struct {
@ -196,7 +197,6 @@ type ToolsConfig struct {
CozeExpress ToolConfig `mapstructure:"cozeExpress"` CozeExpress ToolConfig `mapstructure:"cozeExpress"`
// Coze 公司查询工具 // Coze 公司查询工具
CozeCompany ToolConfig `mapstructure:"cozeCompany"` CozeCompany ToolConfig `mapstructure:"cozeCompany"`
ZltxResellerAuthProductToManagerAndDefaultLossReason ToolConfig `mapstructure:"zltxResellerAuthProductToManagerAndDefaultLossReason"`
} }
// ToolConfig 单个工具配置 // ToolConfig 单个工具配置

View File

@ -9,10 +9,9 @@ const TableNameAiReportDailyCache = "ai_report_daily_cache"
// AiReportDailyCache mapped from table <ai_report_daily_cache> // AiReportDailyCache mapped from table <ai_report_daily_cache>
type AiReportDailyCache struct { type AiReportDailyCache struct {
ID int32 `gorm:"column:id;primaryKey;autoIncrement:true" json:"id"` ID int32 `gorm:"column:id;primaryKey;autoIncrement:true" json:"id"`
CacheKey string `gorm:"column:cache_key;not null;default:1;comment:索引方式,可以是任意类型" json:"cache_key"` // 索引方式,可以是任意类型 Key string `gorm:"column:key;not null;default:1;comment:索引方式,可以是任意类型" json:"key"` // 索引方式,可以是任意类型
Value string `gorm:"column:value;comment:类型下所需路由以及参数" json:"value"` // 类型下所需路由以及参数 Value string `gorm:"column:value;comment:类型下所需路由以及参数" json:"value"` // 类型下所需路由以及参数
CacheIndex string `gorm:"column:cache_index;not null;comment:类型" json:"cache_index"` // 类型 Index string `gorm:"column:index;not null;comment:类型" json:"index"` // 类型
Status int32 `gorm:"column:status;not null;default:1" json:"status"`
} }
// TableName AiReportDailyCache's table name // TableName AiReportDailyCache's table name

View File

@ -21,13 +21,3 @@ type DingTalkBot struct {
ClientId string `json:"client_id"` ClientId string `json:"client_id"`
ClientSecret string `json:"client_secret"` ClientSecret string `json:"client_secret"`
} }
type Task struct {
Index string `json:"bot_index"`
OutPutFormat OutPutFormat `json:"out_put_format"`
}
type OutPutFormat string
const OutPutFormatMarkdown OutPutFormat = "markdown"
const OutPutFormatHtml OutPutFormat = "html"

View File

@ -23,7 +23,6 @@ type TaskExt struct {
SessionInfo model.AiSession SessionInfo model.AiSession
Sys model.AiSy Sys model.AiSy
KnowledgeConf KnowledgeBaseRequest KnowledgeConf KnowledgeBaseRequest
UserName string
} }
type RegistrationTask struct { type RegistrationTask struct {

View File

@ -64,9 +64,7 @@ type FunctionCall struct {
type ToolDefinition struct { type ToolDefinition struct {
Type string `json:"type"` Type string `json:"type"`
Function FunctionDef `json:"function"` Function FunctionDef `json:"function"`
AuthFunc AuthFunc `json:"function"`
} }
type AuthFunc func(rec *Recognize) (token []byte, err error)
// FunctionDef 函数定义 // FunctionDef 函数定义
type FunctionDef struct { type FunctionDef struct {
@ -79,7 +77,7 @@ type FunctionDef struct {
type Tool interface { type Tool interface {
Name() string Name() string
Description() string Description() string
Definition(ctx context.Context) ToolDefinition Definition() ToolDefinition
Execute(ctx context.Context, requireData *Recognize) error Execute(ctx context.Context, requireData *Recognize) error
} }

View File

@ -165,8 +165,7 @@ func (l *Login) cacheToken(ctx context.Context, token string) error {
if token == "" { if token == "" {
return errors.New("token is empty") return errors.New("token is empty")
} }
key := l.getCacheKey() return l.redisCli.Set(ctx, l.getCacheKey(), token, constants.EXPIRE_LSXD_TOKEN).Err()
return l.redisCli.Set(ctx, key, token, constants.EXPIRE_LSXD_TOKEN).Err()
} }
func (l *Login) doRequest(ctx context.Context, method string, url string, authorization string, body []byte) (int, error) { func (l *Login) doRequest(ctx context.Context, method string, url string, authorization string, body []byte) (int, error) {
@ -191,7 +190,7 @@ func (l *Login) doRequestWithBody(ctx context.Context, method string, url string
req.Header.Set("Content-Type", contentType) req.Header.Set("Content-Type", contentType)
} }
if authorization != "" { if authorization != "" {
req.Header.Set("Authorization", "Bearer "+authorization) req.Header.Set("Authorization", authorization)
} }
resp, err := (&http.Client{}).Do(req) resp, err := (&http.Client{}).Do(req)

View File

@ -120,8 +120,8 @@ func run() {
group := qywx.NewGroup(botGroupQywxImpl, qywxAuth) group := qywx.NewGroup(botGroupQywxImpl, qywxAuth)
other := qywx.NewOther(qywxAuth) other := qywx.NewOther(qywxAuth)
qywxAppBiz := biz.NewQywxAppBiz(configConfig, botGroupQywxImpl, group, other) qywxAppBiz := biz.NewQywxAppBiz(configConfig, botGroupQywxImpl, group, other)
groupConfigBiz := biz.NewGroupConfigBiz(toolRegis, utils_ossClient, botGroupConfigImpl, registry, configConfig, impl.NewReportDailyCacheImpl(db), rdb) groupConfigBiz := biz.NewGroupConfigBiz(toolRegis, utils_ossClient, botGroupConfigImpl, registry, configConfig)
dingTalkBotBiz := biz.NewDingTalkBotBiz(doDo, handle, botConfigImpl, botGroupImpl, user, botChatHisImpl, impl.NewReportDailyCacheImpl(db), manager, configConfig, sendCardClient, groupConfigBiz) dingTalkBotBiz := biz.NewDingTalkBotBiz(doDo, handle, botConfigImpl, botGroupImpl, user, botChatHisImpl, manager, configConfig, sendCardClient, groupConfigBiz)
// 初始化钉钉机器人服务 // 初始化钉钉机器人服务
cronService = NewCronService(configConfig, dingTalkBotBiz, qywxAppBiz, groupConfigBiz) cronService = NewCronService(configConfig, dingTalkBotBiz, qywxAppBiz, groupConfigBiz)
} }

View File

@ -4,14 +4,13 @@ import (
"ai_scheduler/internal/pkg" "ai_scheduler/internal/pkg"
"ai_scheduler/internal/pkg/l_request" "ai_scheduler/internal/pkg/l_request"
"ai_scheduler/internal/pkg/util" "ai_scheduler/internal/pkg/util"
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http" "net/http"
"net/url" "net/url"
"strings" "strings"
"github.com/go-kratos/kratos/v2/log"
) )
type StatisOursProductLossSumReq struct { type StatisOursProductLossSumReq struct {
@ -196,49 +195,48 @@ type GetManagerAndDefaultLossReasonResponse struct {
} }
type GetManagerAndDefaultLossReasonResponseList struct { type GetManagerAndDefaultLossReasonResponseList struct {
ResellerInfo *GetManagerAndDefaultLossReasonResponse_ResellerInfo `json:"resellerInfo,omitempty"` ResellerInfo *GetManagerAndDefaultLossReasonResponse_ResellerInfo `json:"GetManagerAndDefaultLossReasonResponse_ResellerInfo,omitempty"`
ProductList []*GetManagerAndDefaultLossReasonResponse_ProductList `json:"productList,omitempty"` ProductList []*GetManagerAndDefaultLossReasonResponse_ProductList `json:"GetManagerAndDefaultLossReasonResponse_ProductList,omitempty"`
} }
type GetManagerAndDefaultLossReasonResponse_ResellerInfo struct { type GetManagerAndDefaultLossReasonResponse_ResellerInfo struct {
ResellerId int32 `json:"resellerId,omitempty"` ResellerId int32 `json:"reseller_id,omitempty"`
AfterSaleName string `json:"AfterSaleName,omitempty"` AfterSaleName string `json:"after_sale_name,omitempty"`
} }
type GetManagerAndDefaultLossReasonResponse_ProductList struct { type GetManagerAndDefaultLossReasonResponse_ProductList struct {
ProductId int32 `json:"productId,omitempty"` ProductId int32 `json:"product_id,omitempty"`
LossReason string `json:"lossReason,omitempty"` LossReason string `json:"loss_reason,omitempty"`
} }
//return []*GetManagerAndDefaultLossReasonResponseList{
//{
//ResellerInfo: &GetManagerAndDefaultLossReasonResponse_ResellerInfo{
//ResellerId: 25009,
//AfterSaleName: "张三",
//},
//ProductList: []*GetManagerAndDefaultLossReasonResponse_ProductList{
//{
//ProductId: 129,
//LossReason: "小米钱包h5-QQ音乐绿钻季卡原因",
//},
//{
//ProductId: 2218,
//LossReason: "小米钱包h5-百度网盘新vip会员月卡原因",
//},
//{
//ProductId: 3226,
//LossReason: "小米钱包h5-腾讯视频月卡-小米钱包2024原因",
//},
//{
//ProductId: 3364,
//LossReason: "小米钱包h5-腾讯视频月卡-0元直充原因",
//},
//},
//},
//}, nil
// GetStatisFilterOfficialProductApi 官方商品列表 // GetStatisFilterOfficialProductApi 官方商品列表
func GetManagerAndDefaultLossReasonApi(param *GetManagerAndDefaultLossReasonRequest, token string, reqUrl string) ([]*GetManagerAndDefaultLossReasonResponseList, error) { func GetManagerAndDefaultLossReasonApi(param *GetManagerAndDefaultLossReasonRequest, token string, reqUrl string) ([]*GetManagerAndDefaultLossReasonResponseList, error) {
return []*GetManagerAndDefaultLossReasonResponseList{
{
ResellerInfo: &GetManagerAndDefaultLossReasonResponse_ResellerInfo{
ResellerId: 25009,
AfterSaleName: "张三",
},
ProductList: []*GetManagerAndDefaultLossReasonResponse_ProductList{
{
ProductId: 129,
LossReason: "小米钱包h5-QQ音乐绿钻季卡原因",
},
{
ProductId: 2218,
LossReason: "小米钱包h5-百度网盘新vip会员月卡原因",
},
{
ProductId: 3226,
LossReason: "小米钱包h5-腾讯视频月卡-小米钱包2024原因",
},
{
ProductId: 3364,
LossReason: "小米钱包h5-腾讯视频月卡-0元直充原因",
},
},
},
}, nil
reqParam, err := util.StructToMap(param) reqParam, err := util.StructToMap(param)
if err != nil { if err != nil {
@ -246,20 +244,17 @@ func GetManagerAndDefaultLossReasonApi(param *GetManagerAndDefaultLossReasonRequ
} }
req := &l_request.Request{ req := &l_request.Request{
Url: reqUrl, Url: reqUrl + "/admin/reseller/resellerAuthProduct/getManagerAndDefaultLossReason",
Method: http.MethodPost, Method: http.MethodPost,
Json: reqParam, Json: reqParam,
Headers: map[string]string{ Headers: map[string]string{
"Authorization": fmt.Sprintf("Bearer %s", token), "Authorization": fmt.Sprintf("Bearer %s", token),
}, },
} }
log.Debug(pkg.JsonStringIgonErr(req.Headers))
res, err := req.Send() res, err := req.Send()
if err != nil { if err != nil {
log.Debug(err.Error())
return nil, err return nil, err
} }
log.Debug(string(res.Content))
if res.StatusCode != http.StatusOK { if res.StatusCode != http.StatusOK {
return nil, fmt.Errorf("request failed, status code: %d,resion: %s", res.StatusCode, res.Reason) return nil, fmt.Errorf("request failed, status code: %d,resion: %s", res.StatusCode, res.Reason)
} }

View File

@ -31,7 +31,7 @@ var (
SumFilter int32 = -150 SumFilter int32 = -150
) )
var ResellerBlackListProduct = []string{ var resellerBlackListProduct = []string{
"悦跑", "悦跑",
"电商-独立", "电商-独立",
"蓝星严选连续包月", "蓝星严选连续包月",
@ -103,7 +103,9 @@ func (b *BbxtTools) DailyReport(
return return
} }
func (b *BbxtTools) ResellerLossSort(ctx context.Context, now time.Time) ([]*ResellerLoss, error) {
// StatisOursProductLossSum 负利润分析
func (b *BbxtTools) StatisOursProductLossSum(ctx context.Context, now time.Time, initFunc LossSumInitFunc) (report []*ReportRes, err error) {
ct := []string{ ct := []string{
time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()).Format("2006-01-02 15:04:05"), time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()).Format("2006-01-02 15:04:05"),
adjustedTime(now), //adjustedTime(time.Date(now.Year(), now.Month(), now.Day(), 23, 59, 59, 0, now.Location())), adjustedTime(now), //adjustedTime(time.Date(now.Year(), now.Month(), now.Day(), 23, 59, 59, 0, now.Location())),
@ -113,10 +115,13 @@ func (b *BbxtTools) ResellerLossSort(ctx context.Context, now time.Time) ([]*Res
Ct: ct, Ct: ct,
}) })
if err != nil { if err != nil {
return nil, err return
} }
var ( var (
resellerMap = make(map[int32]*ResellerLoss) resellerMap = make(map[int32]*ResellerLoss)
total [][]string
gt []*ResellerLoss
totalDetail []*ResellerLoss
) )
for _, info := range data.List { for _, info := range data.List {
@ -161,66 +166,14 @@ func (b *BbxtTools) ResellerLossSort(ctx context.Context, now time.Time) ([]*Res
sort.Slice(resellers, func(i, j int) bool { sort.Slice(resellers, func(i, j int) bool {
return resellers[i].Total < resellers[j].Total return resellers[i].Total < resellers[j].Total
}) })
return resellers, nil
}
func (b *BbxtTools) GetMapResellerLossSumProductRelation(ctx context.Context, now time.Time, initFunc LossSumInitFunc) (map[int32]*ResellerLossSumProductRelation, error) {
resellers, err := b.ResellerLossSort(ctx, now)
if err != nil {
return nil, err
}
var totalDetail []*ResellerLoss
for _, v := range resellers {
if v.Total <= -100 && !slices.Contains(ResellerBlackListProduct, v.ResellerName) {
totalDetail = append(totalDetail, v)
}
}
if len(totalDetail) > 0 {
err = initFunc(ctx, now, totalDetail, b)
if err != nil {
return nil, err
}
}
value := b.ResellerLossToMapResellerLossSumProductRelation(totalDetail)
return value, nil
}
func (b *BbxtTools) ResellerLossToMapResellerLossSumProductRelation(totalDetail []*ResellerLoss) map[int32]*ResellerLossSumProductRelation {
relations := make(map[int32]*ResellerLossSumProductRelation)
for _, detail := range totalDetail {
if _, ok := relations[detail.ResellerId]; !ok {
relations[detail.ResellerId] = &ResellerLossSumProductRelation{
ResellerName: detail.ResellerName,
AfterSaleName: detail.Manager,
Products: make(map[int32]*LossReason),
}
}
for _, product := range detail.ProductLoss {
relations[detail.ResellerId].Products[product.ProductId] = &LossReason{
ProductName: product.ProductName,
LossReason: product.LossReason,
}
}
}
return relations
}
// StatisOursProductLossSum 负利润分析
func (b *BbxtTools) StatisOursProductLossSum(ctx context.Context, now time.Time, initFunc LossSumInitFunc) (report []*ReportRes, err error) {
resellers, err := b.ResellerLossSort(ctx, now)
var ( var (
total [][]string
gt []*ResellerLoss
totalDetail []*ResellerLoss
totalSum float64 totalSum float64
totalSum500 float64 totalSum500 float64
) )
// 构建分组 // 构建分组
for _, v := range resellers { for _, v := range resellers {
if v.Total <= -100 && !slices.Contains(ResellerBlackListProduct, v.ResellerName) { if v.Total <= -100 && !slices.Contains(resellerBlackListProduct, v.ResellerName) {
total = append(total, []string{ total = append(total, []string{
fmt.Sprintf("%s", v.ResellerName), fmt.Sprintf("%s", v.ResellerName),
fmt.Sprintf("%.2f", v.Total), fmt.Sprintf("%.2f", v.Total),
@ -228,7 +181,7 @@ func (b *BbxtTools) StatisOursProductLossSum(ctx context.Context, now time.Time,
totalSum += v.Total totalSum += v.Total
totalDetail = append(totalDetail, v) totalDetail = append(totalDetail, v)
} }
if v.Total <= -500 && !slices.Contains(ResellerBlackListProduct, v.ResellerName) { if v.Total <= -500 && !slices.Contains(resellerBlackListProduct, v.ResellerName) {
gt = append(gt, v) gt = append(gt, v)
totalSum500 += v.Total totalSum500 += v.Total
} }
@ -265,24 +218,24 @@ func (b *BbxtTools) StatisOursProductLossSum(ctx context.Context, now time.Time,
} }
} }
if len(totalDetail) > 0 { //if len(totalDetail) > 0 {
err = initFunc(ctx, now, totalDetail, b) // err = initFunc(ctx, now, totalDetail, b)
if err != nil { // if err != nil {
return // return
} // }
filePath := b.cacheDir + "/kshj_total_ana" + fmt.Sprintf("%d%d", time.Now().Unix(), rand.Intn(1000)) + ".xlsx" // filePath := b.cacheDir + "/kshj_total_ana" + fmt.Sprintf("%d%d", time.Now().Unix(), rand.Intn(1000)) + ".xlsx"
title := "截至" + timeCh + "亏损100以上的分销商&产品负利润原因" // title := "截至" + timeCh + "亏损100以上的分销商&产品负利润原因"
err = b.resellerDetailFillExcelAna(b.excelTempDir+"/"+"kshj_total_ana.xlsx", filePath, totalDetail, title) // err = b.resellerDetailFillExcelAna(b.excelTempDir+"/"+"kshj_total_ana.xlsx", filePath, totalDetail, title)
if err != nil { // if err != nil {
return // return
} // }
report[2] = &ReportRes{ // report[2] = &ReportRes{
ReportName: "负利润分析(亏损100以上)", // ReportName: "负利润分析(亏损100以上)",
Title: "截至" + timeCh + "亏损100以上利润原因", // Title: "截至" + timeCh + "亏损100以上利润原因",
Path: filePath, // Path: filePath,
Data: total, // Data: total,
} // }
} //}
return report, nil return report, nil
} }
@ -310,13 +263,13 @@ func (b *BbxtTools) GetResellerLossMannagerAndLossReasonFromApi(ctx context.Cont
for _, product := range v.ProductLoss { for _, product := range v.ProductLoss {
relationMap[v.ResellerId].Products[product.ProductId] = &LossReason{ relationMap[v.ResellerId].Products[product.ProductId] = &LossReason{
ProductName: product.ProductName, ProductName: product.ProductName,
LossReason: "", // 初始化为未填写 LossReason: "未填写", // 初始化为未填写
} }
} }
} }
res, err := GetManagerAndDefaultLossReasonApi(&GetManagerAndDefaultLossReasonRequest{ res, err := GetManagerAndDefaultLossReasonApi(&GetManagerAndDefaultLossReasonRequest{
Param: resellerMap, Param: resellerMap,
}, b.login.GetToken(ctx), b.config.Tools.ZltxResellerAuthProductToManagerAndDefaultLossReason.BaseURL) }, b.login.GetToken(ctx), b.config.ZLTX.ReqUrl)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -330,12 +283,6 @@ func (b *BbxtTools) GetResellerLossMannagerAndLossReasonFromApi(ctx context.Cont
relationMap[v.ResellerInfo.ResellerId].AfterSaleName = v.ResellerInfo.AfterSaleName relationMap[v.ResellerInfo.ResellerId].AfterSaleName = v.ResellerInfo.AfterSaleName
for _, vv := range v.ProductList { for _, vv := range v.ProductList {
if _, ex := relationMap[v.ResellerInfo.ResellerId].Products[vv.ProductId]; !ex {
continue
}
if len(vv.LossReason) == 0 {
continue
}
relationMap[v.ResellerInfo.ResellerId].Products[vv.ProductId].LossReason = vv.LossReason relationMap[v.ResellerInfo.ResellerId].Products[vv.ProductId].LossReason = vv.LossReason
} }
} }

View File

@ -72,7 +72,8 @@ func Test_GetStatisOfficialProductSumDecline(t *testing.T) {
} }
s := "官方--腾讯-周卡,官方--腾讯-月卡,官方--腾讯-季卡,官方--腾讯-年卡,官方--优酷周卡,官方--优酷月卡,官方--优酷季卡,官方--优酷年卡,官方--爱奇艺-周卡,官方--爱奇艺-月卡,官方--爱奇艺-季卡,官方--爱奇艺-年卡,官方--芒果-PC周卡,官方--芒果-PC月卡,官方--芒果-PC季卡,官方--美团外卖红包5元,官方--美团外卖红包10元,官方--QQ音乐-绿钻月卡,官方--饿了么超级会员月卡,官方--网易云黑胶vip月卡,官方--喜马拉雅巅峰会员月卡" s := "官方--腾讯-周卡,官方--腾讯-月卡,官方--腾讯-季卡,官方--腾讯-年卡,官方--优酷周卡,官方--优酷月卡,官方--优酷季卡,官方--优酷年卡,官方--爱奇艺-周卡,官方--爱奇艺-月卡,官方--爱奇艺-季卡,官方--爱奇艺-年卡,官方--芒果-PC周卡,官方--芒果-PC月卡,官方--芒果-PC季卡,官方--美团外卖红包5元,官方--美团外卖红包10元,官方--QQ音乐-绿钻月卡,官方--饿了么超级会员月卡,官方--网易云黑胶vip月卡,官方--喜马拉雅巅峰会员月卡"
//s := "官方--QQ音乐-绿钻月卡" //s := "官方--QQ音乐-绿钻月卡"
report, err := o.GetStatisOfficialProductSumDecline(time.Now(), 1000, strings.Split(s, ","), -150) now := time.Now()
report, err := o.GetStatisOfficialProductSumDecline(time.Date(now.Year(), now.Month(), now.Day(), 12, 0, 0, 0, now.Location()), 1000, strings.Split(s, ","), -150)
t.Log(report, err) t.Log(report, err)
@ -110,8 +111,8 @@ func GetReportCache(ctx context.Context, day time.Time, totalDetail []*ResellerL
var ResellerProductRelation map[int32]*ResellerLossSumProductRelation var ResellerProductRelation map[int32]*ResellerLossSumProductRelation
dayDate := day.Format(time.DateOnly) dayDate := day.Format(time.DateOnly)
cond := builder.NewCond() cond := builder.NewCond()
cond = cond.And(builder.Eq{"cache_index": IndexLossSumDetail}) cond = cond.And(builder.Eq{"`index`": IndexLossSumDetail})
cond = cond.And(builder.Eq{"cache_key": dayDate}) cond = cond.And(builder.Eq{"`key`": dayDate})
var cache model.AiReportDailyCache var cache model.AiReportDailyCache
err := reportDailyCacheImpl.GetOneBySearchToStrut(&cond, &cache) err := reportDailyCacheImpl.GetOneBySearchToStrut(&cond, &cache)
@ -124,8 +125,8 @@ func GetReportCache(ctx context.Context, day time.Time, totalDetail []*ResellerL
return err return err
} }
cache = model.AiReportDailyCache{ cache = model.AiReportDailyCache{
CacheKey: dayDate, Key: dayDate,
CacheIndex: IndexLossSumDetail, Index: IndexLossSumDetail,
Value: pkg.JsonStringIgonErr(ResellerProductRelation), Value: pkg.JsonStringIgonErr(ResellerProductRelation),
} }
_, err = reportDailyCacheImpl.Add(&cache) _, err = reportDailyCacheImpl.Add(&cache)

View File

@ -6,7 +6,6 @@ import (
"ai_scheduler/internal/pkg/utils_ollama" "ai_scheduler/internal/pkg/utils_ollama"
"ai_scheduler/internal/tools/public" "ai_scheduler/internal/tools/public"
zltxtool "ai_scheduler/internal/tools/zltx" zltxtool "ai_scheduler/internal/tools/zltx"
"ai_scheduler/utils"
"context" "context"
"fmt" "fmt"
@ -16,11 +15,10 @@ import (
type Manager struct { type Manager struct {
tools map[string]entitys.Tool tools map[string]entitys.Tool
llm *utils_ollama.Client llm *utils_ollama.Client
rdb *utils.Rdb
} }
// NewManager 创建工具管理器 // NewManager 创建工具管理器
func NewManager(config *config.Config, llm *utils_ollama.Client, rdb *utils.Rdb) *Manager { func NewManager(config *config.Config, llm *utils_ollama.Client) *Manager {
m := &Manager{ m := &Manager{
tools: make(map[string]entitys.Tool), tools: make(map[string]entitys.Tool),
llm: llm, llm: llm,
@ -28,7 +26,7 @@ func NewManager(config *config.Config, llm *utils_ollama.Client, rdb *utils.Rdb)
// 注册直连天下订单详情工具 // 注册直连天下订单详情工具
if config.Tools.ZltxOrderDetail.Enabled { if config.Tools.ZltxOrderDetail.Enabled {
zltxOrderDetailTool := zltxtool.NewZltxOrderDetailTool(config, rdb, m.llm) zltxOrderDetailTool := zltxtool.NewZltxOrderDetailTool(config.Tools.ZltxOrderDetail, m.llm)
m.tools[zltxOrderDetailTool.Name()] = zltxOrderDetailTool m.tools[zltxOrderDetailTool.Name()] = zltxOrderDetailTool
} }
@ -45,7 +43,7 @@ func NewManager(config *config.Config, llm *utils_ollama.Client, rdb *utils.Rdb)
} }
//注册直连天下订单统计工具 //注册直连天下订单统计工具
if config.Tools.ZltxOrderStatistics.Enabled { if config.Tools.ZltxOrderStatistics.Enabled {
zltxOrderStatisticsTool := zltxtool.NewZltxOrderStatisticsTool(config, rdb) zltxOrderStatisticsTool := zltxtool.NewZltxOrderStatisticsTool(config.Tools.ZltxOrderStatistics)
m.tools[zltxOrderStatisticsTool.Name()] = zltxOrderStatisticsTool m.tools[zltxOrderStatisticsTool.Name()] = zltxOrderStatisticsTool
} }
@ -106,10 +104,5 @@ func (m *Manager) ExecuteTool(ctx context.Context, name string, rec *entitys.Rec
return fmt.Errorf("tool not found: %s", name) return fmt.Errorf("tool not found: %s", name)
} }
return m.ExecuteToolWithToolObj(ctx, tool, rec)
}
// ExecuteTool 执行工具
func (m *Manager) ExecuteToolWithToolObj(ctx context.Context, tool entitys.Tool, rec *entitys.Recognize) error {
return tool.Execute(ctx, rec) return tool.Execute(ctx, rec)
} }

View File

@ -50,7 +50,7 @@ func (c *CozeCompany) Description() string {
} }
// Definition 返回工具定义 // Definition 返回工具定义
func (c *CozeCompany) Definition(ctx context.Context) entitys.ToolDefinition { func (c *CozeCompany) Definition() entitys.ToolDefinition {
return entitys.ToolDefinition{ return entitys.ToolDefinition{
Type: "function", Type: "function",
Function: entitys.FunctionDef{ Function: entitys.FunctionDef{

View File

@ -47,7 +47,7 @@ func (c *CozeExpress) Description() string {
} }
// Definition 返回工具定义 // Definition 返回工具定义
func (c *CozeExpress) Definition(ctx context.Context) entitys.ToolDefinition { func (c *CozeExpress) Definition() entitys.ToolDefinition {
return entitys.ToolDefinition{ return entitys.ToolDefinition{
Type: "function", Type: "function",
Function: entitys.FunctionDef{ Function: entitys.FunctionDef{

View File

@ -38,7 +38,7 @@ func (k *KnowledgeBaseTool) Description() string {
} }
// Definition 返回工具定义 // Definition 返回工具定义
func (k *KnowledgeBaseTool) Definition(ctx context.Context) entitys.ToolDefinition { func (k *KnowledgeBaseTool) Definition() entitys.ToolDefinition {
return entitys.ToolDefinition{ return entitys.ToolDefinition{
Type: "function", Type: "function",
Function: entitys.FunctionDef{ Function: entitys.FunctionDef{

View File

@ -38,7 +38,7 @@ type NormalChat struct {
} }
// Definition 返回工具定义 // Definition 返回工具定义
func (w *NormalChatTool) Definition(ctx context.Context) entitys.ToolDefinition { func (w *NormalChatTool) Definition() entitys.ToolDefinition {
return entitys.ToolDefinition{} return entitys.ToolDefinition{}
} }

View File

@ -35,7 +35,7 @@ func (w *WeatherTool) Description() string {
} }
// Definition 返回工具定义 // Definition 返回工具定义
func (w *WeatherTool) Definition(ctx context.Context) entitys.ToolDefinition { func (w *WeatherTool) Definition() entitys.ToolDefinition {
return entitys.ToolDefinition{ return entitys.ToolDefinition{
Type: "function", Type: "function",
Function: entitys.FunctionDef{ Function: entitys.FunctionDef{

View File

@ -33,7 +33,7 @@ func (t *OrderAfterSaleResellerTool) Description() string {
} }
// 未使用-仅实现接口 // 未使用-仅实现接口
func (t *OrderAfterSaleResellerTool) Definition(ctx context.Context) entitys.ToolDefinition { func (t *OrderAfterSaleResellerTool) Definition() entitys.ToolDefinition {
return entitys.ToolDefinition{} return entitys.ToolDefinition{}
} }

View File

@ -31,7 +31,7 @@ func (t *OrderAfterSaleResellerBatchTool) Description() string {
} }
// 未使用-仅实现接口 // 未使用-仅实现接口
func (t *OrderAfterSaleResellerBatchTool) Definition(ctx context.Context) entitys.ToolDefinition { func (t *OrderAfterSaleResellerBatchTool) Definition() entitys.ToolDefinition {
return entitys.ToolDefinition{} return entitys.ToolDefinition{}
} }

View File

@ -33,7 +33,7 @@ func (t *OrderAfterSaleSupplierTool) Description() string {
} }
// 未使用-仅实现接口 // 未使用-仅实现接口
func (t *OrderAfterSaleSupplierTool) Definition(ctx context.Context) entitys.ToolDefinition { func (t *OrderAfterSaleSupplierTool) Definition() entitys.ToolDefinition {
return entitys.ToolDefinition{} return entitys.ToolDefinition{}
} }

View File

@ -4,28 +4,26 @@ import (
"ai_scheduler/internal/config" "ai_scheduler/internal/config"
"ai_scheduler/internal/entitys" "ai_scheduler/internal/entitys"
"ai_scheduler/internal/pkg" "ai_scheduler/internal/pkg"
"ai_scheduler/internal/pkg/lsxd"
"ai_scheduler/internal/pkg/rec_extra" "ai_scheduler/internal/pkg/rec_extra"
"ai_scheduler/internal/pkg/utils_ollama" "ai_scheduler/internal/pkg/utils_ollama"
"ai_scheduler/utils"
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"gitea.cdlsxd.cn/self-tools/l_request" "gitea.cdlsxd.cn/self-tools/l_request"
"github.com/gofiber/fiber/v2/log"
"github.com/ollama/ollama/api" "github.com/ollama/ollama/api"
) )
// ZltxOrderDetailTool 直连天下订单详情工具 // ZltxOrderDetailTool 直连天下订单详情工具
type ZltxOrderDetailTool struct { type ZltxOrderDetailTool struct {
config *config.Config config config.ToolConfig
llm *utils_ollama.Client llm *utils_ollama.Client
rdb *utils.Rdb
} }
// NewZltxOrderDetailTool 创建直连天下订单详情工具 // NewZltxOrderDetailTool 创建直连天下订单详情工具
func NewZltxOrderDetailTool(config *config.Config, rdb *utils.Rdb, llm *utils_ollama.Client) *ZltxOrderDetailTool { func NewZltxOrderDetailTool(config config.ToolConfig, llm *utils_ollama.Client) *ZltxOrderDetailTool {
return &ZltxOrderDetailTool{config: config, rdb: rdb, llm: llm} return &ZltxOrderDetailTool{config: config, llm: llm}
} }
// Name 返回工具名称 // Name 返回工具名称
@ -39,7 +37,7 @@ func (w *ZltxOrderDetailTool) Description() string {
} }
// Definition 返回工具定义 // Definition 返回工具定义
func (w *ZltxOrderDetailTool) Definition(ctx context.Context) entitys.ToolDefinition { func (w *ZltxOrderDetailTool) Definition() entitys.ToolDefinition {
return entitys.ToolDefinition{ return entitys.ToolDefinition{
Type: "function", Type: "function",
Function: entitys.FunctionDef{ Function: entitys.FunctionDef{
@ -56,19 +54,6 @@ func (w *ZltxOrderDetailTool) Definition(ctx context.Context) entitys.ToolDefini
"required": []string{"number"}, "required": []string{"number"},
}, },
}, },
AuthFunc: func(rec *entitys.Recognize) ([]byte, error) {
ext, err := rec_extra.GetTaskRecExt(rec)
if err != nil {
return nil, err
}
if len(ext.Auth) > 0 {
return []byte(ext.Auth), nil
} else {
login := lsxd.NewLogin(w.config, w.rdb)
token := login.GetToken(ctx)
return []byte(token), nil
}
},
} }
} }
@ -108,7 +93,7 @@ func (w *ZltxOrderDetailTool) Execute(ctx context.Context, rec *entitys.Recogniz
} }
// 这里可以集成真实的直连天下订单详情API // 这里可以集成真实的直连天下订单详情API
return w.getZltxOrderDetail(ctx, rec, req.OrderNumber) return w.getZltxOrderDetail(rec, req.OrderNumber)
} }
func (r *ZltxOrderDetailRequest) UnmarshalJSON(data []byte) error { func (r *ZltxOrderDetailRequest) UnmarshalJSON(data []byte) error {
@ -128,8 +113,8 @@ func (r *ZltxOrderDetailRequest) UnmarshalJSON(data []byte) error {
} }
// getMockZltxOrderDetail 获取模拟直连天下订单详情数据 // getMockZltxOrderDetail 获取模拟直连天下订单详情数据
func (w *ZltxOrderDetailTool) getZltxOrderDetail(ctx context.Context, rec *entitys.Recognize, number interface{}) (err error) { func (w *ZltxOrderDetailTool) getZltxOrderDetail(rec *entitys.Recognize, number interface{}) (err error) {
log.Infof("订单编号:%v,类型:%v")
var orderNum string var orderNum string
switch number.(type) { switch number.(type) {
case int, int32, int64: case int, int32, int64:
@ -142,15 +127,15 @@ func (w *ZltxOrderDetailTool) getZltxOrderDetail(ctx context.Context, rec *entit
orderNum = fmt.Sprintf("%v", number) orderNum = fmt.Sprintf("%v", number)
} }
token, err := w.Definition(ctx).AuthFunc(rec) ext, err := rec_extra.GetTaskRecExt(rec)
if err != nil { if err != nil {
return err return
} }
//查询订单详情 //查询订单详情
req := l_request.Request{ req := l_request.Request{
Url: fmt.Sprintf(w.config.Tools.ZltxOrderDetail.BaseURL, orderNum), Url: fmt.Sprintf(w.config.BaseURL, orderNum),
Headers: map[string]string{ Headers: map[string]string{
"Authorization": fmt.Sprintf("Bearer %s", token), "Authorization": fmt.Sprintf("Bearer %s", ext.Auth),
}, },
Method: "GET", Method: "GET",
} }
@ -175,10 +160,11 @@ func (w *ZltxOrderDetailTool) getZltxOrderDetail(ctx context.Context, rec *entit
if resData.Data.Direct != nil { if resData.Data.Direct != nil {
entitys.ResLoading(rec.Ch, w.Name(), "正在分析订单日志") entitys.ResLoading(rec.Ch, w.Name(), "正在分析订单日志")
req = l_request.Request{ req = l_request.Request{
Url: fmt.Sprintf(w.config.Tools.ZltxOrderDetail.AddURL, resData.Data.Direct["orderOrderNumber"].(string), resData.Data.Direct["serialNumber"].(string)), Url: fmt.Sprintf(w.config.AddURL, resData.Data.Direct["orderOrderNumber"].(string), resData.Data.Direct["serialNumber"].(string)),
Headers: map[string]string{ Headers: map[string]string{
"Authorization": fmt.Sprintf("Bearer %s", token), "Authorization": fmt.Sprintf("Bearer %s", ext.Auth),
}, },
Method: "GET", Method: "GET",
} }
@ -198,7 +184,7 @@ func (w *ZltxOrderDetailTool) getZltxOrderDetail(ctx context.Context, rec *entit
return fmt.Errorf("订单日志解析失败:%s", err) return fmt.Errorf("订单日志解析失败:%s", err)
} }
err = w.llm.ChatStream(ctx, rec.Ch, []api.Message{ err = w.llm.ChatStream(context.TODO(), rec.Ch, []api.Message{
{ {
Role: "system", Role: "system",
Content: "你是一个订单日志助手。用户可能会提供订单日志,你需要按以下规则处理:\n" + Content: "你是一个订单日志助手。用户可能会提供订单日志,你需要按以下规则处理:\n" +

View File

@ -23,7 +23,7 @@ func (t *ZltxOrderLogTool) Description() string {
return "查询订单日志" return "查询订单日志"
} }
func (t *ZltxOrderLogTool) Definition(ctx context.Context) entitys.ToolDefinition { func (t *ZltxOrderLogTool) Definition() entitys.ToolDefinition {
return entitys.ToolDefinition{ return entitys.ToolDefinition{
Type: "function", Type: "function",
Function: entitys.FunctionDef{ Function: entitys.FunctionDef{

View File

@ -25,7 +25,7 @@ func (z ZltxProductTool) Description() string {
return "获取直连天下商品信息" return "获取直连天下商品信息"
} }
func (z ZltxProductTool) Definition(ctx context.Context) entitys.ToolDefinition { func (z ZltxProductTool) Definition() entitys.ToolDefinition {
return entitys.ToolDefinition{ return entitys.ToolDefinition{
Type: "function", Type: "function",
Function: entitys.FunctionDef{ Function: entitys.FunctionDef{

View File

@ -3,9 +3,7 @@ package zltx
import ( import (
"ai_scheduler/internal/config" "ai_scheduler/internal/config"
"ai_scheduler/internal/entitys" "ai_scheduler/internal/entitys"
"ai_scheduler/internal/pkg/lsxd"
"ai_scheduler/internal/pkg/rec_extra" "ai_scheduler/internal/pkg/rec_extra"
"ai_scheduler/utils"
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
@ -15,8 +13,7 @@ import (
) )
type ZltxOrderStatisticsTool struct { type ZltxOrderStatisticsTool struct {
config *config.Config config config.ToolConfig
rdb *utils.Rdb
} }
func (z ZltxOrderStatisticsTool) Name() string { func (z ZltxOrderStatisticsTool) Name() string {
@ -27,7 +24,7 @@ func (z ZltxOrderStatisticsTool) Description() string {
return "通过账号获取订单统计信息" return "通过账号获取订单统计信息"
} }
func (z ZltxOrderStatisticsTool) Definition(ctx context.Context) entitys.ToolDefinition { func (z ZltxOrderStatisticsTool) Definition() entitys.ToolDefinition {
return entitys.ToolDefinition{ return entitys.ToolDefinition{
Type: "function", Type: "function",
Function: entitys.FunctionDef{ Function: entitys.FunctionDef{
@ -44,19 +41,6 @@ func (z ZltxOrderStatisticsTool) Definition(ctx context.Context) entitys.ToolDef
"required": []string{"number"}, "required": []string{"number"},
}, },
}, },
AuthFunc: func(rec *entitys.Recognize) ([]byte, error) {
ext, err := rec_extra.GetTaskRecExt(rec)
if err != nil {
return nil, err
}
if len(ext.Auth) > 0 {
return []byte(ext.Auth), nil
} else {
login := lsxd.NewLogin(z.config, z.rdb)
token := login.GetToken(ctx)
return []byte(token), nil
}
},
} }
} }
@ -72,7 +56,7 @@ func (z ZltxOrderStatisticsTool) Execute(ctx context.Context, rec *entitys.Recog
if req.Number == nil { if req.Number == nil {
return fmt.Errorf("number is required") return fmt.Errorf("number is required")
} }
return z.getZltxOrderStatistics(ctx, req.Number, rec) return z.getZltxOrderStatistics(req.Number, rec)
} }
type ZltxOrderStatisticsResponse struct { type ZltxOrderStatisticsResponse struct {
@ -92,17 +76,17 @@ type ZltxOrderStatisticsData struct {
Total int `json:"total"` Total int `json:"total"`
} }
func (z ZltxOrderStatisticsTool) getZltxOrderStatistics(ctx context.Context, number interface{}, rec *entitys.Recognize) error { func (z ZltxOrderStatisticsTool) getZltxOrderStatistics(number interface{}, rec *entitys.Recognize) error {
token, err := z.Definition(ctx).AuthFunc(rec) ext, err := rec_extra.GetTaskRecExt(rec)
if err != nil { if err != nil {
return err return err
} }
//查询订单详情 //查询订单详情
url := fmt.Sprintf("%s%s", z.config.Tools.ZltxOrderStatistics.BaseURL, fmt.Sprintf("%v", number)) url := fmt.Sprintf("%s%s", z.config.BaseURL, fmt.Sprintf("%v", number))
req := l_request.Request{ req := l_request.Request{
Url: url, Url: url,
Headers: map[string]string{ Headers: map[string]string{
"Authorization": fmt.Sprintf("Bearer %s", string(token)), "Authorization": fmt.Sprintf("Bearer %s", ext.Auth),
}, },
Method: "GET", Method: "GET",
} }
@ -132,9 +116,8 @@ func (z ZltxOrderStatisticsTool) getZltxOrderStatistics(ctx context.Context, num
return nil return nil
} }
func NewZltxOrderStatisticsTool(config *config.Config, rdb *utils.Rdb) *ZltxOrderStatisticsTool { func NewZltxOrderStatisticsTool(config config.ToolConfig) *ZltxOrderStatisticsTool {
return &ZltxOrderStatisticsTool{ return &ZltxOrderStatisticsTool{
config: config, config: config,
rdb: rdb,
} }
} }

Binary file not shown.

Binary file not shown.