- price.go -> service/utils/media_type.go(媒体类型检测) - model_resolve.go -> service/utils/model_resolve.go(模型解析) - 删除 service/parse_error_test.go(逻辑迁往 utils 后无对应测试) - session_sync/session_stream/model_*_service/pricing_client 等调用方适配 - schema_mapping/model_call_dto 补字段路径
151 lines
5.6 KiB
Go
151 lines
5.6 KiB
Go
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
|
|
}
|