195 lines
5.2 KiB
Go
195 lines
5.2 KiB
Go
package biz
|
||
|
||
import (
|
||
"ai_scheduler/internal/biz/do"
|
||
"ai_scheduler/internal/data/constants"
|
||
errors "ai_scheduler/internal/data/error"
|
||
"ai_scheduler/internal/gateway"
|
||
|
||
"ai_scheduler/internal/pkg/rec_extra"
|
||
"context"
|
||
"encoding/json"
|
||
"strings"
|
||
"time"
|
||
|
||
"ai_scheduler/internal/entitys"
|
||
|
||
"github.com/gofiber/fiber/v2/log"
|
||
)
|
||
|
||
// AiRouterBiz 智能路由服务
|
||
type AiRouterBiz struct {
|
||
do *do.Do
|
||
handle *do.Handle
|
||
}
|
||
|
||
// NewAiRouterBiz 创建路由服务
|
||
func NewAiRouterBiz(
|
||
do *do.Do,
|
||
handle *do.Handle,
|
||
) *AiRouterBiz {
|
||
return &AiRouterBiz{
|
||
do: do,
|
||
handle: handle,
|
||
}
|
||
}
|
||
|
||
// 路由处理WebSocket请求
|
||
//
|
||
// 参数:
|
||
// - client: 网关客户端
|
||
// - req: 聊天请求结构体
|
||
//
|
||
// 返回:
|
||
// - err: 错误信息
|
||
func (r *AiRouterBiz) RouteWithSocket(client *gateway.Client, req *entitys.ChatSockRequest) (err error) {
|
||
// 创建请求上下文数据
|
||
requireData := &entitys.RequireData{
|
||
Req: req,
|
||
}
|
||
|
||
//初始化通道/上下文
|
||
ctx, clearFunc := r.do.MakeCh(client, requireData)
|
||
defer func() {
|
||
if err != nil {
|
||
entitys.ResError(requireData.Ch, "", err.Error())
|
||
}
|
||
clearFunc()
|
||
}()
|
||
|
||
//数据验证和收集
|
||
if err = r.do.DataAuth(ctx, client, requireData); err != nil {
|
||
log.Errorf("数据验证和收集失败: %s", err.Error())
|
||
return
|
||
}
|
||
//组装意图识别
|
||
rec, sys, err := r.SetRec(ctx, requireData)
|
||
if err != nil {
|
||
log.Errorf("组装意图识别失败: %s", err.Error())
|
||
return
|
||
}
|
||
//意图识别
|
||
|
||
err = r.handle.Recognize(ctx, &rec, sys)
|
||
if err != nil {
|
||
log.Errorf("意图识别失败: %s", err.Error())
|
||
return
|
||
}
|
||
//任务处理
|
||
rec_extra.SetTaskRecExt(requireData, &rec)
|
||
//向下传递
|
||
if err = r.handle.HandleMatch(ctx, client, &rec, requireData); err != nil {
|
||
log.Errorf("任务处理失败: %s", err.Error())
|
||
return
|
||
}
|
||
return
|
||
}
|
||
|
||
func (r *AiRouterBiz) SetRec(ctx context.Context, requireData *entitys.RequireData) (match entitys.Recognize, sys do.PromptOption, err error) {
|
||
// 参数空值检查
|
||
if requireData == nil || requireData.Req == nil {
|
||
return match, sys, errors.NewBusinessErr(500, "请求参数为空")
|
||
}
|
||
|
||
// 对应不同的appKey, 配置不同的系统提示词
|
||
switch requireData.Sys.AppKey {
|
||
default:
|
||
sys = &do.WithSys{}
|
||
}
|
||
|
||
// 1. 系统提示词
|
||
match.SystemPrompt = requireData.Sys.SysPrompt
|
||
|
||
// 2. 用户输入和文件处理
|
||
match.UserContent, err = r.buildUserContent(requireData)
|
||
if err != nil {
|
||
log.Errorf("构建用户内容失败: %s", err.Error())
|
||
return
|
||
}
|
||
|
||
// 3. 聊天记录 - 只有在有历史记录时才构建
|
||
if len(requireData.Histories) > 0 {
|
||
match.ChatHis = r.buildChatHistory(requireData)
|
||
}
|
||
|
||
// 4. 任务列表 - 预分配切片容量
|
||
if len(requireData.Tasks) > 0 {
|
||
match.Tasks = make([]entitys.RegistrationTask, 0, len(requireData.Tasks))
|
||
for _, task := range requireData.Tasks {
|
||
taskConfig := entitys.TaskConfigDetail{}
|
||
if err = json.Unmarshal([]byte(task.Config), &taskConfig); err != nil {
|
||
log.Errorf("解析任务配置失败: %s, 任务ID: %s", err.Error(), task.Index)
|
||
continue // 解析失败时跳过该任务,而不是直接返回错误
|
||
}
|
||
|
||
match.Tasks = append(match.Tasks, entitys.RegistrationTask{
|
||
Name: task.Index,
|
||
Desc: task.Desc,
|
||
TaskConfigDetail: taskConfig, // 直接使用解析后的配置,避免重复构建
|
||
})
|
||
}
|
||
}
|
||
|
||
match.Ch = requireData.Ch
|
||
return
|
||
}
|
||
|
||
// buildUserContent 构建用户内容
|
||
func (r *AiRouterBiz) buildUserContent(requireData *entitys.RequireData) (*entitys.RecognizeUserContent, error) {
|
||
// 预分配文件切片容量(最多2个文件:File和Img)
|
||
files := make([]*entitys.RecognizeFile, 0, 2)
|
||
|
||
// 处理文件和图片
|
||
fileUrls := []string{requireData.Req.File, requireData.Req.Img}
|
||
for _, item := range fileUrls {
|
||
// 处理逗号分隔的多个URL
|
||
urlList := strings.Split(item, ",")
|
||
for _, url := range urlList {
|
||
if url != "" {
|
||
files = append(files, &entitys.RecognizeFile{FileUrl: url})
|
||
}
|
||
}
|
||
}
|
||
|
||
// 构建并返回用户内容
|
||
return &entitys.RecognizeUserContent{
|
||
Text: requireData.Req.Text,
|
||
File: files,
|
||
ActionCardUrl: "", // TODO: 后续实现操作卡片功能
|
||
Tag: requireData.Req.Tags,
|
||
}, nil
|
||
}
|
||
|
||
// buildChatHistory 构建聊天历史
|
||
func (r *AiRouterBiz) buildChatHistory(requireData *entitys.RequireData) entitys.ChatHis {
|
||
// 预分配消息切片容量(每个历史记录生成2条消息)
|
||
messages := make([]entitys.HisMessage, 0, len(requireData.Histories)*2)
|
||
|
||
// 构建聊天记录
|
||
for _, h := range requireData.Histories {
|
||
// 用户消息
|
||
messages = append(messages, entitys.HisMessage{
|
||
Role: constants.RoleUser, // 用户角色
|
||
Content: h.Ans, // 用户输入内容
|
||
Timestamp: h.CreateAt.Format(time.DateTime),
|
||
})
|
||
|
||
// 助手消息
|
||
messages = append(messages, entitys.HisMessage{
|
||
Role: constants.RoleAssistant, // 助手角色
|
||
Content: h.Ques, // 助手回复内容
|
||
Timestamp: h.CreateAt.Format(time.DateTime),
|
||
})
|
||
}
|
||
|
||
// 构建聊天历史上下文
|
||
return entitys.ChatHis{
|
||
SessionId: requireData.Session,
|
||
Messages: messages,
|
||
Context: entitys.HisContext{
|
||
UserLanguage: constants.LangZhCN, // 默认中文
|
||
SystemMode: constants.SystemModeTechnicalSupport, // 默认技术支持模式
|
||
},
|
||
}
|
||
}
|