Compare commits

...

4 Commits

Author SHA1 Message Date
fuzhongyun a8cfb118fe feat: dev 2025-11-21 15:04:15 +08:00
fuzhongyun ed6ea71bb0 Merge branch 'v3' into feature/v2-fzy 2025-11-21 11:59:31 +08:00
fuzhongyun 4a0cdacbe9 feat: 暂存 2025-11-20 19:25:52 +08:00
renzhiyuan fca01b6f94 feat: 优化聊天功能与模型配置 2025-11-20 15:42:47 +08:00
25 changed files with 414 additions and 234 deletions

File diff suppressed because one or more lines are too long

View File

@ -10,7 +10,7 @@ import (
) )
func main() { func main() {
configPath := flag.String("config", "./config/config.yaml", "Path to configuration file") configPath := flag.String("config", "./config/config_test.yaml", "Path to configuration file")
flag.Parse() flag.Parse()
bc, err := config.LoadConfig(*configPath) bc, err := config.LoadConfig(*configPath)
if err != nil { if err != nil {

View File

@ -7,7 +7,7 @@ ollama:
base_url: "http://127.0.0.1:11434" base_url: "http://127.0.0.1:11434"
model: "qwen3-coder:480b-cloud" model: "qwen3-coder:480b-cloud"
generate_model: "qwen3-coder:480b-cloud" generate_model: "qwen3-coder:480b-cloud"
vl_model: "qwen2.5vl:7b" vl_model: "qwen2.5vl:3b"
timeout: "120s" timeout: "120s"
level: "info" level: "info"
format: "json" format: "json"
@ -55,6 +55,9 @@ tools:
enabled: true enabled: true
api_key: "dingsbbntrkeiyazcfdg" api_key: "dingsbbntrkeiyazcfdg"
api_secret: "ObqxwyR20r9rVNhju0sCPQyQA98_FZSc32W4vgxnGFH_b02HZr1BPCJsOAF816nu" api_secret: "ObqxwyR20r9rVNhju0sCPQyQA98_FZSc32W4vgxnGFH_b02HZr1BPCJsOAF816nu"
zltxOrderAfterSaleSupplier:
enabled: true
base_url: "https://revcl.1688sup.com/api/admin/afterSales/reseller_supplier"
default_prompt: default_prompt:
img_recognize: img_recognize:

2
gen.sh
View File

@ -16,4 +16,4 @@
gentool --dsn "root:SD###sdf323r343@tcp(121.199.38.107:3306)/sys_ai?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai" -outPath ${modeldir} -onlyModel -modelPkgName "model" -tables ${prefix}${tables} gentool --dsn "root:SD###sdf323r343@tcp(121.199.38.107:3306)/sys_ai_test?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai" -outPath ${modeldir} -onlyModel -modelPkgName "model" -tables ${prefix}${tables}

BIN
img.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 MiB

View File

@ -5,6 +5,8 @@ import (
"ai_scheduler/internal/data/model" "ai_scheduler/internal/data/model"
"ai_scheduler/internal/entitys" "ai_scheduler/internal/entitys"
"context" "context"
"xorm.io/builder"
) )
type ChatHistoryBiz struct { type ChatHistoryBiz struct {
@ -15,35 +17,41 @@ func NewChatHistoryBiz(chatRepo *impl.ChatImpl) *ChatHistoryBiz {
s := &ChatHistoryBiz{ s := &ChatHistoryBiz{
chatRepo: chatRepo, chatRepo: chatRepo,
} }
go s.AsyncProcess(context.Background()) //go s.AsyncProcess(context.Background())
return s return s
} }
func (s *ChatHistoryBiz) create(ctx context.Context, sessionID, role, content string) error { //func (s *ChatHistoryBiz) create(ctx context.Context, sessionID, role, content string) error {
chat := model.AiChatHi{ // chat := model.AiChatHi{
SessionID: sessionID, // SessionID: sessionID,
Role: role, // Role: role,
Content: content, // Content: content,
} // }
//
return s.chatRepo.Create(&chat) // return s.chatRepo.Create(&chat)
} //}
//
// 添加会话历史 //// 添加会话历史
func (s *ChatHistoryBiz) Create(ctx context.Context, chat entitys.ChatHistory) error { //func (s *ChatHistoryBiz) Create(ctx context.Context, chat entitys.ChatHistory) error {
return s.create(ctx, chat.SessionID, chat.Role.String(), chat.Content) // return s.create(ctx, chat.SessionID, chat.Role.String(), chat.Content)
} //}
// 异步添加会话历史 // 异步添加会话历史
func (s *ChatHistoryBiz) AsyncCreate(ctx context.Context, chat entitys.ChatHistory) { //func (s *ChatHistoryBiz) AsyncCreate(ctx context.Context, chat entitys.ChatHistory) {
s.chatRepo.AsyncCreate(ctx, model.AiChatHi{ // s.chatRepo.AsyncCreate(ctx, model.AiChatHi{
SessionID: chat.SessionID, // SessionID: chat.SessionID,
Role: chat.Role.String(), // Role: chat.Role.String(),
Content: chat.Content, // Content: chat.Content,
}) // })
} //}
// 异步处理会话历史 // 异步处理会话历史
func (s *ChatHistoryBiz) AsyncProcess(ctx context.Context) { //func (s *ChatHistoryBiz) AsyncProcess(ctx context.Context) {
s.chatRepo.AsyncProcess(ctx) // s.chatRepo.AsyncProcess(ctx)
//}
func (s *ChatHistoryBiz) Update(ctx context.Context, chat *entitys.UseFulRequest) error {
cond := builder.NewCond()
cond = cond.And(builder.Eq{"his_id": chat.HisId})
return s.chatRepo.UpdateByCond(&cond, &model.AiChatHi{HisID: chat.HisId, Useful: chat.Useful})
} }

View File

@ -94,7 +94,7 @@ func (d *Do) DataAuth(c *websocket.Conn) (err error) {
func (d *Do) MakeCh(c *websocket.Conn) (ctx context.Context, deferFunc func()) { func (d *Do) MakeCh(c *websocket.Conn) (ctx context.Context, deferFunc func()) {
d.Ctx.Ch = make(chan entitys.Response) d.Ctx.Ch = make(chan entitys.Response)
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
done := d.startMessageHandler(ctx, c, d.hisImpl) done := d.startMessageHandler(ctx, c)
return ctx, func() { return ctx, func() {
close(d.Ctx.Ch) //关闭主通道 close(d.Ctx.Ch) //关闭主通道
<-done // 等待消息处理完成 <-done // 等待消息处理完成
@ -186,7 +186,7 @@ func (d *Do) getTasks(sysId int32) (tasks []model.AiTask, err error) {
func (d *Do) startMessageHandler( func (d *Do) startMessageHandler(
ctx context.Context, ctx context.Context,
c *websocket.Conn, c *websocket.Conn,
hisImpl *impl.ChatImpl,
) <-chan struct{} { ) <-chan struct{} {
done := make(chan struct{}) done := make(chan struct{})
var chat []string var chat []string
@ -195,23 +195,25 @@ func (d *Do) startMessageHandler(
defer func() { defer func() {
close(done) close(done)
// 保存历史记录 // 保存历史记录
var his = []*model.AiChatHi{ var (
{ hisLog = &entitys.ChatHisLog{}
SessionID: d.Ctx.Session, )
Role: "user",
Content: d.Ctx.Req.Text, // 用户输入在外部处理
},
}
if len(chat) > 0 { if len(chat) > 0 {
his = append(his, &model.AiChatHi{ AiRes := &model.AiChatHi{
SessionID: d.Ctx.Session, SessionID: d.Ctx.Session,
Role: "assistant", Ques: d.Ctx.Req.Text,
Content: strings.Join(chat, ""), Ans: strings.Join(chat, ""),
}) Files: d.Ctx.Req.Img,
} }
for _, hi := range his { d.hisImpl.AddWithData(AiRes)
hisImpl.Add(hi) hisLog.HisId = AiRes.HisID
} }
_ = entitys.MsgSend(c, entitys.Response{
Content: pkg.JsonStringIgonErr(hisLog),
Type: entitys.ResponseEnd,
})
}() }()
for v := range d.Ctx.Ch { // 自动检测通道关闭 for v := range d.Ctx.Ch { // 自动检测通道关闭

View File

@ -90,7 +90,7 @@ func (r *Handle) HandleMatch(ctx context.Context, requireData *entitys.RequireDa
if pointTask == nil || pointTask.Index == "other" { if pointTask == nil || pointTask.Index == "other" {
return r.OtherTask(ctx, requireData) return r.OtherTask(ctx, requireData)
} }
switch pointTask.Type { switch constants.TaskType(pointTask.Type) {
case constants.TaskTypeApi: case constants.TaskTypeApi:
return r.handleApiTask(ctx, requireData, pointTask) return r.handleApiTask(ctx, requireData, pointTask)
case constants.TaskTypeFunc: case constants.TaskTypeFunc:

View File

@ -1,9 +1,11 @@
package llm_service package llm_service
import ( import (
"ai_scheduler/internal/data/constants"
"ai_scheduler/internal/data/model" "ai_scheduler/internal/data/model"
"ai_scheduler/internal/entitys" "ai_scheduler/internal/entitys"
"context" "context"
"time"
) )
type LlmService interface { type LlmService interface {
@ -24,11 +26,18 @@ func buildAssistant(his []model.AiChatHi) (chatHis entitys.ChatHis) {
if len(chatHis.SessionId) == 0 { if len(chatHis.SessionId) == 0 {
chatHis.SessionId = item.SessionID chatHis.SessionId = item.SessionID
} }
chatHis.Messages = append(chatHis.Messages, entitys.HisMessage{ chatHis.Messages = append(chatHis.Messages, []entitys.HisMessage{
Role: item.Role, {
Content: item.Content, Role: constants.RoleUser,
Timestamp: item.CreateAt.Format("2006-01-02 15:04:05"), Content: item.Ques,
}) Timestamp: item.CreateAt.Format(time.DateTime),
},
{
Role: constants.RoleAssistant,
Content: item.Ans,
Timestamp: item.CreateAt.Format(time.DateTime),
},
}...)
} }
chatHis.Context = entitys.HisContext{ chatHis.Context = entitys.HisContext{
UserLanguage: "zh-CN", UserLanguage: "zh-CN",
@ -36,3 +45,23 @@ func buildAssistant(his []model.AiChatHi) (chatHis entitys.ChatHis) {
} }
return return
} }
func BuildChatHisMessage(his []model.AiChatHi) (chatHis []entitys.HisMessage) {
for _, item := range his {
chatHis = append(chatHis, []entitys.HisMessage{
{
Role: constants.RoleUser,
Content: item.Ques,
Timestamp: item.CreateAt.Format(time.DateTime),
},
{
Role: constants.RoleAssistant,
Content: item.Ans,
Timestamp: item.CreateAt.Format(time.DateTime),
},
}...)
}
return
}

View File

@ -2,6 +2,7 @@ package llm_service
import ( import (
"ai_scheduler/internal/config" "ai_scheduler/internal/config"
"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/pkg"
@ -13,20 +14,24 @@ import (
"time" "time"
"github.com/ollama/ollama/api" "github.com/ollama/ollama/api"
"xorm.io/builder"
) )
type OllamaService struct { type OllamaService struct {
client *utils_ollama.Client client *utils_ollama.Client
config *config.Config config *config.Config
chatHis *impl.ChatImpl
} }
func NewOllamaGenerate( func NewOllamaGenerate(
client *utils_ollama.Client, client *utils_ollama.Client,
config *config.Config, config *config.Config,
chatHis *impl.ChatImpl,
) *OllamaService { ) *OllamaService {
return &OllamaService{ return &OllamaService{
client: client, client: client,
config: config, config: config,
chatHis: chatHis,
} }
} }
@ -66,6 +71,10 @@ func (r *OllamaService) getPrompt(ctx context.Context, requireData *entitys.Requ
var ( var (
prompt = make([]api.Message, 0) prompt = make([]api.Message, 0)
) )
content, err := r.getUserContent(ctx, requireData)
if err != nil {
return nil, err
}
prompt = append(prompt, api.Message{ prompt = append(prompt, api.Message{
Role: "system", Role: "system",
Content: buildSystemPrompt(requireData.Sys.SysPrompt), Content: buildSystemPrompt(requireData.Sys.SysPrompt),
@ -74,32 +83,17 @@ func (r *OllamaService) getPrompt(ctx context.Context, requireData *entitys.Requ
Content: "### 聊天记录:" + pkg.JsonStringIgonErr(buildAssistant(requireData.Histories)), Content: "### 聊天记录:" + pkg.JsonStringIgonErr(buildAssistant(requireData.Histories)),
}, api.Message{ }, api.Message{
Role: "user", Role: "user",
Content: r.getUserContent(requireData), Content: content,
//Images: requireData.ImgByte,
}) })
if len(requireData.ImgByte) > 0 {
desc, err := r.RecognizeWithImg(ctx, requireData)
if err != nil {
return nil, err
}
var imgs strings.Builder
imgs.WriteString("### 用户上传图片解析内容:\n")
prompt = append(prompt, api.Message{
Role: "image_desc",
Content: "" + desc.Response,
})
}
return prompt, nil return prompt, nil
} }
func (r *OllamaService) getUserContent(requireData *entitys.RequireData) string { func (r *OllamaService) getUserContent(ctx context.Context, requireData *entitys.RequireData) (string, error) {
var content strings.Builder var content strings.Builder
content.WriteString(requireData.Req.Text) content.WriteString(requireData.Req.Text)
if len(requireData.ImgByte) > 0 { if len(requireData.ImgByte) > 0 {
content.WriteString("\n") content.WriteString("\n")
content.WriteString("### 图片内容已经解析到image_desc里")
} }
if len(requireData.Req.Tags) > 0 { if len(requireData.Req.Tags) > 0 {
@ -107,7 +101,29 @@ func (r *OllamaService) getUserContent(requireData *entitys.RequireData) string
content.WriteString("### 工具必须使用:") content.WriteString("### 工具必须使用:")
content.WriteString(requireData.Req.Tags) content.WriteString(requireData.Req.Tags)
} }
return content.String()
if len(requireData.ImgByte) > 0 {
desc, err := r.RecognizeWithImg(ctx, requireData)
if err != nil {
return "", err
}
content.WriteString("### 上传图片解析内容:\n")
content.WriteString(requireData.Req.Tags)
content.WriteString(desc.Response)
}
if requireData.Req.MarkHis > 0 {
var his model.AiChatHi
cond := builder.NewCond()
cond = cond.And(builder.Eq{"his_id": requireData.Req.MarkHis})
err := r.chatHis.GetOneBySearchToStrut(&cond, &his)
if err != nil {
return "", err
}
content.WriteString("### 引用历史聊天记录:\n")
content.WriteString(pkg.JsonStringIgonErr(BuildChatHisMessage([]model.AiChatHi{his})))
}
return content.String(), nil
} }
func (r *OllamaService) RecognizeWithImg(ctx context.Context, requireData *entitys.RequireData) (desc api.GenerateResponse, err error) { func (r *OllamaService) RecognizeWithImg(ctx context.Context, requireData *entitys.RequireData) (desc api.GenerateResponse, err error) {

View File

@ -80,11 +80,11 @@ func (s *SessionBiz) SessionInit(ctx context.Context, req *entitys.SessionInitRe
result.Prologue = sysConfig.Prologue result.Prologue = sysConfig.Prologue
// 开场白写入会话历史 // 开场白写入会话历史
s.chatRepo.AsyncCreate(ctx, model.AiChatHi{ //s.chatRepo.AsyncCreate(ctx, model.AiChatHi{
SessionID: chat.SessionID, // SessionID: chat.SessionID,
Role: chat.Role.String(), // Role: chat.Role.String(),
Content: chat.Content, // Content: chat.Content,
}) //})
} else { } else {
result.SessionId = session.SessionID result.SessionId = session.SessionID
@ -102,12 +102,20 @@ func (s *SessionBiz) SessionInit(ctx context.Context, req *entitys.SessionInitRe
// 转换为 entitys.ChatHistory 类型 // 转换为 entitys.ChatHistory 类型
for _, chat := range chatList { for _, chat := range chatList {
result.Chat = append(result.Chat, entitys.ChatHistory{ result.Chat = append(result.Chat, []entitys.ChatHistory{
SessionID: chat.SessionID, {
Role: constants.Caller(chat.Role), SessionID: chat.SessionID,
Content: chat.Content, Role: constants.RoleUser,
Prologue: sysConfig.Prologue, Content: chat.Ques,
}) Prologue: sysConfig.Prologue,
},
{
SessionID: chat.SessionID,
Role: constants.RoleAssistant,
Content: chat.Ans,
Prologue: sysConfig.Prologue,
},
}...)
} }
} }

View File

@ -93,6 +93,8 @@ type ToolsConfig struct {
ZltxOrderAfterSaleDetail ToolConfig `mapstructure:"zltxOrderAfterSaleDetail"` ZltxOrderAfterSaleDetail ToolConfig `mapstructure:"zltxOrderAfterSaleDetail"`
//下游订单预检 //下游订单预检
ZltxOrderAfterSalePreCheck ToolConfig `mapstructure:"zltxOrderAfterSalePreCheck"` ZltxOrderAfterSalePreCheck ToolConfig `mapstructure:"zltxOrderAfterSalePreCheck"`
// 上游订单售后
ZltxOrderAfterSaleSupplier ToolConfig `mapstructure:"zltxOrderAfterSaleSupplier"`
} }
// ToolConfig 单个工具配置 // ToolConfig 单个工具配置

View File

@ -3,5 +3,8 @@ package constants
type BotTools string type BotTools string
const ( const (
BotToolsBugOptimizationSubmit = "bug_optimization_submit" // 系统的bug/优化建议 BotToolsBugOptimizationSubmit = "bug_optimization_submit" // 系统的bug/优化建议
BotToolsAfterSalesSupplier = "after_sales_supplier" // 供应商售后
BotToolsAfterSalesResellerSingle = "after_sales_reseller_single" // 分销商单条售后
BotToolsAfterSalesResellerBatch = "after_sales_reseller_batch" // 分销商批量售后
) )

View File

@ -11,8 +11,22 @@ const (
type TaskType int32 type TaskType int32
const ( const (
TaskTypeApi = 1 TaskTypeApi TaskType = 1
TaskTypeKnowle = 2 TaskTypeKnowle TaskType = 2
TaskTypeFunc = 3 TaskTypeFunc TaskType = 3
TaskTypeBot = 4 TaskTypeBot TaskType = 4
) )
type UseFul int32
const (
UseFulNotSolve UseFul = 2
UseFulNotUnclear UseFul = 3
UseFulNotError UseFul = 4
)
var UseFulMap = map[UseFul]string{
UseFulNotSolve: "未解决问题",
UseFulNotUnclear: "回答不明确",
UseFulNotError: "理解错误",
}

View File

@ -12,11 +12,14 @@ const TableNameAiChatHi = "ai_chat_his"
// AiChatHi mapped from table <ai_chat_his> // AiChatHi mapped from table <ai_chat_his>
type AiChatHi struct { type AiChatHi struct {
HisID int64 `gorm:"column:his_id;primaryKey" 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"` SessionID string `gorm:"column:session_id;not null" json:"session_id"`
Role string `gorm:"column:role;not null;comment:system系统输出assistant助手输出,user用户输入" json:"role"` // system系统输出assistant助手输出,user用户输入 Ques string `gorm:"column:ques;not null" json:"ques"`
Content string `gorm:"column:content;not null" json:"content"` Ans string `gorm:"column:ans;not null" json:"ans"`
CreateAt *time.Time `gorm:"column:create_at;default:CURRENT_TIMESTAMP" json:"create_at"` Files string `gorm:"column:files;not null" json:"files"`
Useful int32 `gorm:"column:useful;not null;comment:0不评价,1有用其他为无用" json:"useful"` // 0不评价,1有用其他为无用
CreateAt time.Time `gorm:"column:create_at;default:CURRENT_TIMESTAMP" json:"create_at"`
UpdatedAt time.Time `gorm:"column:updated_at;default:CURRENT_TIMESTAMP" json:"updated_at"`
} }
// TableName AiChatHi's table name // TableName AiChatHi's table name

View File

@ -10,3 +10,7 @@ type ChatHistory struct {
Content string `json:"content"` Content string `json:"content"`
Prologue string `json:"prologue"` Prologue string `json:"prologue"`
} }
type ChatHisLog struct {
HisId int64 `json:"his_id"`
}

View File

@ -23,3 +23,8 @@ type SessionListRequest struct {
type TaskRequest struct { type TaskRequest struct {
SysId int32 `json:"sys_id"` SysId int32 `json:"sys_id"`
} }
type UseFulRequest struct {
HisId int64 `json:"his_id"`
Useful int32 `json:"useful"`
}

View File

@ -1,6 +1,7 @@
package entitys package entitys
import ( import (
"ai_scheduler/internal/data/constants"
"ai_scheduler/internal/data/model" "ai_scheduler/internal/data/model"
"context" "context"
@ -31,7 +32,9 @@ type FirstSockRequest struct {
type ChatSockRequest struct { type ChatSockRequest struct {
Text string `json:"text" binding:"required"` Text string `json:"text" binding:"required"`
Img string `json:"img" binding:"required"` Img string `json:"img" binding:"required"`
File string `json:"file" binding:"required"`
Tags string `json:"tags" binding:"required"` Tags string `json:"tags" binding:"required"`
MarkHis int64 `json:"mark_his" `
Caller string `json:"caller" binding:"required"` Caller string `json:"caller" binding:"required"`
SessionID string `json:"session_id"` SessionID string `json:"session_id"`
} }
@ -130,9 +133,9 @@ type ChatHis struct {
Context HisContext `json:"context"` Context HisContext `json:"context"`
} }
type HisMessage struct { type HisMessage struct {
Role string `json:"role"` Role constants.Caller `json:"role"`
Content string `json:"content"` Content string `json:"content"`
Timestamp string `json:"timestamp"` Timestamp string `json:"timestamp"`
} }
type HisContext struct { type HisContext struct {

View File

@ -50,9 +50,13 @@ func SetupRoutes(app *fiber.App, ChatService *services.ChatService, sessionServi
r.Post("/session/init", sessionService.SessionInit) // 会话初始化,不存在则创建,存在则返回会话ID和默认条数会话历史 r.Post("/session/init", sessionService.SessionInit) // 会话初始化,不存在则创建,存在则返回会话ID和默认条数会话历史
r.Post("/session/list", sessionService.SessionList) r.Post("/session/list", sessionService.SessionList)
r.Post("/sys/tasks", task.Tasks) r.Post("/sys/tasks", task.Tasks)
// 回调 // 评价
r.Post("/callback", callbackService.Callback) r.Post("/chat/useful/list", ChatService.UsefulList)
r.Post("/chat/useful", ChatService.Useful)
// 回调
r.Post("/callback", callbackService.Callback)
//广播 //广播
r.Get("/broadcast", func(ctx *fiber.Ctx) error { r.Get("/broadcast", func(ctx *fiber.Ctx) error {
action := ctx.Query("action") action := ctx.Query("action")

View File

@ -13,6 +13,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/websocket/v2" "github.com/gofiber/websocket/v2"
) )
@ -21,13 +22,19 @@ type ChatService struct {
routerBiz *biz.AiRouterBiz routerBiz *biz.AiRouterBiz
Gw *gateway.Gateway Gw *gateway.Gateway
mu sync.Mutex mu sync.Mutex
ChatHis *biz.ChatHistoryBiz
} }
// NewChatHandler 创建聊天处理器 // NewChatHandler 创建聊天处理器
func NewChatService(routerService *biz.AiRouterBiz, gw *gateway.Gateway) *ChatService { func NewChatService(
routerService *biz.AiRouterBiz,
chatHis *biz.ChatHistoryBiz,
gw *gateway.Gateway,
) *ChatService {
return &ChatService{ return &ChatService{
routerBiz: routerService, routerBiz: routerService,
Gw: gw, Gw: gw,
ChatHis: chatHis,
} }
} }
@ -118,10 +125,7 @@ func (h *ChatService) Chat(c *websocket.Conn) {
Type: entitys.ResponseText, Type: entitys.ResponseText,
}) })
} }
_ = entitys.MsgSend(c, entitys.Response{
Content: "",
Type: entitys.ResponseEnd,
})
} }
h.Gw.RemoveClient(clientID) h.Gw.RemoveClient(clientID)
_ = c.Close() _ = c.Close()
@ -148,3 +152,23 @@ func (h *ChatService) handleMessageToString(c *websocket.Conn, msgType int, msg
} }
return msg.([]byte), constants.ConnStatusIgnore return msg.([]byte), constants.ConnStatusIgnore
} }
func (s *ChatService) Useful(c *fiber.Ctx) error {
req := &entitys.UseFulRequest{}
if err := c.BodyParser(req); err != nil {
return err
}
err := s.ChatHis.Update(c.Context(), req)
if err != nil {
return err
}
return nil
}
func (s *ChatService) UsefulList(c *fiber.Ctx) error {
return c.JSON(constants.UseFulMap)
}

View File

@ -81,6 +81,12 @@ func NewManager(config *config.Config, llm *utils_ollama.Client) *Manager {
m.tools[zltxOrderAfterSalePreCheckTool.Name()] = zltxOrderAfterSalePreCheckTool m.tools[zltxOrderAfterSalePreCheckTool.Name()] = zltxOrderAfterSalePreCheckTool
} }
// 注册直连天下上游售后订单工具
if config.Tools.ZltxOrderAfterSaleSupplier.Enabled {
zltxOrderAfterSaleSupplierTool := NewZltxOrderAfterSaleSupplierTool(config.Tools.ZltxOrderAfterSaleSupplier)
m.tools[zltxOrderAfterSaleSupplierTool.Name()] = zltxOrderAfterSaleSupplierTool
}
// 普通对话 // 普通对话
chat := NewNormalChatTool(m.llm, config) chat := NewNormalChatTool(m.llm, config)
m.tools[chat.Name()] = chat m.tools[chat.Name()] = chat

View File

@ -1,131 +0,0 @@
package tools
import (
"ai_scheduler/internal/config"
"ai_scheduler/internal/entitys"
"context"
"encoding/json"
"fmt"
"sort"
"gitea.cdlsxd.cn/self-tools/l_request"
)
type ZltxOrderAfterSaleTool struct {
config config.ToolConfig
}
func (z ZltxOrderAfterSaleTool) Name() string {
return "zltxOrderAfterSale"
}
func (z ZltxOrderAfterSaleTool) Description() string {
return "查询直连天下订单售后信息"
}
func (z ZltxOrderAfterSaleTool) Definition() entitys.ToolDefinition {
return entitys.ToolDefinition{
Type: "function",
Function: entitys.FunctionDef{
Name: z.Name(),
Description: z.Description(),
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"number": map[string]interface{}{
"type": "string",
"description": "账号或分销商号",
},
},
"required": []string{"number"},
},
},
}
}
type ZltxOrderAfterSaleRequest struct {
Number string `json:"number"`
}
func (z ZltxOrderAfterSaleTool) Execute(ctx context.Context, requireData *entitys.RequireData) error {
var req ZltxOrderAfterSaleRequest
if err := json.Unmarshal([]byte(requireData.Match.Parameters), &req); err != nil {
return err
}
if req.Number == "" {
return fmt.Errorf("number is required")
}
return z.getZltxOrderAfterSale(req.Number, requireData)
}
type ZltxOrderAfterSaleResponse struct {
Code int `json:"code"`
Data struct {
List []*ZltxOrderAfterSaleData `json:"list"`
} `json:"data"`
Error string `json:"error"`
}
type ZltxOrderAfterSaleData struct {
// 处理方式 价款/扣款
HandleMethod string `json:"handle_method"`
// 售后金额
AfterSaleAmount float64 `json:"after_sale_amount"`
// 售后原因
AfterSaleReason string `json:"after_sale_reason"`
// 原单信息
OriginalOrderInfo *OrderInfo
}
type OrderInfo struct {
SerialNumber string `json:"serial_number"` // 订单流水号
SupplierName string `json:"supplier_name"` // 供应商名称
SigningBody string `json:"signing_body"` // 签约主体
ProductName string `json:"product_name"` // 商品名称
UpstreamPrice float64 `json:"upstream_price"` // 上游价格
RechargeAccount string `json:"recharge_account"` // 充值账号
RechargeStatus string `json:"recharge_status"` // 充值状态
}
func (z ZltxOrderAfterSaleTool) getZltxOrderAfterSale(number string, requireData *entitys.RequireData) error {
//查询订单详情
url := fmt.Sprintf("%s%s", z.config.BaseURL, number)
req := l_request.Request{
Url: url,
Headers: map[string]string{
"Authorization": fmt.Sprintf("Bearer %s", requireData.Auth),
},
Method: "GET",
}
res, err := req.Send()
var resData ZltxOrderAfterSaleResponse
if err != nil {
return err
}
if err := json.Unmarshal(res.Content, &resData); err != nil {
return err
}
if resData.Code != 200 {
return fmt.Errorf("为获取到数据,请检查权限: %s", string(res.Content))
}
//按照日期排序
sort.Slice(resData.Data.RecentThreeDays, func(i, j int) bool {
return resData.Data.RecentThreeDays[i].Date < resData.Data.RecentThreeDays[j].Date
})
sort.Slice(resData.Data.RecentOneMonth, func(i, j int) bool {
return resData.Data.RecentOneMonth[i].Date < resData.Data.RecentOneMonth[j].Date
})
jsonByte, err := json.Marshal(resData)
if err != nil {
return err
}
entitys.ResJson(requireData.Ch, z.Name(), string(jsonByte))
return nil
}
func NewZltxOrderAfterSaleTool(config config.ToolConfig) *ZltxOrderAfterSaleTool {
return &ZltxOrderAfterSaleTool{
config: config,
}
}

View File

@ -0,0 +1,169 @@
package tools
import (
"ai_scheduler/internal/config"
"ai_scheduler/internal/entitys"
"context"
"encoding/json"
"fmt"
)
type ZltxOrderAfterSaleSupplierTool struct {
config config.ToolConfig
}
// NewZltxOrderAfterSaleSupplierTool 创建售后订单预检工具
func NewZltxOrderAfterSaleSupplierTool(config config.ToolConfig) *ZltxOrderAfterSaleSupplierTool {
return &ZltxOrderAfterSaleSupplierTool{config: config}
}
// Name 返回工具名称
func (t *ZltxOrderAfterSaleSupplierTool) Name() string {
return "zltxOrderAfterSaleSupplier"
}
func (t *ZltxOrderAfterSaleSupplierTool) Description() string {
return "直连天下售后订单供应商工具"
}
func (t *ZltxOrderAfterSaleSupplierTool) Definition() entitys.ToolDefinition {
return entitys.ToolDefinition{
Type: "function",
Function: entitys.FunctionDef{
Name: t.Name(),
Description: t.Description(),
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"orderType": map[string]interface{}{
"type": "integer",
"description": "售后订单类型",
},
"orderNumber": map[string]interface{}{
"type": "string",
"description": "售后订单号",
},
},
"required": []string{"orderType", "orderNumber"},
},
},
}
}
type ZltxOrderAfterSaleSupplierRequest struct {
OrderNumber string `json:"orderNumber"` // 订单号
SerialNumber string `json:"serialNumber"` // 流水号
Account string `json:"account"` // 充值账号
OrderTimeStart string `json:"orderTimeStart"` // 订单执行开始时间
OrderTimeEnd string `json:"orderTimeEnd"` // 订单执行结束时间
AfterSalesReason string `json:"afterSalesReason"` // 售后原因
AfterSalesPrice string `json:"afterSalesPrice"` // 售后金额
}
type ZltxOrderAfterSaleSupplierResponse struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data []CheckResult2 `json:"data"`
}
type CheckResult2 struct {
OrderType int `json:"orderType"`
OrderNumber string `json:"orderNumber"`
OrderAmount float64 `json:"orderAmount"`
OrderPrice float64 `json:"orderPrice"`
SignCompany int `json:"signCompany"`
OrderQuantity int `json:"orderQuantity"`
ResellerID int `json:"resellerId"`
ResellerName string `json:"resellerName"`
OurProductID int `json:"ourProductId"`
OurProductTitle string `json:"ourProductTitle"`
Account []string `json:"account"`
Platforms struct {
Num4 string `json:"4"`
} `json:"platforms"`
}
func (t *ZltxOrderAfterSaleSupplierTool) Execute(ctx context.Context, requireData *entitys.RequireData) error {
var req ZltxOrderAfterSaleSupplierRequest
if err := json.Unmarshal([]byte(requireData.Match.Parameters), &req); err != nil {
return err
}
if req.OrderNumber == "" {
return fmt.Errorf("orderType and orderNumber are required")
}
return t.checkZltxOrderAfterSaleSupplier(req.OrderNumber, requireData)
}
func (t *ZltxOrderAfterSaleSupplierTool) checkZltxOrderAfterSaleSupplier(orderNumber string, requireData *entitys.RequireData) error {
// req := l_request.Request{
// Url: t.config.BaseURL,
// Headers: map[string]string{
// "Authorization": fmt.Sprintf("Bearer %s", requireData.Auth),
// },
// Method: "POST",
// Data: map[string]string{
// // "orderType": fmt.Sprintf("%d", orderType),
// "orderNumber": orderNumber,
// },
// }
// res, err := req.Send()
// if err != nil {
// return err
// }
// // 解析响应
// var resp ZltxOrderAfterSaleSupplierResponse
// if err := json.Unmarshal(res.Content, &resp); err != nil {
// return err
// }
// if resp.Code != 0 {
// return fmt.Errorf("check failed: %s", resp.Msg)
// }
resp := ZltxOrderAfterSaleSupplierResponse{
Code: 0,
Msg: "success",
Data: []CheckResult2{
{
OrderType: 1,
OrderNumber: orderNumber,
OrderAmount: 100,
OrderPrice: 100,
SignCompany: 1,
OrderQuantity: 1,
ResellerID: 1,
ResellerName: "测试",
OurProductID: 1,
OurProductTitle: "测试",
Account: []string{"123456"},
Platforms: struct {
Num4 string `json:"4"`
}{
Num4: "123456",
},
},
{
OrderType: 2,
OrderNumber: orderNumber,
OrderAmount: 100,
OrderPrice: 100,
SignCompany: 1,
OrderQuantity: 1,
ResellerID: 1,
ResellerName: "测试",
OurProductID: 1,
OurProductTitle: "测试",
Account: []string{"123456"},
Platforms: struct {
Num4 string `json:"4"`
}{
Num4: "123456",
},
},
},
}
jsonByte, err := json.Marshal(resp)
if err != nil {
return err
}
entitys.ResJson(requireData.Ch, t.Name(), string(jsonByte))
return nil
}

View File

@ -17,6 +17,7 @@ type BotTool struct {
llm *utils_ollama.Client llm *utils_ollama.Client
sessionImpl *impl.SessionImpl sessionImpl *impl.SessionImpl
taskMap map[string]string // task_id -> session_id taskMap map[string]string // task_id -> session_id
// zltxOrderAfterSaleTool tools.ZltxOrderAfterSaleTool
} }
// NewBotTool 创建直连天下订单详情工具 // NewBotTool 创建直连天下订单详情工具

View File

@ -68,6 +68,14 @@ func (k DataTemp) Add(data interface{}) (id int, err error) {
return primary.Id, add.Error return primary.Id, add.Error
} }
func (k DataTemp) AddWithData(data interface{}) (interface{}, error) {
result := k.Db.Model(k.Model).Create(data)
if result.Error != nil {
return data, result.Error
}
return data, nil
}
func (k DataTemp) GetList(cond *builder.Cond, pageBoIn *ReqPageBo) (list []map[string]interface{}, pageBoOut *RespPageBo, err error) { func (k DataTemp) GetList(cond *builder.Cond, pageBoIn *ReqPageBo) (list []map[string]interface{}, pageBoOut *RespPageBo, err error) {
var ( var (
query, _ = builder.ToBoundSQL(*cond) query, _ = builder.ToBoundSQL(*cond)