package utils import ( "context" "fmt" "strings" "model-gateway/dao" "model-gateway/model/dto" "model-gateway/model/entity" "github.com/gogf/gf/v2/util/gconv" ) // ====================== 模型引用解析层 ====================== // 引用行(ref_system_model_id>0) 调用时实时取系统模型配置 + 本人 apiKey 合成可执行配置; // 系统模型调整零同步。Id 被覆盖为系统模型 id → 计价/并发键按系统模型走(会话/任务落库仍用引用行 id)。 const apiKeyPlaceholder = "{apiKey}" // replacePlaceholder 递归替换 map/slice/string 中的占位符(泛化自 task_end 的 replaceTaskPlaceholder,非破坏式)。 func replacePlaceholder(v any, from, to string) any { switch val := v.(type) { case string: return strings.ReplaceAll(val, from, to) case map[string]any: m := make(map[string]any, len(val)) for k, x := range val { m[k] = replacePlaceholder(x, from, to) } return m case map[string]string: m := make(map[string]string, len(val)) for k, x := range val { m[k] = strings.ReplaceAll(x, from, to) } return m case []any: arr := make([]any, len(val)) for i, x := range val { arr[i] = replacePlaceholder(x, from, to) } return arr default: return v } } // copyAndReplaceStringMap 非破坏式替换 map[string]string 值(新建 map,不污染入参) func copyAndReplaceStringMap(src map[string]string, from, to string) map[string]string { if src == nil { return nil } m := make(map[string]string, len(src)) for k, v := range src { m[k] = strings.ReplaceAll(v, from, to) } return m } // substituteAPIPlaceholder 把输入侧配置中的 {apiKey} 替换为生效 key(非破坏式:新建 map/struct,不污染入参)。 // 覆盖 BaseURL / RequestHeadMapping / RequestBodyMapping / RequestBusinessFieldMapping / // AsyncTaskMapping(Url/RequestHeadMapping/RequestBodyMapping)。 func substituteAPIPlaceholder(m *entity.ModelManage, key string) { m.BaseURL = strings.ReplaceAll(m.BaseURL, apiKeyPlaceholder, key) m.RequestHeadMapping = copyAndReplaceStringMap(m.RequestHeadMapping, apiKeyPlaceholder, key) m.RequestBusinessFieldMapping = copyAndReplaceStringMap(m.RequestBusinessFieldMapping, apiKeyPlaceholder, key) if v, ok := replacePlaceholder(m.RequestBodyMapping, apiKeyPlaceholder, key).(map[string]any); ok { m.RequestBodyMapping = v } if a := m.AsyncTaskMapping; a != nil { ac := *a ac.Url = strings.ReplaceAll(a.Url, apiKeyPlaceholder, key) ac.RequestHeadMapping = copyAndReplaceStringMap(a.RequestHeadMapping, apiKeyPlaceholder, key) if v, ok := replacePlaceholder(a.RequestBodyMapping, apiKeyPlaceholder, key).(map[string]any); ok { ac.RequestBodyMapping = v } m.AsyncTaskMapping = &ac } } // mergeReferenceConfig 引用行 + 系统行 → 有效配置(纯函数,便于单测)。 // 配置字段取系统行;个人字段(apiKey/enabled/chatModel)取引用行;enabled 取 AND(系统停用=引用失效)。 func mergeReferenceConfig(stub, sys *entity.ModelManage) *entity.ModelManage { out := *sys out.RefSystemModelId = stub.RefSystemModelId // 保留引用标记,调用侧据此判断引用行门禁(Id 已覆盖为系统模型 id) out.ApiKey = stub.ApiKey if stub.Enabled != nil { out.Enabled = stub.Enabled } if stub.ChatModel != nil { out.ChatModel = stub.ChatModel } if sys.Enabled != nil && !*sys.Enabled { out.Enabled = gconv.PtrBool(false) } return &out } // MergeReferenceConfigForQuery 管理端 Get 查询展示用:以引用行为基底,把系统行的配置列合入, // 保留引用行自身 Id/RefSystemModelId/SystemModel/Creator/时间戳与个人字段(apiKey/enabled/chatModel)。 // 与 mergeReferenceConfig 的区别:不替换 {apiKey}(Get 非引用行也不替换,展示模板), // enabled 不做 AND(展示引用行个人开关,调用时才按系统行生效状态门禁)。 func MergeReferenceConfigForQuery(stub, sys *entity.ModelManage) *entity.ModelManage { out := *stub out.BaseURL = sys.BaseURL out.HttpMethod = sys.HttpMethod out.ResponseType = sys.ResponseType out.RequestHeadMapping = sys.RequestHeadMapping out.RequestBodyMapping = sys.RequestBodyMapping out.RequestBusinessFieldMapping = sys.RequestBusinessFieldMapping out.ResponseMapping = sys.ResponseMapping out.ResponseBodyMapping = sys.ResponseBodyMapping out.ResponseBusinessFieldMapping = sys.ResponseBusinessFieldMapping out.MaxConcurrency = sys.MaxConcurrency out.TokenMapping = sys.TokenMapping out.AsyncTaskMapping = sys.AsyncTaskMapping out.TokenPredictPrice = sys.TokenPredictPrice out.TokenPredictPriceUnit = sys.TokenPredictPriceUnit out.MaxTokens = sys.MaxTokens out.MinDuration = sys.MinDuration out.MaxDuration = sys.MaxDuration out.LastFrame = sys.LastFrame out.ErrorMessageMapping = sys.ErrorMessageMapping return &out } // ResolveModelConfig 把请求命中的模型行解析为可执行配置: // 引用行 → 系统行配置 + 引用行 apiKey(Id 覆盖为系统模型 id);非引用行 → 原配置 + 自身 apiKey 替换占位。 // 引用系统模型已删除 → 报错(调用方阻塞)。 func ResolveModelConfig(ctx context.Context, m *entity.ModelManage) (*entity.ModelManage, error) { if m == nil { return nil, nil } if m.RefSystemModelId > 0 { sys, err := dao.ModelManage.GetNotTenantId(ctx, &dto.GetModelManageReq{Id: m.RefSystemModelId}) if err != nil { return nil, err } if sys == nil { return nil, fmt.Errorf("引用的系统模型已删除") } out := mergeReferenceConfig(m, sys) substituteAPIPlaceholder(out, out.ApiKey) return out, nil } out := *m substituteAPIPlaceholder(&out, out.ApiKey) return &out, nil }