refactor(core): 模型路由与画布 - 收敛渠道模型映射并优化交互

This commit is contained in:
ddCat committed 2026-08-19 20:27:26 +08:00
1 parent 52424543ea
commit d47975f9bc
34 files changed
+601 -901

No files matched your search

+56 -340
View File
@@ -37,17 +37,10 @@ type LogicalModelRequest struct {
}
type LogicalRouteRequest struct {
PhysicalVariantID string `json:"physicalVariantId"`
Enabled bool `json:"enabled"`
Priority int `json:"priority"`
Weight int `json:"weight"`
}
type PhysicalVariantRequest struct {
ChannelModelID string `json:"channelModelId"`
Name string `json:"name"`
Enabled bool `json:"enabled"`
CapabilitySpec CapabilitySpec `json:"capabilitySpec"`
ChannelModelID string `json:"channelModelId"`
Enabled bool `json:"enabled"`
Priority int `json:"priority"`
Weight int `json:"weight"`
}
type PublicLogicalModel struct {
@@ -73,12 +66,10 @@ type PublicLogicalModel struct {
type AdminLogicalRoute struct {
ID string `json:"id"`
PhysicalVariantID string `json:"physicalVariantId"`
PhysicalVariantName string `json:"physicalVariantName"`
ChannelModelID string `json:"channelModelId"`
ChannelID string `json:"channelId"`
PhysicalModelKey string `json:"physicalModelKey"`
PhysicalModelName string `json:"physicalModelName"`
ChannelModelKey string `json:"channelModelKey"`
ChannelModelName string `json:"channelModelName"`
Enabled bool `json:"enabled"`
Priority int `json:"priority"`
Weight int `json:"weight"`
@@ -97,32 +88,18 @@ type AdminLogicalModel struct {
Routes []AdminLogicalRoute `json:"routes"`
}
type AdminPhysicalVariant struct {
ID string `json:"id"`
ChannelModelID string `json:"channelModelId"`
ChannelID string `json:"channelId"`
ModelKey string `json:"modelKey"`
ModelName string `json:"modelName"`
Name string `json:"name"`
Capability string `json:"capability"`
Protocol string `json:"protocol"`
Enabled bool `json:"enabled"`
ModelEnabled bool `json:"modelEnabled"`
UsageCount int64 `json:"usageCount"`
CapabilitySpec CapabilitySpec `json:"capabilitySpec"`
}
type RouteSimulationCandidate struct {
RouteID string `json:"routeId"`
VariantID string `json:"variantId"`
ChannelModelID string `json:"channelModelId"`
Priority int `json:"priority"`
Weight int `json:"weight"`
Enabled bool `json:"enabled"`
Matched bool `json:"matched"`
Blocked bool `json:"blocked"`
InPool bool `json:"inPool"`
Reasons []string `json:"reasons,omitempty"`
RouteID string `json:"routeId"`
ChannelModelID string `json:"channelModelId"`
ChannelModelKey string `json:"channelModelKey"`
ChannelModelName string `json:"channelModelName"`
Priority int `json:"priority"`
Weight int `json:"weight"`
Enabled bool `json:"enabled"`
Matched bool `json:"matched"`
Blocked bool `json:"blocked"`
InPool bool `json:"inPool"`
Reasons []string `json:"reasons,omitempty"`
}
type RouteSimulationResult struct {
@@ -151,7 +128,7 @@ func (s *Service) PublicLogicalModels(intent *ModelRequestIntent) ([]PublicLogic
available = false
if coverageValid {
for _, route := range cached.Routes {
if route.Route.Enabled && route.Route.Weight > 0 && !s.logicalRouteBlocked(route) && MatchCapability(route.VariantSpec, resolvedIntent).Matched {
if route.Route.Enabled && route.Route.Weight > 0 && !s.logicalRouteBlocked(route) && MatchCapability(route.CapabilitySpec, resolvedIntent).Matched {
available = true
break
}
@@ -167,22 +144,22 @@ func publicLogicalModel(cached cachedLogicalModel, available bool) PublicLogical
item := cached.Model
routeSpecs := make([]CapabilitySpec, 0, len(cached.Routes))
for _, route := range cached.Routes {
routeSpecs = append(routeSpecs, route.VariantSpec)
routeSpecs = append(routeSpecs, route.CapabilitySpec)
}
productSpec := capabilitySpecWithRoutePresets(cached.ProductSpec, routeSpecs)
variants := make([]CapabilitySpec, 0, len(cached.Routes))
profiles := make([]CapabilitySpec, 0, len(cached.Routes))
seen := make(map[string]bool, len(cached.Routes))
for _, route := range cached.Routes {
if !route.Route.Enabled || route.Route.Weight <= 0 {
continue
}
key := capabilityFingerprint(route.VariantSpec)
key := capabilityFingerprint(route.CapabilitySpec)
if !seen[key] {
seen[key] = true
variants = append(variants, route.VariantSpec)
profiles = append(profiles, route.CapabilitySpec)
}
}
return PublicLogicalModel{ID: item.ID, Code: item.Code, Name: item.Name, Icon: item.Icon, Description: item.Description, Capability: item.Capability, SortOrder: item.SortOrder, PricePolicy: item.PricePolicy, BillingMode: item.BillingMode, UnitPriceMicrocredits: item.UnitPriceMicrocredits, InputPriceMicrocredits: item.InputPriceMicrocredits, OutputPriceMicrocredits: item.OutputPriceMicrocredits, CachedPriceMicrocredits: item.CachedPriceMicrocredits, CapabilitySpec: productSpec, CapabilityProfiles: variants, DefaultOptions: cached.Defaults, Available: available}
return PublicLogicalModel{ID: item.ID, Code: item.Code, Name: item.Name, Icon: item.Icon, Description: item.Description, Capability: item.Capability, SortOrder: item.SortOrder, PricePolicy: item.PricePolicy, BillingMode: item.BillingMode, UnitPriceMicrocredits: item.UnitPriceMicrocredits, InputPriceMicrocredits: item.InputPriceMicrocredits, OutputPriceMicrocredits: item.OutputPriceMicrocredits, CachedPriceMicrocredits: item.CachedPriceMicrocredits, CapabilitySpec: productSpec, CapabilityProfiles: profiles, DefaultOptions: cached.Defaults, Available: available}
}
// capabilitySpecWithRoutePresets repairs old front-model snapshots that stored
@@ -273,7 +250,7 @@ func (s *Service) AdminLogicalModels(actor *model.User) ([]AdminLogicalModel, er
}
admin, buildErr := s.buildAdminLogicalModel(item, graph, systemChannelByID)
if buildErr != nil {
continue
return nil, buildErr
}
result = append(result, *admin)
}
@@ -285,32 +262,24 @@ func (s *Service) buildAdminLogicalModel(item model.LogicalModel, graph *reposit
if err != nil {
return nil, err
}
variantByID := make(map[string]model.PhysicalCapabilityVariant, len(graph.Variants))
for _, variant := range graph.Variants {
variantByID[variant.ID] = variant
}
channelModelByID := make(map[string]model.ChannelModel, len(graph.ChannelModels))
for _, channelModel := range graph.ChannelModels {
channelModelByID[channelModel.ID] = channelModel
}
admin := AdminLogicalModel{PublicLogicalModel: publicLogicalModel(cachedLogicalModel{Model: item, ProductSpec: productSpec, Defaults: map[string]any{}}, false), Enabled: item.Enabled, ActiveRevisionID: graph.Revision.ID, RevisionVersion: graph.Revision.Version, Routes: []AdminLogicalRoute{}}
for _, route := range graph.Routes {
variant, variantOK := variantByID[route.PhysicalVariantID]
if !variantOK {
continue
}
channelModel, channelOK := channelModelByID[variant.ChannelModelID]
channelModel, channelOK := channelModelByID[route.ChannelModelID]
if !channelOK {
continue
return nil, errors.New("供应线路引用的渠道模型不存在")
}
variantSpec, specErr := effectivePhysicalVariantSpec(variant, channelModel)
capabilitySpec, specErr := channelModelCapabilitySpec(channelModel)
if specErr != nil {
continue
return nil, specErr
}
_, channelOK = systemChannelByID[channelModel.ChannelID]
structurallyAvailable := route.Enabled && route.Weight > 0 && variant.Enabled && channelModel.Enabled && channelOK
structurallyAvailable := route.Enabled && route.Weight > 0 && channelModel.Enabled && channelOK
available := structurallyAvailable && (item.PricePolicy != "channel" || channelModel.PriceConfigured)
admin.Routes = append(admin.Routes, AdminLogicalRoute{ID: route.ID, PhysicalVariantID: variant.ID, PhysicalVariantName: variant.Name, ChannelModelID: channelModel.ID, ChannelID: channelModel.ChannelID, PhysicalModelKey: channelModel.ModelKey, PhysicalModelName: channelModel.DisplayName, Enabled: route.Enabled, Priority: route.Priority, Weight: route.Weight, Available: available, structurallyAvailable: structurallyAvailable, CapabilitySpec: variantSpec})
admin.Routes = append(admin.Routes, AdminLogicalRoute{ID: route.ID, ChannelModelID: channelModel.ID, ChannelID: channelModel.ChannelID, ChannelModelKey: channelModel.ModelKey, ChannelModelName: channelModel.DisplayName, Enabled: route.Enabled, Priority: route.Priority, Weight: route.Weight, Available: available, structurallyAvailable: structurallyAvailable, CapabilitySpec: capabilitySpec})
}
routeSpecs := make([]CapabilitySpec, 0, len(admin.Routes))
for _, route := range admin.Routes {
@@ -368,7 +337,7 @@ func availableCachedRouteSpecs(routes []cachedLogicalRoute) []CapabilitySpec {
result := make([]CapabilitySpec, 0, len(routes))
for _, route := range routes {
if route.Route.Enabled && route.Route.Weight > 0 {
result = append(result, route.VariantSpec)
result = append(result, route.CapabilitySpec)
}
}
return result
@@ -508,25 +477,24 @@ func (s *Service) logicalModelBundle(actor *model.User, id string, req LogicalMo
defaultsJSON, _ := json.Marshal(defaultMap(req.DefaultOptions))
revision := &model.LogicalModelRevision{ID: revisionID, LogicalModelID: item.ID, CapabilitySpecJSON: string(specJSON), DefaultOptionsJSON: string(defaultsJSON), CreatedBy: actor.ID, CreatedAt: time.Now()}
routes := make([]model.LogicalModelRoute, 0, len(req.Routes))
seenVariants := make(map[string]bool, len(req.Routes))
seenChannelModels := make(map[string]bool, len(req.Routes))
structuralRouteSpecs := make([]CapabilitySpec, 0, len(req.Routes))
settlementRouteSpecs := make([]CapabilitySpec, 0, len(req.Routes))
for _, input := range req.Routes {
variantID := strings.TrimSpace(input.PhysicalVariantID)
if variantID == "" || seenVariants[variantID] {
return nil, nil, nil, false, BadAuthRequest("供应线路必须选择不重复的可用配置")
channelModelID := strings.TrimSpace(input.ChannelModelID)
if channelModelID == "" || seenChannelModels[channelModelID] {
return nil, nil, nil, false, BadAuthRequest("供应线路必须选择不重复的渠道模型")
}
seenVariants[variantID] = true
variant, variantErr := s.repo.PhysicalVariant(variantID)
if variantErr != nil {
return nil, nil, nil, false, BadAuthRequest("供应线路引用的可用配置不存在")
}
channelModel, modelErr := s.repo.ChannelModel(variant.ChannelModelID)
seenChannelModels[channelModelID] = true
channelModel, modelErr := s.repo.ChannelModel(channelModelID)
if modelErr != nil {
return nil, nil, nil, false, BadAuthRequest("可用配置引用的渠道模型不存在")
return nil, nil, nil, false, BadAuthRequest("供应线路引用的渠道模型不存在")
}
variantSpec, specErr := effectivePhysicalVariantSpec(*variant, *channelModel)
if specErr != nil || normalizeCapability(variantSpec.Capability) != capability {
capabilitySpec, specErr := channelModelCapabilitySpec(*channelModel)
if specErr != nil {
return nil, nil, nil, false, specErr
}
if normalizeCapability(capabilitySpec.Capability) != capability {
return nil, nil, nil, false, BadAuthRequest("供应线路能力类型与前台模型不一致")
}
if req.Enabled && input.Enabled && input.Weight <= 0 {
@@ -535,26 +503,22 @@ func (s *Service) logicalModelBundle(actor *model.User, id string, req LogicalMo
if input.Weight < 0 {
return nil, nil, nil, false, BadAuthRequest("供应线路的同级权重不能为负数")
}
if req.Enabled && input.Enabled && variant.Enabled {
if err := validateVariantWithinChannelModel(*channelModel, variantSpec); err != nil {
return nil, nil, nil, false, err
}
if channelModel.Enabled {
if _, channelErr := s.repo.SystemChannel(channelModel.ChannelID); channelErr == nil {
structuralRouteSpecs = append(structuralRouteSpecs, variantSpec)
if pricePolicy != "channel" || channelModel.PriceConfigured {
settlementRouteSpecs = append(settlementRouteSpecs, variantSpec)
}
}
if _, channelErr := s.repo.SystemChannel(channelModel.ChannelID); channelErr != nil {
return nil, nil, nil, false, BadAuthRequest("供应线路只能选择系统渠道模型")
}
if req.Enabled && input.Enabled && channelModel.Enabled {
structuralRouteSpecs = append(structuralRouteSpecs, capabilitySpec)
if pricePolicy != "channel" || channelModel.PriceConfigured {
settlementRouteSpecs = append(settlementRouteSpecs, capabilitySpec)
}
}
routeID, idErr := s.repo.NextPrefixedID("ROUTE")
if idErr != nil {
return nil, nil, nil, false, idErr
}
routes = append(routes, model.LogicalModelRoute{ID: routeID, PhysicalVariantID: variant.ID, Enabled: input.Enabled, Priority: input.Priority, Weight: input.Weight, CreatedAt: time.Now(), UpdatedAt: time.Now()})
routes = append(routes, model.LogicalModelRoute{ID: routeID, ChannelModelID: channelModel.ID, Enabled: input.Enabled, Priority: input.Priority, Weight: input.Weight, CreatedAt: time.Now(), UpdatedAt: time.Now()})
}
// 停用必须始终可执行,便于管理员立即阻止失效配置继续对外服务;重新启用时再强校验结构能力和计费可用性。
// 停用必须始终可执行,便于管理员立即阻止失效线路继续对外服务;重新启用时再强校验结构能力和计费可用性。
if req.Enabled {
if len(structuralRouteSpecs) == 0 {
return nil, nil, nil, false, BadAuthRequest("启用前台模型前至少需要一条已启用的供应线路")
@@ -601,149 +565,12 @@ func defaultMap(value map[string]any) map[string]any {
return value
}
func (s *Service) AdminPhysicalVariants(actor *model.User) ([]AdminPhysicalVariant, error) {
return s.adminPhysicalVariants(actor, "")
}
func (s *Service) AdminPhysicalVariantsForChannelModel(actor *model.User, channelModelID string) ([]AdminPhysicalVariant, error) {
return s.adminPhysicalVariants(actor, strings.TrimSpace(channelModelID))
}
func (s *Service) adminPhysicalVariants(actor *model.User, channelModelID string) ([]AdminPhysicalVariant, error) {
if err := s.RequireAdmin(actor); err != nil {
return nil, err
}
var variants []model.PhysicalCapabilityVariant
var err error
if channelModelID != "" {
if _, err := s.repo.ChannelModel(channelModelID); err != nil {
return nil, BadAuthRequest("渠道模型不存在")
}
variants, err = s.repo.PhysicalVariants([]string{channelModelID}, true)
} else {
variants, err = s.repo.AllPhysicalVariants(true)
}
func channelModelCapabilitySpec(channelModel model.ChannelModel) (CapabilitySpec, error) {
config, err := DecodeModelCapabilityConfig(channelModel.CapabilityConfigJSON)
if err != nil {
return nil, err
return CapabilitySpec{}, BadAuthRequest("渠道模型能力配置无效,请先修复渠道模型")
}
variantIDs := make([]string, 0, len(variants))
for _, variant := range variants {
variantIDs = append(variantIDs, variant.ID)
}
usageCounts, err := s.repo.PhysicalVariantRouteCounts(variantIDs)
if err != nil {
return nil, err
}
result := make([]AdminPhysicalVariant, 0, len(variants))
for _, variant := range variants {
channelModel, modelErr := s.repo.ChannelModel(variant.ChannelModelID)
if modelErr != nil {
continue
}
spec, specErr := effectivePhysicalVariantSpec(variant, *channelModel)
if specErr != nil {
continue
}
result = append(result, AdminPhysicalVariant{ID: variant.ID, ChannelModelID: channelModel.ID, ChannelID: channelModel.ChannelID, ModelKey: channelModel.ModelKey, ModelName: channelModel.DisplayName, Name: variant.Name, Capability: channelModel.Capability, Protocol: string(channelModel.Protocol), Enabled: variant.Enabled, ModelEnabled: channelModel.Enabled, UsageCount: usageCounts[variant.ID], CapabilitySpec: spec})
}
return result, nil
}
func effectivePhysicalVariantSpec(variant model.PhysicalCapabilityVariant, channelModel model.ChannelModel) (CapabilitySpec, error) {
config, configErr := DecodeModelCapabilityConfig(channelModel.CapabilityConfigJSON)
if configErr == nil && config != nil {
if spec, err := CapabilitySpecFromModelCapabilityConfig(config, normalizeCapability(channelModel.Capability)); err == nil {
return spec, nil
}
}
// 音频模型以及历史数据可能没有新的能力配置,保留旧快照作为只读降级,避免读路径直接丢失线路。
return DecodeCapabilitySpec(variant.CapabilitySpecJSON)
}
func (s *Service) SaveAdminPhysicalVariant(actor *model.User, id string, req PhysicalVariantRequest) (*AdminPhysicalVariant, error) {
if err := s.RequireAdmin(actor); err != nil {
return nil, err
}
channelModel, err := s.repo.ChannelModel(strings.TrimSpace(req.ChannelModelID))
if err != nil {
return nil, BadAuthRequest("请选择有效的渠道模型")
}
name := strings.TrimSpace(req.Name)
if name == "" || len([]rune(name)) > 120 {
return nil, BadAuthRequest("请填写 1-120 个字符的可用配置名称")
}
creating := strings.TrimSpace(id) == ""
var item *model.PhysicalCapabilityVariant
if creating {
existing, existingErr := s.repo.PhysicalVariants([]string{channelModel.ID}, true)
if existingErr != nil {
return nil, existingErr
}
if len(existing) > 0 {
return nil, BadAuthRequest("一个渠道模型只能配置一个可用配置,请编辑现有配置")
}
id, err = s.repo.NextPrefixedID("VARIANT")
if err != nil {
return nil, err
}
item = &model.PhysicalCapabilityVariant{ID: id, CreatedAt: time.Now()}
} else {
item, err = s.repo.PhysicalVariant(id)
if err != nil {
return nil, err
}
if item.ChannelModelID != channelModel.ID {
return nil, BadAuthRequest("已创建的可用配置不能换绑渠道模型,请新建可用配置")
}
}
// variant 不再承载第二份可编辑能力配置,能力快照始终从渠道模型生成。
// 读取旧请求中的 capabilitySpec 仅为兼容旧客户端,不再作为事实来源。
// 停用是强制止损操作:即使渠道模型能力 JSON 已损坏,也必须允许管理员先停用;
// 只有启用路径才要求重新生成并校验能力快照。
capabilityConfig, configErr := DecodeModelCapabilityConfig(channelModel.CapabilityConfigJSON)
derivedSpec, deriveErr := CapabilitySpecFromModelCapabilityConfig(capabilityConfig, normalizeCapability(channelModel.Capability))
if req.Enabled {
if configErr != nil {
return nil, BadAuthRequest("渠道模型能力配置无效,请先修复渠道模型")
}
if deriveErr != nil {
return nil, deriveErr
}
if err := validateVariantWithinChannelModel(*channelModel, derivedSpec); err != nil {
return nil, err
}
}
spec := derivedSpec
if deriveErr != nil {
// 禁用时保留已有快照,避免坏配置导致管理列表丢失;新建记录使用最小可读快照。
if existingSpec, existingErr := DecodeCapabilitySpec(item.CapabilitySpecJSON); existingErr == nil {
spec = existingSpec
} else {
spec = CapabilitySpec{Version: 1, Capability: normalizeCapability(channelModel.Capability)}
}
}
specJSON, _ := json.Marshal(spec)
item.ChannelModelID, item.Name, item.Enabled, item.CapabilitySpecJSON, item.UpdatedAt = channelModel.ID, name, req.Enabled, string(specJSON), time.Now()
if creating {
err = s.repo.CreatePhysicalVariant(item)
} else {
err = s.repo.SavePhysicalVariant(item)
}
if err != nil {
return nil, err
}
s.invalidateRouteCatalog()
_ = s.appendAdminAudit(actor, map[bool]string{true: "physical_variant.create", false: "physical_variant.update"}[creating], "physical_variant", item.ID, "保存可用配置", map[string]any{"channelModelId": channelModel.ID})
variants, err := s.AdminPhysicalVariants(actor)
if err != nil {
return nil, err
}
for index := range variants {
if variants[index].ID == item.ID {
return &variants[index], nil
}
}
return nil, gorm.ErrRecordNotFound
return CapabilitySpecFromModelCapabilityConfig(config, normalizeCapability(channelModel.Capability))
}
func (s *Service) SimulateLogicalModelRoute(actor *model.User, id string, intent ModelRequestIntent) (*RouteSimulationResult, error) {
@@ -764,100 +591,6 @@ func (s *Service) SimulateLogicalModelRoute(actor *model.User, id string, intent
func logicalModelNotFound(err error) bool { return errors.Is(err, gorm.ErrRecordNotFound) }
// 可用配置只能收窄渠道模型能力,不能声明协议适配器实际无法发送的范围。
func validateVariantWithinChannelModel(channelModel model.ChannelModel, spec CapabilitySpec) error {
profile, err := DecodeModelCapabilityConfig(channelModel.CapabilityConfigJSON)
if err != nil {
return BadAuthRequest("渠道模型能力配置无效,请先修复渠道模型")
}
switch normalizeCapability(spec.Capability) {
case "text":
if profile == nil || profile.Text == nil {
return BadAuthRequest("渠道文本模型尚未配置能力参数")
}
limits := map[string]int{"image": profile.Text.References.MaxImages, "video": profile.Text.References.MaxVideos}
for kind, constraint := range spec.Inputs {
limit, known := limits[kind]
if !known && constraint.Max > 0 || known && constraint.Max > limit {
return BadAuthRequest("可用配置的 " + kind + " 输入上限超过渠道模型配置")
}
}
return validateKnownVariantOptions(spec.Options, map[string]OptionConstraint{})
case "image":
if profile == nil || profile.Image == nil {
return BadAuthRequest("渠道图片模型尚未配置能力参数")
}
image := profile.Image
for kind, constraint := range spec.Inputs {
switch kind {
case "image":
if constraint.Max > image.References.MaxImages {
return BadAuthRequest("可用配置的图片输入上限超过渠道模型配置")
}
case "mask":
if constraint.Max > 1 || constraint.Max > 0 && !image.References.MaskSupported {
return BadAuthRequest("可用配置声明了渠道模型不支持的蒙版输入")
}
default:
if constraint.Max > 0 {
return BadAuthRequest("图片协议尚未接入 " + kind + " 输入")
}
}
}
physical := map[string]OptionConstraint{
"size": imageSizeOptionConstraint(image.Size),
"quality": anyValues(image.Quality.Values),
"transparentBackground": boolValues(image.TransparentBackground.Supported),
"count": numericRange(1, float64(image.MaxOutputs), 1),
}
return validateKnownVariantOptions(spec.Options, physical)
case "video":
if profile == nil || profile.Video == nil {
return BadAuthRequest("渠道视频模型尚未配置能力参数")
}
video := profile.Video
limits := map[string]int{"image": video.References.MaxImages, "video": video.References.MaxVideos, "audio": video.References.MaxAudios}
for kind, constraint := range spec.Inputs {
limit, known := limits[kind]
if !known && constraint.Max > 0 || known && constraint.Max > limit {
return BadAuthRequest("可用配置的 " + kind + " 输入上限超过渠道模型配置")
}
}
for _, operation := range spec.Operations {
if !containsCapabilityString(video.Operations, operation) {
return BadAuthRequest("可用配置包含渠道模型不支持的操作:" + operation)
}
}
duration := OptionConstraint{}
if video.Duration.Selection == "enum" {
values := make([]any, 0, len(video.Duration.Values))
for _, value := range video.Duration.Values {
values = append(values, value)
}
duration.Values = values
} else {
duration = numericRange(float64(video.Duration.Min), float64(video.Duration.Max), float64(video.Duration.Step))
}
physical := map[string]OptionConstraint{
"videoSeconds": duration,
"size": anyValues(video.Ratios),
"vquality": anyValues(video.Resolutions),
"videoGenerateAudio": boolValues(video.GenerateAudio.Supported),
"videoWatermark": boolValues(video.Watermark.Supported),
}
return validateKnownVariantOptions(spec.Options, physical)
case "audio":
for kind, constraint := range spec.Inputs {
if constraint.Max > 0 {
return BadAuthRequest("音频协议尚未接入 " + kind + " 输入")
}
}
wildcard := OptionConstraint{Values: []any{"*"}}
return validateKnownVariantOptions(spec.Options, map[string]OptionConstraint{"audioVoice": wildcard, "audioFormat": wildcard, "audioSpeed": wildcard, "audioInstructions": wildcard})
}
return BadAuthRequest("可用配置类型暂不支持")
}
// 前台模型只声明供应线路真实提供的总目录;组合是否可承接仍由匿名 capabilityProfiles 按 OR 语义判断。
func validateProductSpecWithinRoutes(product CapabilitySpec, routeSpecs []CapabilitySpec) error {
for _, routeSpec := range routeSpecs {
@@ -1030,23 +763,6 @@ func continuousOptionRangeCovered(minimum float64, maximum float64, constraints
return true
}
func validateKnownVariantOptions(options map[string]OptionConstraint, physical map[string]OptionConstraint) error {
for name, constraint := range options {
limit, known := physical[name]
if !known {
return BadAuthRequest("参数 " + name + " 尚未接入该类型的供应商协议映射")
}
if !optionConstraintWithin(constraint, limit) {
return BadAuthRequest("参数 " + name + " 超过渠道模型能力范围")
}
}
return nil
}
func optionConstraintWithin(candidate OptionConstraint, limit OptionConstraint) bool {
return optionConstraintCovered(candidate, "value", []CapabilitySpec{{Options: map[string]OptionConstraint{"value": limit}}})
}
func anyValues(values []string) OptionConstraint {
result := make([]any, 0, len(values))
for _, value := range values {