diff --git a/common/util/json.go b/common/util/json.go index ed16af0..e7ef599 100644 --- a/common/util/json.go +++ b/common/util/json.go @@ -3,10 +3,14 @@ package util import ( "encoding/json" "fmt" + "strconv" "github.com/gogf/gf/v2/container/gvar" "github.com/gogf/gf/v2/encoding/gjson" "github.com/gogf/gf/v2/util/gconv" + + tGjson "github.com/tidwall/gjson" + "github.com/tidwall/sjson" ) // ParseOutput 解析模型输出为 JSON 格式 @@ -57,14 +61,15 @@ func UserFormToJSON(form []map[string]any) string { return string(b) } -// MustMarshal 将对象序列化为 JSON 字符串,失败时返回空对象 -func MustMarshal(v any) string { +// MustMarshalToMap 将对象序列化为 map[string]any,失败时返回空 map +func MustMarshalToMap(v any) map[string]any { b, err := json.Marshal(v) if err != nil { - return "{}" + return make(map[string]any) } - - return string(b) + var m map[string]any + json.Unmarshal(b, &m) + return m } // JSONPretty 将任意类型转为格式化的 JSON 字符串 @@ -82,42 +87,6 @@ func JSONPretty(v any) string { return string(b) } -// GvarToMap 将 *gvar.Var 类型转换为 map[string]any -func GvarToMap(v *gvar.Var) map[string]any { - if v == nil || v.IsNil() { - return nil - } - - result := make(map[string]any) - - // 方法1:尝试获取 map 值 - if m := v.Map(); len(m) > 0 { - return m - } - - // 方法2:尝试解析 JSON 字符串 - str := v.String() - if str != "" && str != "" { - json.Unmarshal([]byte(str), &result) - if len(result) > 0 { - return result - } - } - - // 方法3:尝试获取 interface 再转换 - if val := v.Val(); val != nil { - switch val.(type) { - case map[string]any: - return val.(map[string]any) - default: - data, _ := json.Marshal(val) - json.Unmarshal(data, &result) - } - } - - return result -} - // ParseJSONFieldFromGvar 专门处理 *gvar.Var 类型的 JSON 字段解析 func ParseJSONFieldFromGvar(source any, target any) { if source == nil { @@ -149,3 +118,112 @@ func ParseJSONFieldFromGvar(source any, target any) { json.Unmarshal(data, target) } } + +// MergeConsult 将 consult 附件合并到模型生成的 messages 结构中。 +// +// 参数说明: +// - req: 请求参数 map,需包含 "consult" 字段,值为 []any,每个元素是 {"type":"xxx","url":"..."} +// - messages: 模型生成的返回结构(如 rounds[...].messages[...].content 数组) +// - extendMapping: 附加映射配置,格式: +// {"attachments": {"image": {"template": {...}, "target_path": "...", "field_mapping": {...}}, ...}} +// +// 返回值:合并后的完整 map。 +func MergeConsult(req map[string]any, messages map[string]any, extendMapping map[string]any) map[string]any { + if len(req) == 0 || len(messages) == 0 || len(extendMapping) == 0 { + return messages + } + + reqJSON, _ := json.Marshal(req) + msgJSON, _ := json.Marshal(messages) + extJSON, _ := json.Marshal(extendMapping) + + reqStr := string(reqJSON) + msgStr := string(msgJSON) + extStr := string(extJSON) + + // 获取 consult 数组 + consultResult := tGjson.Get(reqStr, "consult") + if !consultResult.Exists() || !consultResult.IsArray() { + return messages + } + + // 获取 attachments 配置 + attachmentsResult := tGjson.Get(extStr, "attachments") + if !attachmentsResult.Exists() || !attachmentsResult.IsObject() { + return messages + } + + consultArr := consultResult.Array() + attachmentsMap := attachmentsResult.Map() + + for _, consultItem := range consultArr { + if !consultItem.IsObject() { + continue + } + + itemType := consultItem.Get("type").String() + if itemType == "" { + continue + } + + // 查找对应类型的附件配置 + attachResult, ok := attachmentsMap[itemType] + if !ok || !attachResult.IsObject() { + continue + } + + // 获取模板 + templateResult := attachResult.Get("template") + if !templateResult.Exists() || !templateResult.IsObject() { + continue + } + + // 深拷贝模板 + filledTemplateStr := templateResult.Raw + + // 应用字段映射 + fieldMappingResult := attachResult.Get("field_mapping") + if fieldMappingResult.Exists() && fieldMappingResult.IsObject() { + fieldMapping := fieldMappingResult.Map() + for fieldPath, valueSource := range fieldMapping { + sourceKey := valueSource.String() + valueResult := consultItem.Get(sourceKey) + if valueResult.Exists() { + var err error + filledTemplateStr, err = sjson.SetRaw(filledTemplateStr, fieldPath, valueResult.Raw) + if err != nil { + continue + } + } + } + } + + // 获取目标路径 + targetPath := attachResult.Get("target_path").String() + if targetPath == "" { + continue + } + + // 检查目标路径是否存在且为数组 + targetResult := tGjson.Get(msgStr, targetPath) + if !targetResult.Exists() || !targetResult.IsArray() { + continue + } + + // 追加到数组末尾 + arrLen := len(targetResult.Array()) + appendPath := targetPath + "." + strconv.Itoa(arrLen) + var err error + msgStr, err = sjson.SetRaw(msgStr, appendPath, filledTemplateStr) + if err != nil { + continue + } + } + + // 转回 map[string]any + var result map[string]any + if err := json.Unmarshal([]byte(msgStr), &result); err != nil { + return messages + } + return result +} diff --git a/controller/prompt_compose_controller.go b/controller/prompt_compose_controller.go index 3d3e200..1996b94 100644 --- a/controller/prompt_compose_controller.go +++ b/controller/prompt_compose_controller.go @@ -2,9 +2,16 @@ package controller import ( "context" + "prompts-core/common/util" + "prompts-core/dao" "prompts-core/model/dto" + "prompts-core/model/entity" promptService "prompts-core/service/prompt" + + "gitea.com/red-future/common/beans" + "github.com/gogf/gf/v2/encoding/gjson" + "github.com/gogf/gf/v2/frame/g" ) type prompt struct{} @@ -27,3 +34,31 @@ func (c *prompt) Callback(ctx context.Context, req *dto.CallbackReq) (res *dto.C func (c *prompt) GetComposeTask(ctx context.Context, req *dto.GetComposeTaskReq) (res *dto.GetComposeTaskRes, err error) { return promptService.GetComposeTask(ctx, req.TaskId) } + +func (c *prompt) Text(ctx context.Context, req *dto.TextReq) (res *dto.TextRes, err error) { + composeTask, err := dao.ComposeTask.Get(ctx, &entity.ComposeTask{ + TaskId: "c58c9296-994f-4e83-8285-1daebf3c492d", + }) + if err != nil { + return + } + model, err := dao.Model.Get(ctx, &entity.AsynchModel{ + SQLBaseDO: beans.SQLBaseDO{Creator: composeTask.Creator}, + ModelName: composeTask.ModelName, + }) + if err != nil { + return + } + message := promptService.ParsePromptResult(composeTask.ResultText) + + // 加这两行 + g.Log().Infof(ctx, "[Text] RequestPayload.consult: %v", composeTask.RequestPayload["consult"]) + g.Log().Infof(ctx, "[Text] ExtendMapping: %v", model.ExtendMapping) + messages := util.MergeConsult(composeTask.RequestPayload, message, model.ExtendMapping) + g.Log().Infof(ctx, "[Text] MergeConsult 结果 rounds[0].messages[0].content: %v", + gjson.New(messages).Get("rounds.0.messages.0.content")) + res = &dto.TextRes{ + Messages: messages, + } + return +} diff --git a/go.mod b/go.mod index 5dfab77..97e91c6 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/gogf/gf/contrib/drivers/pgsql/v2 v2.10.0 github.com/gogf/gf/contrib/nosql/redis/v2 v2.10.0 github.com/gogf/gf/v2 v2.10.0 + github.com/tidwall/sjson v1.2.5 ) require ( @@ -68,7 +69,7 @@ require ( github.com/r3labs/diff/v2 v2.15.1 // indirect github.com/redis/go-redis/v9 v9.12.1 // indirect github.com/rivo/uniseg v0.4.7 // indirect - github.com/tidwall/gjson v1.19.0 + github.com/tidwall/gjson v1.19.0 // indirect github.com/tiger1103/gfast-token v1.0.10 // indirect github.com/vcaesar/cedar v0.30.0 // indirect github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect diff --git a/go.sum b/go.sum index 97621ce..21da8e2 100644 --- a/go.sum +++ b/go.sum @@ -288,12 +288,15 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU= github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/tiger1103/gfast-token v1.0.10 h1:fNiBE/Dq5iTHvTGlCx3DmXa2o4hr0NtumFpffZ39k6s= github.com/tiger1103/gfast-token v1.0.10/go.mod h1:a/21mxmj7zFeNvjhZSC0XpEAFHfb1aT2k6DXnufFU1s= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= diff --git a/model/dto/prompt_compose_dto.go b/model/dto/prompt_compose_dto.go index ffe076f..9a6ac31 100644 --- a/model/dto/prompt_compose_dto.go +++ b/model/dto/prompt_compose_dto.go @@ -11,19 +11,19 @@ type ComposeMessagesReq struct { CallbackUrl string `p:"callbackUrl" json:"callbackUrl" dc:"回调地址"` Form map[string]any `p:"form" json:"form" dc:"系统表单:form 下所有字段都作为系统提示词来源"` UserForm []map[string]any `p:"userForm" json:"userForm" dc:"用户表单:userForm 下所有字段都作为用户提示词来源;若与 form 含义接近则严格覆盖系统字段"` + Consult []ConsultItem `json:"consult" dc:"附件列表(图片/视频/音频)"` SkillName string `p:"skillName" json:"skillName" dc:"技能名称"` - UserFiles []string `p:"userFiles" json:"userFiles" dc:"用户附件地址列表"` } +// ConsultItem 单个附件 +type ConsultItem struct { + Type string `json:"type" dc:"附件类型:image/video/audio"` + Url string `json:"url" dc:"附件地址"` +} type ComposeMessagesRes struct { TaskId string `json:"taskId" dc:"任务ID"` } -/* - Messages *MultiRoundResult `json:"messages,omitempty" dc:"最终消息数组"` - EpicycleId int64 `json:"epicycleId" dc:"轮次ID"` -*/ - // MultiRoundResult 多轮返回结果 type MultiRoundResult struct { TotalRounds int `json:"total_rounds"` // 总轮数 @@ -58,3 +58,11 @@ type GetComposeTaskRes struct { OssFile string `json:"ossFile" dc:"结果文件地址"` FileType string `json:"fileType" dc:"结果文件类型"` } + +type TextReq struct { + g.Meta `path:"/text" method:"post" tags:"提示词处理" summary:"拼接提示词" dc:"按 modelTypeId 读取 prompts_model_prompt.prompt_info 与 response_json_schema;form 作为系统表单,userForm 作为用户表单,结合 userFiles 调用 model-gateway,并直接返回最终 messages"` +} + +type TextRes struct { + Messages any `json:"messages" dc:"文本结果"` +} diff --git a/model/entity/asynch_model.go b/model/entity/asynch_model.go index 0166240..ab59c7d 100644 --- a/model/entity/asynch_model.go +++ b/model/entity/asynch_model.go @@ -2,37 +2,6 @@ package entity import "gitea.com/red-future/common/beans" -// AsynchModel 异步模型配置 -type AsynchModel struct { - beans.SQLBaseDO `orm:",inline"` - ModelName string `orm:"model_name" json:"modelName"` - ModelType int `orm:"model_type" json:"modelType"` - BaseURL string `orm:"base_url" json:"baseUrl"` - HttpMethod string `orm:"http_method" json:"httpMethod"` - HeadMsg string `orm:"head_msg" json:"headMsg"` - Form any `orm:"form_json" json:"form"` - RequestMapping any `orm:"request_mapping" json:"requestMapping"` - ResponseMapping any `orm:"response_mapping" json:"responseMapping"` - ResponseBody any `orm:"response_body" json:"responseBody"` - ResponseTokenField string `orm:"response_token_field" json:"responseTokenField"` - Prompt string `orm:"prompt" json:"prompt"` - IsPrivate *int `orm:"is_private" json:"isPrivate"` - IsChatModel *int `orm:"is_chat_model" json:"isChatModel"` - ApiKey string `orm:"api_key" json:"apiKey"` - Enabled *int `orm:"enabled" json:"enabled"` - MaxConcurrency int `orm:"max_concurrency" json:"maxConcurrency"` - QueueLimit int `orm:"queue_limit" json:"queueLimit"` - TimeoutSeconds int `orm:"timeout_seconds" json:"timeoutSeconds"` - ExpectedSeconds int `orm:"expected_seconds" json:"expectedSeconds"` - RetryTimes int `orm:"retry_times" json:"retryTimes"` - RetryQueueMaxSeconds int `orm:"retry_queue_max_seconds" json:"retryQueueMaxSeconds"` - AutoCleanSeconds int `orm:"auto_clean_seconds" json:"autoCleanSeconds"` - Remark string `orm:"remark" json:"remark"` - IsOwner *int `json:"isOwner" orm:"is_owner"` - OperatorName string `orm:"operator_name" json:"operatorName"` - TokenConfig any `orm:"token_config" json:"tokenConfig"` -} - type asynchModelCol struct { beans.SQLBaseCol ModelName string @@ -61,6 +30,8 @@ type asynchModelCol struct { IsOwner string OperatorName string TokenConfig string + ExtendMapping string + QueryConfig string } var AsynchModelCol = asynchModelCol{ @@ -91,4 +62,39 @@ var AsynchModelCol = asynchModelCol{ IsOwner: "is_owner", OperatorName: "operator_name", TokenConfig: "token_config", + ExtendMapping: "extend_mapping", + QueryConfig: "query_config", +} + +// AsynchModel 异步模型配置 +type AsynchModel struct { + beans.SQLBaseDO `orm:",inline"` + ModelName string `orm:"model_name" json:"modelName"` + ModelType int `orm:"model_type" json:"modelType"` + BaseURL string `orm:"base_url" json:"baseUrl"` + HttpMethod string `orm:"http_method" json:"httpMethod"` + HeadMsg string `orm:"head_msg" json:"headMsg"` + Form map[string]any `orm:"form_json" json:"form"` + RequestMapping map[string]any `orm:"request_mapping" json:"requestMapping"` + ResponseMapping map[string]any `orm:"response_mapping" json:"responseMapping"` + ResponseBody map[string]any `orm:"response_body" json:"responseBody"` + ResponseTokenField string `orm:"response_token_field" json:"responseTokenField"` + Prompt string `orm:"prompt" json:"prompt"` + IsPrivate *int `orm:"is_private" json:"isPrivate"` + IsChatModel *int `orm:"is_chat_model" json:"isChatModel"` + ApiKey string `orm:"api_key" json:"apiKey"` + Enabled *int `orm:"enabled" json:"enabled"` + MaxConcurrency int `orm:"max_concurrency" json:"maxConcurrency"` + QueueLimit int `orm:"queue_limit" json:"queueLimit"` + TimeoutSeconds int `orm:"timeout_seconds" json:"timeoutSeconds"` + ExpectedSeconds int `orm:"expected_seconds" json:"expectedSeconds"` + RetryTimes int `orm:"retry_times" json:"retryTimes"` + RetryQueueMaxSeconds int `orm:"retry_queue_max_seconds" json:"retryQueueMaxSeconds"` + AutoCleanSeconds int `orm:"auto_clean_seconds" json:"autoCleanSeconds"` + Remark string `orm:"remark" json:"remark"` + IsOwner *int `json:"isOwner" orm:"is_owner"` + OperatorName string `orm:"operator_name" json:"operatorName"` + TokenConfig map[string]any `orm:"token_config" json:"tokenConfig"` + ExtendMapping map[string]any `orm:"extend_mapping" json:"extendMapping"` + QueryConfig map[string]any `orm:"query_config" json:"queryConfig"` } diff --git a/model/entity/prompts_compose_task.go b/model/entity/prompts_compose_task.go index 2645045..a185c75 100644 --- a/model/entity/prompts_compose_task.go +++ b/model/entity/prompts_compose_task.go @@ -4,19 +4,19 @@ import "gitea.com/red-future/common/beans" type ComposeTask struct { beans.SQLBaseDO `orm:",inline"` - TaskId string `orm:"task_id" json:"taskId"` - ModelName string `orm:"model_name" json:"modelName"` - SkillName string `orm:"skill_name" json:"skillName"` - BuildType int `orm:"build_type" json:"buildType"` - CallbackUrl string `orm:"callback_url" json:"callbackUrl"` - GatewayState int `orm:"gateway_state" json:"gatewayState"` - RequestPayload any `orm:"request_payload" json:"requestPayload"` - ResultText string `orm:"result_text" json:"resultText"` - Messages any `orm:"messages" json:"messages"` - Status string `orm:"status" json:"status"` - ErrorMessage string `orm:"error_message" json:"errorMessage"` - OssFile string `orm:"oss_file" json:"ossFile"` - FileType string `orm:"file_type" json:"fileType"` + TaskId string `orm:"task_id" json:"taskId"` + ModelName string `orm:"model_name" json:"modelName"` + SkillName string `orm:"skill_name" json:"skillName"` + BuildType int `orm:"build_type" json:"buildType"` + CallbackUrl string `orm:"callback_url" json:"callbackUrl"` + GatewayState int `orm:"gateway_state" json:"gatewayState"` + RequestPayload map[string]any `orm:"request_payload" json:"requestPayload"` + ResultText string `orm:"result_text" json:"resultText"` + Messages map[string]any `orm:"messages" json:"messages"` + Status string `orm:"status" json:"status"` + ErrorMessage string `orm:"error_message" json:"errorMessage"` + OssFile string `orm:"oss_file" json:"ossFile"` + FileType string `orm:"file_type" json:"fileType"` } type composeTaskCol struct { diff --git a/service/prompt/prompt_build_service.go b/service/prompt/prompt_build_service.go index c9dd432..9aa5ccd 100644 --- a/service/prompt/prompt_build_service.go +++ b/service/prompt/prompt_build_service.go @@ -12,9 +12,21 @@ import ( "prompts-core/model/dto" "prompts-core/model/entity" + "github.com/gogf/gf/v2/encoding/gjson" "github.com/gogf/gf/v2/util/gconv" ) +// UserPromptPayload 用户提示词请求体 +type UserPromptPayload struct { + Model string `json:"model"` + PromptInfo string `json:"promptInfo"` + Form map[string]any `json:"form"` + UserForm any `json:"userForm"` + Consult []dto.ConsultItem `json:"consult"` + UserFilesText map[string]string `json:"userFilesText"` + Skills string `json:"skills"` +} + // buildInferenceRequest 构建推理请求 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) @@ -117,7 +129,7 @@ func promptBuildWithRounds(ctx context.Context, req *dto.ComposeMessagesReq, mod %s 技能名称: %s 用户文件: %v -`, req.ModelName, formInfo, req.SkillName, req.UserFiles) +`, req.ModelName, formInfo, req.SkillName, req.Consult) return fmt.Sprintf(providerProtocol.SystemPromptTemplate, req.ModelName, // %s 目标模型名称 @@ -148,19 +160,16 @@ func checkOverallContent(ir *PromptIR, model *entity.AsynchModel) bool { // buildUserPrompt 构建用户提示词 func buildUserPrompt(ctx context.Context, req *dto.ComposeMessagesReq, prompt string) string { - userFormForPayload := prepareUserFormPayload(req.UserForm) - - payload := map[string]any{ - "model": req.ModelName, - "promptInfo": prompt, - "form": req.Form, - "userForm": userFormForPayload, - "userFiles": req.UserFiles, - "userFilesText": FetchFileTexts(ctx, req.UserFiles), - "skills": SkillMdContent(ctx, req.SkillName), + payload := UserPromptPayload{ + Model: req.ModelName, + PromptInfo: prompt, + Form: req.Form, + UserForm: prepareUserFormPayload(req.UserForm), + Consult: req.Consult, + UserFilesText: ExtractFileTexts(ctx, req.Consult), + Skills: SkillMdContent(ctx, req.SkillName), } - - return util.MustMarshal(payload) + return gjson.New(payload).String() } // prepareUserFormPayload 准备用户表单载荷 diff --git a/service/prompt/prompt_compose_service.go b/service/prompt/prompt_compose_service.go index 1157164..9bf0b88 100644 --- a/service/prompt/prompt_compose_service.go +++ b/service/prompt/prompt_compose_service.go @@ -8,6 +8,7 @@ import ( "gitea.com/red-future/common/beans" "gitea.com/red-future/common/utils" + "github.com/gogf/gf/v2/encoding/gjson" "github.com/gogf/gf/v2/frame/g" "prompts-core/common/util" @@ -122,7 +123,7 @@ func saveComposeTask(ctx context.Context, taskID string, req *dto.ComposeMessage SkillName: req.SkillName, BuildType: req.BuildType, CallbackUrl: req.CallbackUrl, - RequestPayload: util.MustMarshal(req), + RequestPayload: util.MustMarshalToMap(req), Status: public.ComposeStatusPending, }) return err @@ -182,13 +183,13 @@ func callInferenceModel(ctx context.Context, req *dto.ComposeMessagesReq, chatMo } // createDefaultResult 创建默认结果 -func createDefaultResult(data map[string]any) *dto.MultiRoundResult { +func createDefaultResult(data map[string]any) map[string]any { if data == nil { data = make(map[string]any) } - return &dto.MultiRoundResult{ - TotalRounds: 1, - Rounds: []map[string]any{data}, + return map[string]any{ + "total_rounds": 1, + "rounds": []map[string]any{data}, } } @@ -196,14 +197,19 @@ func createDefaultResult(data map[string]any) *dto.MultiRoundResult { 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)) + // 查询任务 composeTask, err := dao.ComposeTask.Get(ctx, &entity.ComposeTask{ TaskId: req.TaskId, }) if err != nil { return fmt.Errorf("查询任务失败: %w", err) } - if composeTask == nil { - return fmt.Errorf("任务不存在: %s", req.TaskId) + model, err := dao.Model.Get(ctx, &entity.AsynchModel{ + SQLBaseDO: beans.SQLBaseDO{Creator: composeTask.Creator}, + ModelName: composeTask.ModelName, + }) + if err != nil { + return fmt.Errorf("查询模型失败: %w", err) } //处理失败 if req.State == 3 { @@ -232,16 +238,18 @@ func Callback(ctx context.Context, req *dto.CallbackReq) error { //处理成功 if req.State == 2 { // 1. 根据 BuildType 解析结果 - var messages any + var messages map[string]any switch composeTask.BuildType { case public.BuildTypePrompt: // 提示词构建解析 - messages = parsePromptResult(req.Text) + messages = ParsePromptResult(req.Text) case public.BuildTypeNode: // 节点构建解析 - messages = parseNodeResult(req.Text) + messages = ParseNodeResult(req.Text) default: - messages = req.Text + messages = gjson.New(req.Text).Map() } - // 2. 更新数据库 + // 2. 处理附加字段 + messages = util.MergeConsult(composeTask.RequestPayload, messages, model.ExtendMapping) + // 3. 更新数据库 _, err = dao.ComposeTask.Update(ctx, &entity.ComposeTask{ TaskId: req.TaskId, Status: public.ComposeStatusSuccess, @@ -269,8 +277,8 @@ func Callback(ctx context.Context, req *dto.CallbackReq) error { return err } -// parsePromptResult 解析提示词构建结果 -func parsePromptResult(raw string) *dto.MultiRoundResult { +// ParsePromptResult 解析提示词构建结果 +func ParsePromptResult(raw string) map[string]any { var wrapper map[string]any if err := json.Unmarshal([]byte(raw), &wrapper); err != nil { return createDefaultResult(map[string]any{"raw": raw}) @@ -283,17 +291,17 @@ func parsePromptResult(raw string) *dto.MultiRoundResult { // 先尝试解析为数组 if roundsArray := tryParseAsMapArray(contentStr); roundsArray != nil { - return &dto.MultiRoundResult{ - TotalRounds: len(roundsArray), - Rounds: roundsArray, + return map[string]any{ + "total_rounds": len(roundsArray), + "rounds": roundsArray, } } // 再尝试解析为单个对象 if singleRound := tryParseAsMap(contentStr); singleRound != nil { - return &dto.MultiRoundResult{ - TotalRounds: 1, - Rounds: []map[string]any{singleRound}, + return map[string]any{ + "total_rounds": 1, + "rounds": []map[string]any{singleRound}, } } @@ -322,8 +330,8 @@ func tryParseAsMap(jsonStr string) map[string]any { return obj } -// parseNodeResult 解析节点构建结果 -func parseNodeResult(raw string) *dto.MultiRoundResult { +// ParseNodeResult 解析节点构建结果 +func ParseNodeResult(raw string) map[string]any { var result map[string]any if err := json.Unmarshal([]byte(raw), &result); err != nil { return createDefaultResult(map[string]any{"raw": raw}) @@ -335,10 +343,9 @@ func parseNodeResult(raw string) *dto.MultiRoundResult { result = inner } } - - return &dto.MultiRoundResult{ - TotalRounds: 1, - Rounds: []map[string]any{result}, + return map[string]any{ + "total_rounds": 1, + "rounds": []map[string]any{result}, } } diff --git a/service/prompt/prompt_files_handle_service.go b/service/prompt/prompt_files_handle_service.go index 8f749de..30f6821 100644 --- a/service/prompt/prompt_files_handle_service.go +++ b/service/prompt/prompt_files_handle_service.go @@ -7,6 +7,7 @@ import ( "fmt" "io" "net/http" + "prompts-core/model/dto" "strings" "time" @@ -21,6 +22,17 @@ const ( bytesPerMB = 1024 * 1024 ) +// ExtractFileTexts 从 ConsultItem 列表中提取文件内容 +func ExtractFileTexts(ctx context.Context, consult []dto.ConsultItem) map[string]string { + urls := make([]string, 0, len(consult)) + for _, item := range consult { + if item.Url != "" { + urls = append(urls, item.Url) + } + } + return FetchFileTexts(ctx, urls) +} + // FetchFileTexts 从 URL 列表获取文件内容,支持 zip 内文件 func FetchFileTexts(ctx context.Context, urls []string) map[string]string { result := make(map[string]string) diff --git a/service/prompt/prompt_files_handle_service.markdown b/service/prompt/prompt_files_handle_service.markdown deleted file mode 100644 index bd6cbaf..0000000 --- a/service/prompt/prompt_files_handle_service.markdown +++ /dev/null @@ -1,75 +0,0 @@ -# Prompts-Core(提示词核心服务) - -> 智能提示词构建与管理系统,支持多模态 AI 模型的提示词组装、会话管理和协议适配。 - ---- - -## 项目简介 - -**Prompts-Core** 是一个基于 Go 语言开发的提示词核心服务,作为 AI 应用层与模型网关之间的桥梁,负责将业务需求转换为标准化的模型请求。 - -### 核心价值 -- **统一提示词管理**:集中化管理不同模型类型的提示词模板 -- **智能会话维护**:基于 Redis + PostgreSQL 的双层会话存储 -- **多协议适配**:支持 OpenAI、DeepSeek、Qwen、Gemini 等多种模型协议 -- **文件处理能力**:自动提取文本文件和 ZIP 压缩包内容 -- **技能系统集成**:支持从外部加载 Markdown 格式的技能描述 - ---- - -## 核心功能 - -### 1. 提示词构建引擎 - -#### 多模态支持 -| 类型 | 说明 | 适用场景 | -|------|------|----------| -| Type 1 | 文字处理助手 | 文章撰写、文案优化、翻译等 | -| Type 2 | 图片处理助手 | 图像生成、风格迁移等 | -| Type 3 | 音频处理助手 | 语音合成、识别、降噪等 | -| Type 4 | 向量化处理助手 | 语义检索、知识索引等 | -| Type 5 | 全模态助手 | 跨模态转换、多模态融合等 | - -#### 构建模式 -- **BuildType 1(提示词构建)**:完整流程,包含系统提示词、历史会话、用户输入的智能组装 -- **BuildType 2(节点构建)**:工作流路由决策,根据上下文选择节点 ID - -#### 分批处理 -当用户表单内容超出模型窗口限制时,自动按 Token 大小分批处理。 - -### 2. 会话管理系统 - -- **双层存储**:Redis 缓存(最近 N 轮)+ PostgreSQL 持久化 -- **自动管理**:最大轮数控制(默认 10 轮)、自动过期(默认 30 分钟) - -### 3. 协议适配器 - -通过配置动态支持多种模型协议: -- 角色映射:system/user/assistant → 目标协议角色 -- 内容字段映射:content → parts.text 等 -- 消息顺序控制:灵活配置拼接顺序 -- 请求模板渲染:支持占位符替换 - -### 4. 任务调度 - -- **异步流程**:创建网关任务 → 轮询等待 → 接收回调 → 返回结果 -- **重试机制**:可配置最大重试次数(默认 3 次) -- **超时保护**:默认 300 秒超时 - ---- - -## 技术架构 - -### 技术栈 - -| 组件 | 版本 | 用途 | -|------|------|------| -| Go | 1.26.0 | 编程语言 | -| GoFrame | v2.10.0 | Web 框架 | -| PostgreSQL | - | 关系型数据库 | -| Redis | - | 缓存与会话存储 | -| Consul | - | 服务注册与发现 | -| Jaeger | - | 分布式链路追踪 | - -### 架构图 -