feat(prompt): 重构提示词服务并添加模型类型子分类
This commit is contained in:
@@ -16,8 +16,8 @@ import (
|
||||
)
|
||||
|
||||
// buildInferenceRequest 构建推理请求
|
||||
func buildInferenceRequest(ctx context.Context, req *dto.ComposeMessagesReq, chatModel *entity.AsynchModel, targetModel *entity.AsynchModel, history []map[string]any) (map[string]any, error) {
|
||||
processedReq, totalBatches, err := ProcessUserFormBatches(ctx, req, targetModel)
|
||||
func buildInferenceRequest(ctx context.Context, req *dto.ComposeMessagesReq, chatModel *entity.AsynchModel, aiModel *entity.AsynchModel, history []map[string]any) (map[string]any, error) {
|
||||
processedReq, totalBatches, err := ProcessUserFormBatches(ctx, req, aiModel)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("处理用户表单分批失败: %w", err)
|
||||
}
|
||||
@@ -26,7 +26,7 @@ func buildInferenceRequest(ctx context.Context, req *dto.ComposeMessagesReq, cha
|
||||
|
||||
switch req.BuildType {
|
||||
case public.BuildTypePrompt:
|
||||
return buildPromptTypeRequest(ctx, processedReq, targetModel, chatModel, history, ir, totalBatches)
|
||||
return buildPromptTypeRequest(ctx, processedReq, aiModel, chatModel, history, ir, totalBatches)
|
||||
case public.BuildTypeNode:
|
||||
return buildNodeTypeRequest(ctx, req, chatModel, ir)
|
||||
default:
|
||||
@@ -35,8 +35,8 @@ func buildInferenceRequest(ctx context.Context, req *dto.ComposeMessagesReq, cha
|
||||
}
|
||||
|
||||
// buildPromptTypeRequest 构建提示词类型请求(BuildType=1)
|
||||
func buildPromptTypeRequest(ctx context.Context, req *dto.ComposeMessagesReq, targetModel *entity.AsynchModel, chatModel *entity.AsynchModel, history []map[string]any, ir *PromptIR, totalBatches int) (map[string]any, error) {
|
||||
systemPrompt := promptBuildWithRounds(ctx, req, targetModel, totalBatches)
|
||||
func buildPromptTypeRequest(ctx context.Context, req *dto.ComposeMessagesReq, aiModel *entity.AsynchModel, chatModel *entity.AsynchModel, history []map[string]any, ir *PromptIR, totalBatches int) (map[string]any, error) {
|
||||
systemPrompt := promptBuildWithRounds(ctx, req, aiModel, totalBatches)
|
||||
ir.AddSystem(systemPrompt)
|
||||
|
||||
for _, msg := range history {
|
||||
@@ -47,26 +47,30 @@ func buildPromptTypeRequest(ctx context.Context, req *dto.ComposeMessagesReq, ta
|
||||
ir.AddHistory(role, gconv.String(msg["content"]))
|
||||
}
|
||||
|
||||
userPrompt := buildUserPrompt(ctx, req, util.GetModelPrompt(ctx, targetModel.ModelType))
|
||||
userPrompt := buildUserPrompt(ctx, req, util.GetModelPrompt(ctx, aiModel.ModelType))
|
||||
ir.AddUser(userPrompt)
|
||||
if !checkOverallContent(ir, targetModel) {
|
||||
availableWindow := util.GetAvailableWindow(targetModel.TokenConfig)
|
||||
if !checkOverallContent(ir, aiModel) {
|
||||
availableWindow := util.GetAvailableWindow(aiModel.TokenConfig)
|
||||
return nil, fmt.Errorf("整体内容超出模型窗口大小限制(可用窗口=%d tokens),请精简后重试", availableWindow)
|
||||
}
|
||||
|
||||
return compileToProviderRequest(ctx, ir, targetModel.OperatorName, targetModel.ModelName, chatModel)
|
||||
// 记录历史会话
|
||||
_, _ = dao.ComposeSession.Insert(ctx, &entity.ComposeSession{
|
||||
SessionId: req.SessionId,
|
||||
RequestContent: ir.User,
|
||||
})
|
||||
return compileToProviderRequest(ctx, ir, chatModel)
|
||||
}
|
||||
|
||||
// buildNodeTypeRequest 构建节点类型请求(BuildType=2)
|
||||
func buildNodeTypeRequest(ctx context.Context, req *dto.ComposeMessagesReq, chatModel *entity.AsynchModel, ir *PromptIR) (map[string]any, error) {
|
||||
ir.AddUser(NodeBuild(ctx, req))
|
||||
|
||||
return compileToProviderRequest(ctx, ir, req.ModelName, req.ModelName, chatModel)
|
||||
return compileToProviderRequest(ctx, ir, chatModel)
|
||||
}
|
||||
|
||||
// compileToProviderRequest 编译为 Provider 请求
|
||||
func compileToProviderRequest(ctx context.Context, ir *PromptIR, providerName string, modelName string, chatModel *entity.AsynchModel) (map[string]any, error) {
|
||||
protocol, err := GetProtocolByProvider(ctx, providerName)
|
||||
func compileToProviderRequest(ctx context.Context, ir *PromptIR, chatModel *entity.AsynchModel) (map[string]any, error) {
|
||||
protocol, err := GetProtocolByProvider(ctx, chatModel.OperatorName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取协议配置失败: %w", err)
|
||||
}
|
||||
@@ -79,7 +83,7 @@ func compileToProviderRequest(ctx context.Context, ir *PromptIR, providerName st
|
||||
}
|
||||
|
||||
return map[string]any{
|
||||
"modelName": modelName,
|
||||
"modelName": chatModel.ModelName,
|
||||
"bizName": "prompts-core",
|
||||
"callbackUrl": util.GetCallbackURL(ctx, "/prompt/callback"),
|
||||
"requestPayload": providerReq,
|
||||
|
||||
@@ -5,12 +5,9 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gitea.com/red-future/common/beans"
|
||||
"gitea.com/red-future/common/utils"
|
||||
"github.com/gogf/gf/v2/container/gvar"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
|
||||
"prompts-core/common/util"
|
||||
@@ -27,147 +24,19 @@ func ComposeMessages(ctx context.Context, req *dto.ComposeMessagesReq) (*dto.Com
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = validateUserForm(ctx, req, aiModel); err != nil {
|
||||
if err = validateUserForm(req, aiModel); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fmt.Printf("req打印%+v", req)
|
||||
switch req.BuildType {
|
||||
case public.BuildTypePrompt:
|
||||
return handlePromptBuild(ctx, req, chatModel, aiModel) // 提示词构建
|
||||
case public.BuildTypeNode:
|
||||
return handleNodeBuild(ctx, req, chatModel, aiModel) // 节点构建
|
||||
default:
|
||||
return handleDefaultCase(ctx, req)
|
||||
return nil, errors.New("BuildType 不支持")
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
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
|
||||
for attempt := 0; attempt <= maxRetryTimes; attempt++ {
|
||||
if attempt > 0 {
|
||||
g.Log().Warningf(ctx, "[重试]第 %d/%d 次调用推理模型", attempt, maxRetryTimes)
|
||||
}
|
||||
|
||||
taskID, err := callInferenceModel(ctx, req, chatModel, aiModel, history)
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "调用推理模型失败(第%d次): %v", attempt+1, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if err = saveComposeTask(ctx, taskID, req); err != nil {
|
||||
g.Log().Errorf(ctx, "保存任务记录失败(第%d次): %v", attempt+1, err)
|
||||
continue
|
||||
}
|
||||
//等待结果
|
||||
taskRecord, err = waitForResult(ctx, taskID)
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "等待结果失败(第%d次): %v", attempt+1, err)
|
||||
continue
|
||||
}
|
||||
//处理结果
|
||||
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)
|
||||
}
|
||||
|
||||
message := parseNodeBuild(taskRecord)
|
||||
|
||||
return &dto.ComposeMessagesRes{
|
||||
Messages: message,
|
||||
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{
|
||||
EpicycleId: epicycleId,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// GetModelMessage 获取模型信息
|
||||
func GetModelMessage(ctx context.Context, req *dto.ComposeMessagesReq) (*entity.AsynchModel, *entity.AsynchModel, error) {
|
||||
userInfo, err := utils.GetUserInfo(ctx)
|
||||
@@ -188,6 +57,77 @@ func GetModelMessage(ctx context.Context, req *dto.ComposeMessagesReq) (*entity.
|
||||
return chatModel, aiModel, nil
|
||||
}
|
||||
|
||||
// validateUserForm 校验用户表单
|
||||
func validateUserForm(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)
|
||||
}
|
||||
|
||||
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) {
|
||||
// 获取历史会话
|
||||
history, err := GetHistoryMessages(ctx, req.SessionId)
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "获取历史会话失败: %v,将不使用历史会话", err)
|
||||
history = nil
|
||||
}
|
||||
// 调用推理模型
|
||||
taskID, err := callInferenceModel(ctx, req, chatModel, aiModel, history)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("调用推理模型失败: %w", err)
|
||||
}
|
||||
// 保存任务记录
|
||||
if err = saveComposeTask(ctx, taskID, req); err != nil {
|
||||
return nil, fmt.Errorf("保存任务记录失败: %w", err)
|
||||
}
|
||||
return &dto.ComposeMessagesRes{
|
||||
TaskId: taskID,
|
||||
}, 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)
|
||||
}
|
||||
|
||||
return &dto.ComposeMessagesRes{
|
||||
TaskId: taskID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 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,
|
||||
BuildType: req.BuildType,
|
||||
CallbackUrl: req.CallbackUrl,
|
||||
RequestPayload: util.MustMarshal(req),
|
||||
Status: public.ComposeStatusPending,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// getChatModel 获取聊天模型
|
||||
func getChatModel(ctx context.Context, userName string) (*entity.AsynchModel, error) {
|
||||
chatModel, err := dao.Model.Get(ctx, &entity.AsynchModel{
|
||||
@@ -223,8 +163,8 @@ func getAIModel(ctx context.Context, userName, modelName string) (*entity.Asynch
|
||||
}
|
||||
|
||||
// callInferenceModel 调用推理模型
|
||||
func callInferenceModel(ctx context.Context, req *dto.ComposeMessagesReq, chatModel *entity.AsynchModel, model *entity.AsynchModel, history []map[string]any) (string, error) {
|
||||
taskReq, err := buildInferenceRequest(ctx, req, chatModel, model, history)
|
||||
func callInferenceModel(ctx context.Context, req *dto.ComposeMessagesReq, chatModel *entity.AsynchModel, idModel *entity.AsynchModel, history []map[string]any) (string, error) {
|
||||
taskReq, err := buildInferenceRequest(ctx, req, chatModel, idModel, history)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("构建推理请求失败: %w", err)
|
||||
}
|
||||
@@ -241,147 +181,6 @@ func callInferenceModel(ctx context.Context, req *dto.ComposeMessagesReq, chatMo
|
||||
return taskID, nil
|
||||
}
|
||||
|
||||
// waitForResult 等待结果
|
||||
// waitForResult 等待结果(优先channel通知,兜底网关查询)
|
||||
func waitForResult(ctx context.Context, taskID string) (*entity.ComposeTask, error) {
|
||||
timeout := time.Duration(g.Cfg().MustGet(ctx, "task.waitTimeoutSeconds", 300).Int()) * time.Second
|
||||
// 设置超时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)
|
||||
}
|
||||
|
||||
if record == nil {
|
||||
return nil, fmt.Errorf("任务不存在(taskId=%s)", taskID)
|
||||
}
|
||||
|
||||
switch record.Status {
|
||||
case public.ComposeStatusSuccess:
|
||||
return record, nil
|
||||
case public.ComposeStatusFailed:
|
||||
if strings.TrimSpace(record.ErrorMessage) == "" {
|
||||
return nil, fmt.Errorf("任务失败(taskId=%s)", taskID)
|
||||
}
|
||||
return nil, fmt.Errorf("任务失败(taskId=%s): %s", taskID, record.ErrorMessage)
|
||||
default:
|
||||
// 还在处理中,但已超时
|
||||
return nil, fmt.Errorf("等待任务回调超时(taskId=%s)", taskID)
|
||||
}
|
||||
}
|
||||
|
||||
// parsePromptBuild 解析提示词构建结果(BuildType == 1)
|
||||
func parsePromptBuild(taskRecord *entity.ComposeTask, model *entity.AsynchModel) *dto.MultiRoundResult {
|
||||
if taskRecord == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
mapped := parseTaskMessages(taskRecord.Messages)
|
||||
if mapped == nil {
|
||||
return createDefaultResult(nil)
|
||||
}
|
||||
|
||||
contentField := getContentField(model)
|
||||
contentStr, ok := mapped[contentField].(string)
|
||||
if !ok || contentStr == "" {
|
||||
return createDefaultResult(mapped)
|
||||
}
|
||||
|
||||
// 尝试解析为数组
|
||||
if roundsArray := tryParseAsMapArray(contentStr); roundsArray != nil {
|
||||
return &dto.MultiRoundResult{
|
||||
TotalRounds: len(roundsArray),
|
||||
Rounds: roundsArray,
|
||||
}
|
||||
}
|
||||
|
||||
// 尝试解析为单个对象
|
||||
if singleRound := tryParseAsMap(contentStr); singleRound != nil {
|
||||
return &dto.MultiRoundResult{
|
||||
TotalRounds: 1,
|
||||
Rounds: []map[string]any{singleRound},
|
||||
}
|
||||
}
|
||||
|
||||
// 纯文本,包装为默认格式
|
||||
return createDefaultResult(map[string]any{"content": contentStr})
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// parseTaskMessages 解析任务消息
|
||||
func parseTaskMessages(messages any) map[string]any {
|
||||
var mapped map[string]any
|
||||
|
||||
switch v := messages.(type) {
|
||||
case *gvar.Var:
|
||||
if v != nil {
|
||||
json.Unmarshal([]byte(v.String()), &mapped)
|
||||
}
|
||||
case string:
|
||||
json.Unmarshal([]byte(v), &mapped)
|
||||
case map[string]any:
|
||||
mapped = v
|
||||
default:
|
||||
b, _ := json.Marshal(v)
|
||||
json.Unmarshal(b, &mapped)
|
||||
}
|
||||
|
||||
return mapped
|
||||
}
|
||||
|
||||
// tryParseAsArray 尝试将字符串解析为数组
|
||||
func tryParseAsArray(contentStr string) []any {
|
||||
var roundsArray []any
|
||||
if err := json.Unmarshal([]byte(contentStr), &roundsArray); err != nil {
|
||||
return nil
|
||||
}
|
||||
return roundsArray
|
||||
}
|
||||
|
||||
// tryParseAsObject 尝试将字符串解析为对象
|
||||
func tryParseAsObject(contentStr string) any {
|
||||
var singleRound any
|
||||
if err := json.Unmarshal([]byte(contentStr), &singleRound); err != nil {
|
||||
return nil
|
||||
}
|
||||
return singleRound
|
||||
}
|
||||
|
||||
// createDefaultResult 创建默认结果
|
||||
func createDefaultResult(data map[string]any) *dto.MultiRoundResult {
|
||||
if data == nil {
|
||||
@@ -393,72 +192,17 @@ func createDefaultResult(data map[string]any) *dto.MultiRoundResult {
|
||||
}
|
||||
}
|
||||
|
||||
// getContentField 从模型 ResponseMapping 中获取 content 字段名
|
||||
func getContentField(model *entity.AsynchModel) string {
|
||||
if model == nil {
|
||||
return "content"
|
||||
}
|
||||
|
||||
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) {
|
||||
case *gvar.Var:
|
||||
if v != nil {
|
||||
json.Unmarshal([]byte(v.String()), &result)
|
||||
}
|
||||
case string:
|
||||
json.Unmarshal([]byte(v), &result)
|
||||
case map[string]interface{}:
|
||||
for k, val := range v {
|
||||
if s, ok := val.(string); ok {
|
||||
result[k] = s
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// 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,
|
||||
Rounds: []map[string]any{result},
|
||||
}
|
||||
}
|
||||
|
||||
// Callback 回调处理
|
||||
func Callback(ctx context.Context, req *dto.CallbackReq) error {
|
||||
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{
|
||||
composeTask, err := dao.ComposeTask.Get(ctx, &entity.ComposeTask{
|
||||
TaskId: req.TaskId,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("查询任务失败: %w", err)
|
||||
}
|
||||
if task == nil {
|
||||
if composeTask == nil {
|
||||
return fmt.Errorf("任务不存在: %s", req.TaskId)
|
||||
}
|
||||
//处理失败
|
||||
@@ -467,41 +211,134 @@ func Callback(ctx context.Context, req *dto.CallbackReq) error {
|
||||
TaskId: req.TaskId,
|
||||
Status: public.ComposeStatusFailed,
|
||||
ErrorMessage: req.ErrorMsg,
|
||||
GatewayState: req.State,
|
||||
OssFile: req.OssFile,
|
||||
FileType: req.FileType,
|
||||
ResultText: req.Text,
|
||||
})
|
||||
// 通知等待者:任务失败
|
||||
notifyWaiter(req.TaskId, nil, fmt.Errorf("任务失败: %s", req.ErrorMsg))
|
||||
// 用更新后的值发送回调
|
||||
if composeTask.CallbackUrl != "" {
|
||||
failedTask := &entity.ComposeTask{
|
||||
TaskId: req.TaskId,
|
||||
Status: public.ComposeStatusFailed,
|
||||
ErrorMessage: req.ErrorMsg,
|
||||
CallbackUrl: composeTask.CallbackUrl,
|
||||
Messages: composeTask.Messages,
|
||||
}
|
||||
gateway.SendCallback(ctx, failedTask)
|
||||
}
|
||||
return err
|
||||
}
|
||||
//处理成功
|
||||
if req.State == 2 {
|
||||
result, err := util.ParseOutput(req.Text)
|
||||
// 1. 根据 BuildType 解析结果
|
||||
var messages any
|
||||
if result != nil {
|
||||
messages = result
|
||||
switch composeTask.BuildType {
|
||||
case public.BuildTypePrompt: // 提示词构建解析
|
||||
messages = parsePromptResult(req.Text)
|
||||
case public.BuildTypeNode: // 节点构建解析
|
||||
messages = parseNodeResult(req.Text)
|
||||
default:
|
||||
messages = req.Text
|
||||
}
|
||||
// 2. 更新数据库
|
||||
_, err = dao.ComposeTask.Update(ctx, &entity.ComposeTask{
|
||||
TaskId: req.TaskId,
|
||||
Status: public.ComposeStatusSuccess,
|
||||
Messages: messages,
|
||||
TaskId: req.TaskId,
|
||||
Status: public.ComposeStatusSuccess,
|
||||
Messages: messages,
|
||||
GatewayState: req.State,
|
||||
OssFile: req.OssFile,
|
||||
FileType: req.FileType,
|
||||
ResultText: req.Text,
|
||||
})
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "[Callback] 更新任务失败 taskId=%s err=%v", req.TaskId, err)
|
||||
g.Log().Errorf(ctx, "[Callback] 更新成功状态失败 taskId=%s err=%v", req.TaskId, err)
|
||||
return err
|
||||
}
|
||||
// 4. 发送回调给业务方
|
||||
if composeTask.CallbackUrl != "" {
|
||||
successTask := &entity.ComposeTask{
|
||||
TaskId: req.TaskId,
|
||||
Status: public.ComposeStatusSuccess,
|
||||
Messages: messages,
|
||||
CallbackUrl: composeTask.CallbackUrl,
|
||||
}
|
||||
gateway.SendCallback(ctx, successTask)
|
||||
}
|
||||
notifyWaiter(req.TaskId, &entity.ComposeTask{
|
||||
TaskId: req.TaskId,
|
||||
Status: public.ComposeStatusSuccess,
|
||||
Messages: messages,
|
||||
}, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// 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)
|
||||
// parsePromptResult 解析提示词构建结果
|
||||
func parsePromptResult(raw string) *dto.MultiRoundResult {
|
||||
var wrapper map[string]any
|
||||
if err := json.Unmarshal([]byte(raw), &wrapper); err != nil {
|
||||
return createDefaultResult(map[string]any{"raw": raw})
|
||||
}
|
||||
|
||||
contentStr, ok := wrapper["content"].(string)
|
||||
if !ok || contentStr == "" {
|
||||
return createDefaultResult(wrapper)
|
||||
}
|
||||
|
||||
// 先尝试解析为数组
|
||||
if roundsArray := tryParseAsMapArray(contentStr); roundsArray != nil {
|
||||
return &dto.MultiRoundResult{
|
||||
TotalRounds: len(roundsArray),
|
||||
Rounds: roundsArray,
|
||||
}
|
||||
}
|
||||
|
||||
// 再尝试解析为单个对象
|
||||
if singleRound := tryParseAsMap(contentStr); singleRound != nil {
|
||||
return &dto.MultiRoundResult{
|
||||
TotalRounds: 1,
|
||||
Rounds: []map[string]any{singleRound},
|
||||
}
|
||||
}
|
||||
|
||||
return createDefaultResult(map[string]any{"content": contentStr})
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// parseNodeResult 解析节点构建结果
|
||||
func parseNodeResult(raw string) *dto.MultiRoundResult {
|
||||
var result map[string]any
|
||||
if err := json.Unmarshal([]byte(raw), &result); err != nil {
|
||||
return createDefaultResult(map[string]any{"raw": raw})
|
||||
}
|
||||
|
||||
if contentStr, ok := result["content"].(string); ok && contentStr != "" {
|
||||
var inner map[string]any
|
||||
if err := json.Unmarshal([]byte(contentStr), &inner); err == nil {
|
||||
result = inner
|
||||
}
|
||||
}
|
||||
|
||||
return &dto.MultiRoundResult{
|
||||
TotalRounds: 1,
|
||||
Rounds: []map[string]any{result},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,8 +23,7 @@ func SessionCallback(ctx context.Context, req *dto.SessionCallbackReq) (*dto.Ses
|
||||
}
|
||||
|
||||
result["role"] = "assistant"
|
||||
|
||||
if err := updateSessionResponse(ctx, req.EpicycleId, result); err != nil {
|
||||
if err = updateSessionResponse(ctx, req.EpicycleId, result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
@@ -1,125 +0,0 @@
|
||||
package prompt
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrTaskNotFound = errors.New("task not found")
|
||||
ErrAlreadyNotified = errors.New("task already notified")
|
||||
TaskWaiter = NewManager()
|
||||
)
|
||||
|
||||
// Result 任务结果
|
||||
type Result struct {
|
||||
Data interface{}
|
||||
Error error
|
||||
}
|
||||
|
||||
// Manager 管理异步任务等待
|
||||
type Manager struct {
|
||||
mu sync.Mutex
|
||||
waiters map[string]*waiter
|
||||
}
|
||||
|
||||
// waiter 单个等待者
|
||||
type waiter struct {
|
||||
result chan Result
|
||||
closed chan struct{}
|
||||
notifyOnce sync.Once
|
||||
}
|
||||
|
||||
// NewManager 创建管理器
|
||||
func NewManager() *Manager {
|
||||
return &Manager{
|
||||
waiters: make(map[string]*waiter),
|
||||
}
|
||||
}
|
||||
|
||||
// Wait 等待任务结果
|
||||
func (m *Manager) Wait(ctx context.Context, taskID string) (interface{}, error) {
|
||||
w := m.getOrCreate(taskID)
|
||||
defer m.remove(taskID)
|
||||
|
||||
select {
|
||||
case result := <-w.result:
|
||||
if result.Error != nil {
|
||||
return nil, result.Error
|
||||
}
|
||||
return result.Data, nil
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
case <-w.closed:
|
||||
// context取消后notify才到达的边缘情况
|
||||
select {
|
||||
case result := <-w.result:
|
||||
if result.Error != nil {
|
||||
return nil, result.Error
|
||||
}
|
||||
return result.Data, nil
|
||||
default:
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Notify 通知任务完成(安全,无阻塞)
|
||||
func (m *Manager) Notify(taskID string, data interface{}, err error) error {
|
||||
m.mu.Lock()
|
||||
w, exists := m.waiters[taskID]
|
||||
if !exists {
|
||||
m.mu.Unlock()
|
||||
return ErrTaskNotFound
|
||||
}
|
||||
|
||||
var notified bool
|
||||
w.notifyOnce.Do(func() {
|
||||
notified = true
|
||||
close(w.closed) // 先关闭信号channel
|
||||
// 根据err构造Result
|
||||
if err != nil {
|
||||
w.result <- Result{Error: err}
|
||||
} else {
|
||||
w.result <- Result{Data: data}
|
||||
}
|
||||
})
|
||||
m.mu.Unlock()
|
||||
|
||||
if !notified {
|
||||
return ErrAlreadyNotified
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// getOrCreate 获取或创建等待者
|
||||
func (m *Manager) getOrCreate(taskID string) *waiter {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
if w, exists := m.waiters[taskID]; exists {
|
||||
return w
|
||||
}
|
||||
|
||||
w := &waiter{
|
||||
result: make(chan Result, 1),
|
||||
closed: make(chan struct{}),
|
||||
}
|
||||
m.waiters[taskID] = w
|
||||
return w
|
||||
}
|
||||
|
||||
// remove 安全移除等待者
|
||||
func (m *Manager) remove(taskID string) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
delete(m.waiters, taskID)
|
||||
}
|
||||
|
||||
// ActiveCount 当前活跃等待数量
|
||||
func (m *Manager) ActiveCount() int {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
return len(m.waiters)
|
||||
}
|
||||
Reference in New Issue
Block a user