2026-05-18 19:19:17 +08:00
|
|
|
|
package prompt
|
2026-05-12 13:59:15 +08:00
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"context"
|
|
|
|
|
|
"encoding/json"
|
|
|
|
|
|
"errors"
|
|
|
|
|
|
"fmt"
|
|
|
|
|
|
"strings"
|
|
|
|
|
|
"time"
|
|
|
|
|
|
|
2026-05-18 19:19:17 +08:00
|
|
|
|
"gitea.com/red-future/common/beans"
|
|
|
|
|
|
"gitea.com/red-future/common/utils"
|
2026-05-12 13:59:15 +08:00
|
|
|
|
"github.com/gogf/gf/v2/container/gvar"
|
|
|
|
|
|
"github.com/gogf/gf/v2/frame/g"
|
2026-05-20 11:36:39 +08:00
|
|
|
|
|
|
|
|
|
|
"prompts-core/common/util"
|
|
|
|
|
|
"prompts-core/consts/public"
|
|
|
|
|
|
"prompts-core/dao"
|
|
|
|
|
|
"prompts-core/model/dto"
|
|
|
|
|
|
"prompts-core/model/entity"
|
|
|
|
|
|
"prompts-core/service/gateway"
|
2026-05-12 13:59:15 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
2026-05-18 19:19:17 +08:00
|
|
|
|
// ComposeMessages 核心拼接提示词主流程
|
2026-05-20 11:36:39 +08:00
|
|
|
|
func ComposeMessages(ctx context.Context, req *dto.ComposeMessagesReq) (*dto.ComposeMessagesRes, error) {
|
2026-05-18 19:19:17 +08:00
|
|
|
|
chatModel, aiModel, err := GetModelMessage(ctx, req)
|
2026-05-12 13:59:15 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
2026-05-20 11:36:39 +08:00
|
|
|
|
if err = validateUserForm(ctx, req, aiModel); err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
2026-05-21 10:53:58 +08:00
|
|
|
|
fmt.Printf("req打印%+v", req)
|
2026-05-15 09:45:51 +08:00
|
|
|
|
switch req.BuildType {
|
2026-05-20 11:36:39 +08:00
|
|
|
|
case public.BuildTypePrompt:
|
|
|
|
|
|
return handlePromptBuild(ctx, req, chatModel, aiModel) // 提示词构建
|
|
|
|
|
|
case public.BuildTypeNode:
|
|
|
|
|
|
return handleNodeBuild(ctx, req, chatModel, aiModel) // 节点构建
|
|
|
|
|
|
default:
|
|
|
|
|
|
return handleDefaultCase(ctx, req)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-05-12 13:59:15 +08:00
|
|
|
|
|
2026-05-20 11:36:39 +08:00
|
|
|
|
// validateUserForm 校验用户表单
|
|
|
|
|
|
func validateUserForm(ctx context.Context, req *dto.ComposeMessagesReq, model *entity.AsynchModel) error {
|
|
|
|
|
|
if len(req.UserForm) == 0 {
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
isValid, exceedTokens, err := util.CheckUserFormWithinWindow(req.UserForm, model.TokenConfig)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return fmt.Errorf("校验用户表单失败: %w", err)
|
|
|
|
|
|
}
|
2026-05-12 13:59:15 +08:00
|
|
|
|
|
2026-05-20 11:36:39 +08:00
|
|
|
|
if !isValid {
|
|
|
|
|
|
availableWindow := util.GetAvailableWindow(model.TokenConfig)
|
|
|
|
|
|
return fmt.Errorf("UserForm 内容超出窗口大小: 超出 %d tokens,可用窗口 %d tokens,请精简后重试",
|
|
|
|
|
|
exceedTokens, availableWindow)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// handlePromptBuild 处理提示词构建(BuildType=1)
|
|
|
|
|
|
func handlePromptBuild(ctx context.Context, req *dto.ComposeMessagesReq, chatModel, aiModel *entity.AsynchModel) (*dto.ComposeMessagesRes, error) {
|
|
|
|
|
|
maxRetryTimes := g.Cfg().MustGet(ctx, "promptsRetry.maxRetryTimes", 3).Int()
|
|
|
|
|
|
history, err := GetHistoryMessages(ctx, req.SessionId)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
g.Log().Errorf(ctx, "获取历史会话失败: %v,将不使用历史会话", err)
|
|
|
|
|
|
history = nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var message *dto.MultiRoundResult
|
|
|
|
|
|
var taskRecord *entity.ComposeTask
|
2026-05-20 11:50:45 +08:00
|
|
|
|
for attempt := 0; attempt <= maxRetryTimes; attempt++ {
|
2026-05-20 11:36:39 +08:00
|
|
|
|
if attempt > 0 {
|
|
|
|
|
|
g.Log().Warningf(ctx, "[重试]第 %d/%d 次调用推理模型", attempt, maxRetryTimes)
|
2026-05-15 09:45:51 +08:00
|
|
|
|
}
|
2026-05-20 11:36:39 +08:00
|
|
|
|
|
|
|
|
|
|
taskID, err := callInferenceModel(ctx, req, chatModel, aiModel, history)
|
2026-05-15 09:45:51 +08:00
|
|
|
|
if err != nil {
|
2026-05-20 11:36:39 +08:00
|
|
|
|
g.Log().Errorf(ctx, "调用推理模型失败(第%d次): %v", attempt+1, err)
|
|
|
|
|
|
continue
|
2026-05-15 09:45:51 +08:00
|
|
|
|
}
|
2026-05-20 11:36:39 +08:00
|
|
|
|
|
|
|
|
|
|
if err = saveComposeTask(ctx, taskID, req); err != nil {
|
|
|
|
|
|
g.Log().Errorf(ctx, "保存任务记录失败(第%d次): %v", attempt+1, err)
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
2026-05-21 10:53:58 +08:00
|
|
|
|
//等待结果
|
2026-05-20 11:36:39 +08:00
|
|
|
|
taskRecord, err = waitForResult(ctx, taskID)
|
2026-05-12 13:59:15 +08:00
|
|
|
|
if err != nil {
|
2026-05-20 11:36:39 +08:00
|
|
|
|
g.Log().Errorf(ctx, "等待结果失败(第%d次): %v", attempt+1, err)
|
|
|
|
|
|
continue
|
2026-05-12 13:59:15 +08:00
|
|
|
|
}
|
2026-05-21 10:53:58 +08:00
|
|
|
|
//处理结果
|
2026-05-20 11:36:39 +08:00
|
|
|
|
message = parsePromptBuild(taskRecord, chatModel)
|
|
|
|
|
|
if message != nil {
|
|
|
|
|
|
break
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
g.Log().Warningf(ctx, "[重试] 推理结果不合法(第%d次),准备重新请求", attempt+1)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if message == nil {
|
|
|
|
|
|
return nil, errors.New("推理模型调用失败,请稍后再试")
|
|
|
|
|
|
}
|
|
|
|
|
|
epicycleId, err := dao.ComposeSession.Insert(ctx, &entity.ComposeSession{
|
|
|
|
|
|
SessionId: req.SessionId,
|
|
|
|
|
|
RequestContent: message,
|
|
|
|
|
|
})
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
g.Log().Errorf(ctx, "创建会话记录失败: %v", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
return &dto.ComposeMessagesRes{
|
|
|
|
|
|
Messages: message,
|
|
|
|
|
|
EpicycleId: epicycleId,
|
|
|
|
|
|
}, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// handleNodeBuild 处理节点构建(BuildType=2)
|
|
|
|
|
|
func handleNodeBuild(ctx context.Context, req *dto.ComposeMessagesReq, chatModel, aiModel *entity.AsynchModel) (*dto.ComposeMessagesRes, error) {
|
|
|
|
|
|
taskID, err := callInferenceModel(ctx, req, chatModel, aiModel, nil)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, fmt.Errorf("调用推理模型失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if err := saveComposeTask(ctx, taskID, req); err != nil {
|
|
|
|
|
|
return nil, fmt.Errorf("保存任务记录失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
taskRecord, err := waitForResult(ctx, taskID)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, fmt.Errorf("等待结果失败: %w", err)
|
2026-05-12 13:59:15 +08:00
|
|
|
|
}
|
2026-05-20 11:36:39 +08:00
|
|
|
|
|
|
|
|
|
|
message := parseNodeBuild(taskRecord)
|
|
|
|
|
|
|
|
|
|
|
|
return &dto.ComposeMessagesRes{
|
2026-05-15 09:45:51 +08:00
|
|
|
|
Messages: message,
|
2026-05-20 11:36:39 +08:00
|
|
|
|
EpicycleId: 0,
|
|
|
|
|
|
}, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// handleDefaultCase 处理默认情况
|
|
|
|
|
|
func handleDefaultCase(ctx context.Context, req *dto.ComposeMessagesReq) (*dto.ComposeMessagesRes, error) {
|
|
|
|
|
|
epicycleId, err := dao.ComposeSession.Insert(ctx, &entity.ComposeSession{
|
|
|
|
|
|
SessionId: req.SessionId,
|
|
|
|
|
|
Remark: req.Cause,
|
|
|
|
|
|
})
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, fmt.Errorf("创建会话记录失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return &dto.ComposeMessagesRes{
|
2026-05-12 13:59:15 +08:00
|
|
|
|
EpicycleId: epicycleId,
|
|
|
|
|
|
}, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-20 11:36:39 +08:00
|
|
|
|
// saveComposeTask 保存组合任务
|
|
|
|
|
|
func saveComposeTask(ctx context.Context, taskID string, req *dto.ComposeMessagesReq) error {
|
|
|
|
|
|
_, err := dao.ComposeTask.Insert(ctx, &entity.ComposeTask{
|
|
|
|
|
|
TaskId: taskID,
|
|
|
|
|
|
ModelName: req.ModelName,
|
|
|
|
|
|
SkillName: req.SkillName,
|
|
|
|
|
|
RequestPayload: util.MustMarshal(req),
|
|
|
|
|
|
Status: public.ComposeStatusPending,
|
|
|
|
|
|
})
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-18 19:19:17 +08:00
|
|
|
|
// GetModelMessage 获取模型信息
|
2026-05-20 11:36:39 +08:00
|
|
|
|
func GetModelMessage(ctx context.Context, req *dto.ComposeMessagesReq) (*entity.AsynchModel, *entity.AsynchModel, error) {
|
2026-05-18 19:19:17 +08:00
|
|
|
|
userInfo, err := utils.GetUserInfo(ctx)
|
2026-05-20 11:36:39 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, nil, fmt.Errorf("获取用户信息失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
chatModel, err := getChatModel(ctx, userInfo.UserName)
|
2026-05-12 13:59:15 +08:00
|
|
|
|
if err != nil {
|
2026-05-18 19:19:17 +08:00
|
|
|
|
return nil, nil, err
|
2026-05-12 13:59:15 +08:00
|
|
|
|
}
|
2026-05-20 11:36:39 +08:00
|
|
|
|
|
|
|
|
|
|
aiModel, err := getAIModel(ctx, userInfo.UserName, req.ModelName)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return chatModel, aiModel, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// getChatModel 获取聊天模型
|
|
|
|
|
|
func getChatModel(ctx context.Context, userName string) (*entity.AsynchModel, error) {
|
2026-05-18 19:19:17 +08:00
|
|
|
|
chatModel, err := dao.Model.Get(ctx, &entity.AsynchModel{
|
2026-05-20 11:36:39 +08:00
|
|
|
|
SQLBaseDO: beans.SQLBaseDO{Creator: userName},
|
|
|
|
|
|
IsChatModel: new(1),
|
2026-05-18 19:19:17 +08:00
|
|
|
|
})
|
2026-05-15 09:45:51 +08:00
|
|
|
|
if err != nil {
|
2026-05-20 11:36:39 +08:00
|
|
|
|
return nil, fmt.Errorf("查询聊天模型失败: %w", err)
|
2026-05-15 09:45:51 +08:00
|
|
|
|
}
|
2026-05-20 11:36:39 +08:00
|
|
|
|
|
2026-05-15 09:45:51 +08:00
|
|
|
|
if chatModel == nil {
|
2026-05-20 11:36:39 +08:00
|
|
|
|
return nil, errors.New("当前没有对话模型,请添加")
|
2026-05-15 09:45:51 +08:00
|
|
|
|
}
|
2026-05-20 11:36:39 +08:00
|
|
|
|
|
|
|
|
|
|
return chatModel, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// getAIModel 获取AI模型
|
|
|
|
|
|
func getAIModel(ctx context.Context, userName, modelName string) (*entity.AsynchModel, error) {
|
2026-05-18 19:19:17 +08:00
|
|
|
|
aiModel, err := dao.Model.Get(ctx, &entity.AsynchModel{
|
2026-05-20 11:36:39 +08:00
|
|
|
|
SQLBaseDO: beans.SQLBaseDO{Creator: userName},
|
|
|
|
|
|
ModelName: modelName,
|
2026-05-18 19:19:17 +08:00
|
|
|
|
})
|
2026-05-15 09:45:51 +08:00
|
|
|
|
if err != nil {
|
2026-05-20 11:36:39 +08:00
|
|
|
|
return nil, fmt.Errorf("查询AI模型失败: %w", err)
|
2026-05-15 09:45:51 +08:00
|
|
|
|
}
|
2026-05-20 11:36:39 +08:00
|
|
|
|
|
2026-05-18 19:19:17 +08:00
|
|
|
|
if aiModel == nil {
|
2026-05-20 11:36:39 +08:00
|
|
|
|
return nil, fmt.Errorf("需要构建的模型 %s 不存在", modelName)
|
2026-05-15 09:45:51 +08:00
|
|
|
|
}
|
2026-05-20 11:36:39 +08:00
|
|
|
|
|
|
|
|
|
|
return aiModel, nil
|
2026-05-15 09:45:51 +08:00
|
|
|
|
}
|
2026-05-12 13:59:15 +08:00
|
|
|
|
|
2026-05-15 09:45:51 +08:00
|
|
|
|
// callInferenceModel 调用推理模型
|
2026-05-20 11:36:39 +08:00
|
|
|
|
func callInferenceModel(ctx context.Context, req *dto.ComposeMessagesReq, chatModel *entity.AsynchModel, model *entity.AsynchModel, history []map[string]any) (string, error) {
|
2026-05-15 09:45:51 +08:00
|
|
|
|
taskReq, err := buildInferenceRequest(ctx, req, chatModel, model, history)
|
2026-05-12 13:59:15 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
return "", fmt.Errorf("构建推理请求失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-18 19:19:17 +08:00
|
|
|
|
taskID, err := gateway.CreateGatewayTask(ctx, taskReq)
|
2026-05-12 13:59:15 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
return "", fmt.Errorf("创建网关任务失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if taskID == "" {
|
|
|
|
|
|
return "", errors.New("网关未返回taskId")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return taskID, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-18 19:19:17 +08:00
|
|
|
|
// waitForResult 等待结果
|
2026-05-21 14:23:34 +08:00
|
|
|
|
// waitForResult 等待结果(优先channel通知,兜底网关查询)
|
2026-05-18 19:19:17 +08:00
|
|
|
|
func waitForResult(ctx context.Context, taskID string) (*entity.ComposeTask, error) {
|
2026-05-15 09:45:51 +08:00
|
|
|
|
timeout := time.Duration(g.Cfg().MustGet(ctx, "task.waitTimeoutSeconds", 300).Int()) * time.Second
|
2026-05-21 14:23:34 +08:00
|
|
|
|
// 设置超时context
|
|
|
|
|
|
ctx, cancel := context.WithTimeout(ctx, timeout)
|
|
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
|
|
|
|
// 优先等待channel通知(来自回调)
|
|
|
|
|
|
result, err := TaskWaiter.Wait(ctx, taskID)
|
|
|
|
|
|
if err == nil {
|
|
|
|
|
|
// 成功收到回调通知
|
|
|
|
|
|
return result.(*entity.ComposeTask), nil
|
|
|
|
|
|
}
|
|
|
|
|
|
// channel等待失败(超时/取消),从数据库读取最终状态作为兜底
|
|
|
|
|
|
g.Log().Warningf(ctx, "[waitForResult] channel等待失败,从DB获取最终状态 taskId=%s err=%v", taskID, err)
|
|
|
|
|
|
record, dbErr := dao.ComposeTask.Get(ctx, &entity.ComposeTask{
|
|
|
|
|
|
TaskId: taskID,
|
|
|
|
|
|
})
|
|
|
|
|
|
if dbErr != nil {
|
|
|
|
|
|
return nil, fmt.Errorf("查询数据库失败: %w", dbErr)
|
|
|
|
|
|
}
|
2026-05-12 13:59:15 +08:00
|
|
|
|
|
2026-05-21 14:23:34 +08:00
|
|
|
|
if record == nil {
|
|
|
|
|
|
return nil, fmt.Errorf("任务不存在(taskId=%s)", taskID)
|
|
|
|
|
|
}
|
2026-05-15 09:45:51 +08:00
|
|
|
|
|
2026-05-21 14:23:34 +08:00
|
|
|
|
switch record.Status {
|
|
|
|
|
|
case public.ComposeStatusSuccess:
|
|
|
|
|
|
return record, nil
|
|
|
|
|
|
case public.ComposeStatusFailed:
|
|
|
|
|
|
if strings.TrimSpace(record.ErrorMessage) == "" {
|
|
|
|
|
|
return nil, fmt.Errorf("任务失败(taskId=%s)", taskID)
|
2026-05-15 09:45:51 +08:00
|
|
|
|
}
|
2026-05-21 14:23:34 +08:00
|
|
|
|
return nil, fmt.Errorf("任务失败(taskId=%s): %s", taskID, record.ErrorMessage)
|
|
|
|
|
|
default:
|
|
|
|
|
|
// 还在处理中,但已超时
|
|
|
|
|
|
return nil, fmt.Errorf("等待任务回调超时(taskId=%s)", taskID)
|
2026-05-12 13:59:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-15 09:45:51 +08:00
|
|
|
|
// parsePromptBuild 解析提示词构建结果(BuildType == 1)
|
2026-05-20 11:36:39 +08:00
|
|
|
|
func parsePromptBuild(taskRecord *entity.ComposeTask, model *entity.AsynchModel) *dto.MultiRoundResult {
|
2026-05-12 13:59:15 +08:00
|
|
|
|
if taskRecord == nil {
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
2026-05-21 14:23:34 +08:00
|
|
|
|
|
2026-05-20 11:36:39 +08:00
|
|
|
|
mapped := parseTaskMessages(taskRecord.Messages)
|
|
|
|
|
|
if mapped == nil {
|
|
|
|
|
|
return createDefaultResult(nil)
|
|
|
|
|
|
}
|
2026-05-12 13:59:15 +08:00
|
|
|
|
|
2026-05-20 11:36:39 +08:00
|
|
|
|
contentField := getContentField(model)
|
|
|
|
|
|
contentStr, ok := mapped[contentField].(string)
|
|
|
|
|
|
if !ok || contentStr == "" {
|
|
|
|
|
|
return createDefaultResult(mapped)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-21 14:23:34 +08:00
|
|
|
|
// 尝试解析为数组
|
|
|
|
|
|
if roundsArray := tryParseAsMapArray(contentStr); roundsArray != nil {
|
2026-05-20 11:36:39 +08:00
|
|
|
|
return &dto.MultiRoundResult{
|
|
|
|
|
|
TotalRounds: len(roundsArray),
|
|
|
|
|
|
Rounds: roundsArray,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-21 14:23:34 +08:00
|
|
|
|
// 尝试解析为单个对象
|
|
|
|
|
|
if singleRound := tryParseAsMap(contentStr); singleRound != nil {
|
2026-05-20 11:36:39 +08:00
|
|
|
|
return &dto.MultiRoundResult{
|
|
|
|
|
|
TotalRounds: 1,
|
2026-05-21 14:23:34 +08:00
|
|
|
|
Rounds: []map[string]any{singleRound},
|
2026-05-20 11:36:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-21 14:23:34 +08:00
|
|
|
|
// 纯文本,包装为默认格式
|
2026-05-20 11:36:39 +08:00
|
|
|
|
return createDefaultResult(map[string]any{"content": contentStr})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-21 14:23:34 +08:00
|
|
|
|
// tryParseAsMapArray 尝试解析JSON字符串为 []map[string]any
|
|
|
|
|
|
func tryParseAsMapArray(jsonStr string) []map[string]any {
|
|
|
|
|
|
var arr []map[string]any
|
|
|
|
|
|
if err := json.Unmarshal([]byte(jsonStr), &arr); err != nil {
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
if len(arr) == 0 {
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
return arr
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// tryParseAsMap 尝试解析JSON字符串为 map[string]any
|
|
|
|
|
|
func tryParseAsMap(jsonStr string) map[string]any {
|
|
|
|
|
|
var obj map[string]any
|
|
|
|
|
|
if err := json.Unmarshal([]byte(jsonStr), &obj); err != nil {
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
if len(obj) == 0 {
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
return obj
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-20 11:36:39 +08:00
|
|
|
|
// parseTaskMessages 解析任务消息
|
|
|
|
|
|
func parseTaskMessages(messages any) map[string]any {
|
2026-05-15 09:45:51 +08:00
|
|
|
|
var mapped map[string]any
|
2026-05-20 11:36:39 +08:00
|
|
|
|
|
|
|
|
|
|
switch v := messages.(type) {
|
2026-05-12 13:59:15 +08:00
|
|
|
|
case *gvar.Var:
|
|
|
|
|
|
if v != nil {
|
|
|
|
|
|
json.Unmarshal([]byte(v.String()), &mapped)
|
|
|
|
|
|
}
|
|
|
|
|
|
case string:
|
|
|
|
|
|
json.Unmarshal([]byte(v), &mapped)
|
|
|
|
|
|
case map[string]any:
|
2026-05-15 09:45:51 +08:00
|
|
|
|
mapped = v
|
|
|
|
|
|
default:
|
|
|
|
|
|
b, _ := json.Marshal(v)
|
|
|
|
|
|
json.Unmarshal(b, &mapped)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-20 11:36:39 +08:00
|
|
|
|
return mapped
|
|
|
|
|
|
}
|
2026-05-12 13:59:15 +08:00
|
|
|
|
|
2026-05-20 11:36:39 +08:00
|
|
|
|
// tryParseAsArray 尝试将字符串解析为数组
|
|
|
|
|
|
func tryParseAsArray(contentStr string) []any {
|
|
|
|
|
|
var roundsArray []any
|
|
|
|
|
|
if err := json.Unmarshal([]byte(contentStr), &roundsArray); err != nil {
|
|
|
|
|
|
return nil
|
2026-05-15 09:45:51 +08:00
|
|
|
|
}
|
2026-05-20 11:36:39 +08:00
|
|
|
|
return roundsArray
|
|
|
|
|
|
}
|
2026-05-15 09:45:51 +08:00
|
|
|
|
|
2026-05-20 11:36:39 +08:00
|
|
|
|
// tryParseAsObject 尝试将字符串解析为对象
|
|
|
|
|
|
func tryParseAsObject(contentStr string) any {
|
|
|
|
|
|
var singleRound any
|
|
|
|
|
|
if err := json.Unmarshal([]byte(contentStr), &singleRound); err != nil {
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
return singleRound
|
|
|
|
|
|
}
|
2026-05-12 13:59:15 +08:00
|
|
|
|
|
2026-05-20 11:36:39 +08:00
|
|
|
|
// createDefaultResult 创建默认结果
|
2026-05-21 14:23:34 +08:00
|
|
|
|
func createDefaultResult(data map[string]any) *dto.MultiRoundResult {
|
2026-05-20 11:36:39 +08:00
|
|
|
|
if data == nil {
|
|
|
|
|
|
data = make(map[string]any)
|
|
|
|
|
|
}
|
|
|
|
|
|
return &dto.MultiRoundResult{
|
|
|
|
|
|
TotalRounds: 1,
|
2026-05-21 14:23:34 +08:00
|
|
|
|
Rounds: []map[string]any{data},
|
2026-05-20 11:36:39 +08:00
|
|
|
|
}
|
2026-05-12 13:59:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-20 11:36:39 +08:00
|
|
|
|
// getContentField 从模型 ResponseMapping 中获取 content 字段名
|
|
|
|
|
|
func getContentField(model *entity.AsynchModel) string {
|
|
|
|
|
|
if model == nil {
|
|
|
|
|
|
return "content"
|
2026-05-12 13:59:15 +08:00
|
|
|
|
}
|
2026-05-20 11:36:39 +08:00
|
|
|
|
|
|
|
|
|
|
respMapping := parseResponseMapping(model.ResponseMapping)
|
|
|
|
|
|
for k, v := range respMapping {
|
|
|
|
|
|
if strings.Contains(v, "content") {
|
|
|
|
|
|
return k
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return "content"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// parseResponseMapping 解析响应映射
|
|
|
|
|
|
func parseResponseMapping(mapping any) map[string]string {
|
|
|
|
|
|
result := make(map[string]string)
|
|
|
|
|
|
|
|
|
|
|
|
switch v := mapping.(type) {
|
2026-05-15 09:45:51 +08:00
|
|
|
|
case *gvar.Var:
|
|
|
|
|
|
if v != nil {
|
|
|
|
|
|
json.Unmarshal([]byte(v.String()), &result)
|
2026-05-12 13:59:15 +08:00
|
|
|
|
}
|
2026-05-15 09:45:51 +08:00
|
|
|
|
case string:
|
|
|
|
|
|
json.Unmarshal([]byte(v), &result)
|
2026-05-20 11:36:39 +08:00
|
|
|
|
case map[string]interface{}:
|
|
|
|
|
|
for k, val := range v {
|
|
|
|
|
|
if s, ok := val.(string); ok {
|
|
|
|
|
|
result[k] = s
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-05-12 13:59:15 +08:00
|
|
|
|
}
|
2026-05-20 11:36:39 +08:00
|
|
|
|
|
2026-05-12 13:59:15 +08:00
|
|
|
|
return result
|
|
|
|
|
|
}
|
2026-05-18 19:19:17 +08:00
|
|
|
|
|
2026-05-20 11:36:39 +08:00
|
|
|
|
// parseNodeBuild 解析节点构建结果(BuildType == 2)
|
|
|
|
|
|
func parseNodeBuild(taskRecord *entity.ComposeTask) *dto.MultiRoundResult {
|
|
|
|
|
|
if taskRecord == nil {
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
result := parseTaskMessages(taskRecord.Messages)
|
|
|
|
|
|
if result == nil {
|
|
|
|
|
|
result = make(map[string]any)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return &dto.MultiRoundResult{
|
|
|
|
|
|
TotalRounds: 1,
|
2026-05-21 14:23:34 +08:00
|
|
|
|
Rounds: []map[string]any{result},
|
2026-05-20 11:36:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-18 19:19:17 +08:00
|
|
|
|
// Callback 回调处理
|
2026-05-20 11:36:39 +08:00
|
|
|
|
func Callback(ctx context.Context, req *dto.CallbackReq) error {
|
2026-05-18 19:19:17 +08:00
|
|
|
|
g.Log().Infof(ctx, "[Callback][RECV] taskId=%s state=%d ossFile=%s fileType=%s textLen=%d",
|
|
|
|
|
|
req.TaskId, req.State, req.OssFile, req.FileType, len(req.Text))
|
|
|
|
|
|
task, err := dao.ComposeTask.Get(ctx, &entity.ComposeTask{
|
|
|
|
|
|
TaskId: req.TaskId,
|
|
|
|
|
|
})
|
|
|
|
|
|
if err != nil {
|
2026-05-20 11:36:39 +08:00
|
|
|
|
return fmt.Errorf("查询任务失败: %w", err)
|
2026-05-18 19:19:17 +08:00
|
|
|
|
}
|
|
|
|
|
|
if task == nil {
|
|
|
|
|
|
return fmt.Errorf("任务不存在: %s", req.TaskId)
|
|
|
|
|
|
}
|
2026-05-21 14:23:34 +08:00
|
|
|
|
//处理失败
|
2026-05-18 19:19:17 +08:00
|
|
|
|
if req.State == 3 {
|
2026-05-21 14:23:34 +08:00
|
|
|
|
_, err = dao.ComposeTask.Update(ctx, &entity.ComposeTask{
|
|
|
|
|
|
TaskId: req.TaskId,
|
|
|
|
|
|
Status: public.ComposeStatusFailed,
|
|
|
|
|
|
ErrorMessage: req.ErrorMsg,
|
|
|
|
|
|
})
|
|
|
|
|
|
// 通知等待者:任务失败
|
|
|
|
|
|
notifyWaiter(req.TaskId, nil, fmt.Errorf("任务失败: %s", req.ErrorMsg))
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
//处理成功
|
|
|
|
|
|
if req.State == 2 {
|
|
|
|
|
|
result, err := util.ParseOutput(req.Text)
|
|
|
|
|
|
var messages any
|
|
|
|
|
|
if result != nil {
|
|
|
|
|
|
messages = result
|
|
|
|
|
|
}
|
|
|
|
|
|
_, err = dao.ComposeTask.Update(ctx, &entity.ComposeTask{
|
|
|
|
|
|
TaskId: req.TaskId,
|
|
|
|
|
|
Status: public.ComposeStatusSuccess,
|
|
|
|
|
|
Messages: messages,
|
|
|
|
|
|
})
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
g.Log().Errorf(ctx, "[Callback] 更新任务失败 taskId=%s err=%v", req.TaskId, err)
|
|
|
|
|
|
}
|
|
|
|
|
|
notifyWaiter(req.TaskId, &entity.ComposeTask{
|
|
|
|
|
|
TaskId: req.TaskId,
|
|
|
|
|
|
Status: public.ComposeStatusSuccess,
|
|
|
|
|
|
Messages: messages,
|
|
|
|
|
|
}, err)
|
2026-05-18 19:19:17 +08:00
|
|
|
|
}
|
2026-05-20 11:36:39 +08:00
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-21 14:23:34 +08:00
|
|
|
|
// notifyWaiter 通知等待者(不影响主流程)
|
|
|
|
|
|
func notifyWaiter(taskID string, result interface{}, err error) {
|
|
|
|
|
|
notifyErr := TaskWaiter.Notify(taskID, result, err)
|
|
|
|
|
|
if notifyErr != nil {
|
|
|
|
|
|
// 只记录日志,不影响回调处理结果
|
|
|
|
|
|
g.Log().Infof(context.Background(), "[Callback] 通知等待者失败 taskId=%s err=%v", taskID, notifyErr)
|
2026-05-18 19:19:17 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetComposeTask 查询任务结果
|
2026-05-20 11:36:39 +08:00
|
|
|
|
func GetComposeTask(ctx context.Context, taskID string) (*dto.GetComposeTaskRes, error) {
|
2026-05-18 19:19:17 +08:00
|
|
|
|
record, err := dao.ComposeTask.Get(ctx, &entity.ComposeTask{
|
|
|
|
|
|
TaskId: taskID,
|
|
|
|
|
|
})
|
|
|
|
|
|
if err != nil {
|
2026-05-20 11:36:39 +08:00
|
|
|
|
return nil, fmt.Errorf("查询任务失败: %w", err)
|
2026-05-18 19:19:17 +08:00
|
|
|
|
}
|
|
|
|
|
|
if record == nil {
|
|
|
|
|
|
return nil, fmt.Errorf("未找到任务(taskId=%s)", taskID)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-20 11:36:39 +08:00
|
|
|
|
messages := parseMessagesForResponse(record.Messages)
|
2026-05-18 19:19:17 +08:00
|
|
|
|
|
2026-05-20 11:36:39 +08:00
|
|
|
|
return &dto.GetComposeTaskRes{
|
2026-05-18 19:19:17 +08:00
|
|
|
|
TaskId: record.TaskId,
|
|
|
|
|
|
Status: record.Status,
|
|
|
|
|
|
ErrorMessage: record.ErrorMessage,
|
|
|
|
|
|
Messages: messages,
|
|
|
|
|
|
}, nil
|
|
|
|
|
|
}
|
2026-05-20 11:36:39 +08:00
|
|
|
|
|
|
|
|
|
|
// parseMessagesForResponse 解析用于响应的消息
|
|
|
|
|
|
func parseMessagesForResponse(messages any) any {
|
|
|
|
|
|
str, ok := messages.(string)
|
|
|
|
|
|
if !ok || str == "" {
|
|
|
|
|
|
return messages
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var parsed any
|
|
|
|
|
|
if err := json.Unmarshal([]byte(str), &parsed); err == nil {
|
|
|
|
|
|
return parsed
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return messages
|
|
|
|
|
|
}
|