diff --git a/backend/ent/group.go b/backend/ent/group.go index 747a09d1aa4f..21a254e0f795 100644 --- a/backend/ent/group.go +++ b/backend/ent/group.go @@ -124,10 +124,12 @@ type Group struct { AllowMessagesDispatch bool `json:"allow_messages_dispatch,omitempty"` // 是否允许此 OpenAI 分组访问 Live 接口 AllowLive bool `json:"allow_live,omitempty"` - // 是否强制此 OpenAI/Composite 分组请求使用 service_tier=priority - ForceOpenaiFast bool `json:"force_openai_fast,omitempty"` + // 强制此 OpenAI/Composite 分组请求使用的 service_tier;空字符串表示跟随请求 + ForcedServiceTier string `json:"forced_service_tier,omitempty"` // 是否让此 OpenAI/Composite 分组的 Fast 请求按 Standard 价格计费 FreeOpenaiFast bool `json:"free_openai_fast,omitempty"` + // 是否让此 OpenAI/Composite 分组的 UltraFast 请求按 Standard 价格计费 + FreeOpenaiUltrafast bool `json:"free_openai_ultrafast,omitempty"` // 仅允许非 apikey 类型账号关联到此分组 RequireOauthOnly bool `json:"require_oauth_only,omitempty"` // 调度时仅允许 privacy 已成功设置的账号 @@ -262,13 +264,13 @@ func (*Group) scanValues(columns []string) ([]any, error) { switch columns[i] { case group.FieldI18n, group.FieldVideoModelPrices, group.FieldModelPricing, group.FieldModelRouting, group.FieldSupportedModelScopes, group.FieldMessagesDispatchModelConfig, group.FieldModelAllowlist, group.FieldCodexModelsManifestConfig, group.FieldReasoningEffortMappings: values[i] = new([]byte) - case group.FieldPeakRateEnabled, group.FieldIsExclusive, group.FieldAllowImageGeneration, group.FieldAllowBatchImageGeneration, group.FieldImageRateIndependent, group.FieldVideoRateIndependent, group.FieldLongContextPricingEnabled, group.FieldClaudeCodeOnly, group.FieldModelRoutingEnabled, group.FieldMcpXMLInject, group.FieldAllowMessagesDispatch, group.FieldAllowLive, group.FieldForceOpenaiFast, group.FieldFreeOpenaiFast, group.FieldRequireOauthOnly, group.FieldRequirePrivacySet, group.FieldProfitControlEnabled: + case group.FieldPeakRateEnabled, group.FieldIsExclusive, group.FieldAllowImageGeneration, group.FieldAllowBatchImageGeneration, group.FieldImageRateIndependent, group.FieldVideoRateIndependent, group.FieldLongContextPricingEnabled, group.FieldClaudeCodeOnly, group.FieldModelRoutingEnabled, group.FieldMcpXMLInject, group.FieldAllowMessagesDispatch, group.FieldAllowLive, group.FieldFreeOpenaiFast, group.FieldFreeOpenaiUltrafast, group.FieldRequireOauthOnly, group.FieldRequirePrivacySet, group.FieldProfitControlEnabled: values[i] = new(sql.NullBool) case group.FieldRateMultiplier, group.FieldPeakRateMultiplier, group.FieldDailyLimitUsd, group.FieldWeeklyLimitUsd, group.FieldMonthlyLimitUsd, group.FieldImageRateMultiplier, group.FieldImagePrice1k, group.FieldImagePrice2k, group.FieldImagePrice4k, group.FieldBatchImageDiscountMultiplier, group.FieldBatchImageHoldMultiplier, group.FieldVideoRateMultiplier, group.FieldVideoPrice480p, group.FieldVideoPrice720p, group.FieldVideoPrice1080p, group.FieldWebSearchPricePerCall, group.FieldSearchPricePer1k, group.FieldAudioRealtimePricePerMin, group.FieldAudioTtsPricePerMillionChars, group.FieldAudioSttPricePerHour, group.FieldProfitMinMargin, group.FieldProfitSafetyBuffer: values[i] = new(sql.NullFloat64) case group.FieldID, group.FieldDefaultValidityDays, group.FieldFallbackGroupID, group.FieldFallbackGroupIDOnInvalidRequest, group.FieldSortOrder, group.FieldRpmLimit: values[i] = new(sql.NullInt64) - case group.FieldName, group.FieldDescription, group.FieldPeakStart, group.FieldPeakEnd, group.FieldStatus, group.FieldDuplicateOperationID, group.FieldPlatform, group.FieldSubscriptionType, group.FieldDefaultMappedModel, group.FieldMaxReasoningEffort, group.FieldMaxReasoningEffortOverLimit: + case group.FieldName, group.FieldDescription, group.FieldPeakStart, group.FieldPeakEnd, group.FieldStatus, group.FieldDuplicateOperationID, group.FieldPlatform, group.FieldSubscriptionType, group.FieldForcedServiceTier, group.FieldDefaultMappedModel, group.FieldMaxReasoningEffort, group.FieldMaxReasoningEffortOverLimit: values[i] = new(sql.NullString) case group.FieldCreatedAt, group.FieldUpdatedAt, group.FieldDeletedAt: values[i] = new(sql.NullTime) @@ -634,11 +636,11 @@ func (_m *Group) assignValues(columns []string, values []any) error { } else if value.Valid { _m.AllowLive = value.Bool } - case group.FieldForceOpenaiFast: - if value, ok := values[i].(*sql.NullBool); !ok { - return fmt.Errorf("unexpected type %T for field force_openai_fast", values[i]) + case group.FieldForcedServiceTier: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field forced_service_tier", values[i]) } else if value.Valid { - _m.ForceOpenaiFast = value.Bool + _m.ForcedServiceTier = value.String } case group.FieldFreeOpenaiFast: if value, ok := values[i].(*sql.NullBool); !ok { @@ -646,6 +648,12 @@ func (_m *Group) assignValues(columns []string, values []any) error { } else if value.Valid { _m.FreeOpenaiFast = value.Bool } + case group.FieldFreeOpenaiUltrafast: + if value, ok := values[i].(*sql.NullBool); !ok { + return fmt.Errorf("unexpected type %T for field free_openai_ultrafast", values[i]) + } else if value.Valid { + _m.FreeOpenaiUltrafast = value.Bool + } case group.FieldRequireOauthOnly: if value, ok := values[i].(*sql.NullBool); !ok { return fmt.Errorf("unexpected type %T for field require_oauth_only", values[i]) @@ -1002,12 +1010,15 @@ func (_m *Group) String() string { builder.WriteString("allow_live=") builder.WriteString(fmt.Sprintf("%v", _m.AllowLive)) builder.WriteString(", ") - builder.WriteString("force_openai_fast=") - builder.WriteString(fmt.Sprintf("%v", _m.ForceOpenaiFast)) + builder.WriteString("forced_service_tier=") + builder.WriteString(_m.ForcedServiceTier) builder.WriteString(", ") builder.WriteString("free_openai_fast=") builder.WriteString(fmt.Sprintf("%v", _m.FreeOpenaiFast)) builder.WriteString(", ") + builder.WriteString("free_openai_ultrafast=") + builder.WriteString(fmt.Sprintf("%v", _m.FreeOpenaiUltrafast)) + builder.WriteString(", ") builder.WriteString("require_oauth_only=") builder.WriteString(fmt.Sprintf("%v", _m.RequireOauthOnly)) builder.WriteString(", ") diff --git a/backend/ent/group/group.go b/backend/ent/group/group.go index cf3feeb6c9e6..df7f47ff539d 100644 --- a/backend/ent/group/group.go +++ b/backend/ent/group/group.go @@ -120,10 +120,12 @@ const ( FieldAllowMessagesDispatch = "allow_messages_dispatch" // FieldAllowLive holds the string denoting the allow_live field in the database. FieldAllowLive = "allow_live" - // FieldForceOpenaiFast holds the string denoting the force_openai_fast field in the database. - FieldForceOpenaiFast = "force_openai_fast" + // FieldForcedServiceTier holds the string denoting the forced_service_tier field in the database. + FieldForcedServiceTier = "forced_service_tier" // FieldFreeOpenaiFast holds the string denoting the free_openai_fast field in the database. FieldFreeOpenaiFast = "free_openai_fast" + // FieldFreeOpenaiUltrafast holds the string denoting the free_openai_ultrafast field in the database. + FieldFreeOpenaiUltrafast = "free_openai_ultrafast" // FieldRequireOauthOnly holds the string denoting the require_oauth_only field in the database. FieldRequireOauthOnly = "require_oauth_only" // FieldRequirePrivacySet holds the string denoting the require_privacy_set field in the database. @@ -277,8 +279,9 @@ var Columns = []string{ FieldSortOrder, FieldAllowMessagesDispatch, FieldAllowLive, - FieldForceOpenaiFast, + FieldForcedServiceTier, FieldFreeOpenaiFast, + FieldFreeOpenaiUltrafast, FieldRequireOauthOnly, FieldRequirePrivacySet, FieldDefaultMappedModel, @@ -403,10 +406,12 @@ var ( DefaultAllowMessagesDispatch bool // DefaultAllowLive holds the default value on creation for the "allow_live" field. DefaultAllowLive bool - // DefaultForceOpenaiFast holds the default value on creation for the "force_openai_fast" field. - DefaultForceOpenaiFast bool + // DefaultForcedServiceTier holds the default value on creation for the "forced_service_tier" field. + DefaultForcedServiceTier string // DefaultFreeOpenaiFast holds the default value on creation for the "free_openai_fast" field. DefaultFreeOpenaiFast bool + // DefaultFreeOpenaiUltrafast holds the default value on creation for the "free_openai_ultrafast" field. + DefaultFreeOpenaiUltrafast bool // DefaultRequireOauthOnly holds the default value on creation for the "require_oauth_only" field. DefaultRequireOauthOnly bool // DefaultRequirePrivacySet holds the default value on creation for the "require_privacy_set" field. @@ -684,9 +689,9 @@ func ByAllowLive(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldAllowLive, opts...).ToFunc() } -// ByForceOpenaiFast orders the results by the force_openai_fast field. -func ByForceOpenaiFast(opts ...sql.OrderTermOption) OrderOption { - return sql.OrderByField(FieldForceOpenaiFast, opts...).ToFunc() +// ByForcedServiceTier orders the results by the forced_service_tier field. +func ByForcedServiceTier(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldForcedServiceTier, opts...).ToFunc() } // ByFreeOpenaiFast orders the results by the free_openai_fast field. @@ -694,6 +699,11 @@ func ByFreeOpenaiFast(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldFreeOpenaiFast, opts...).ToFunc() } +// ByFreeOpenaiUltrafast orders the results by the free_openai_ultrafast field. +func ByFreeOpenaiUltrafast(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldFreeOpenaiUltrafast, opts...).ToFunc() +} + // ByRequireOauthOnly orders the results by the require_oauth_only field. func ByRequireOauthOnly(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldRequireOauthOnly, opts...).ToFunc() diff --git a/backend/ent/group/where.go b/backend/ent/group/where.go index 149f2da8a083..2bad97c5d14a 100644 --- a/backend/ent/group/where.go +++ b/backend/ent/group/where.go @@ -290,9 +290,9 @@ func AllowLive(v bool) predicate.Group { return predicate.Group(sql.FieldEQ(FieldAllowLive, v)) } -// ForceOpenaiFast applies equality check predicate on the "force_openai_fast" field. It's identical to ForceOpenaiFastEQ. -func ForceOpenaiFast(v bool) predicate.Group { - return predicate.Group(sql.FieldEQ(FieldForceOpenaiFast, v)) +// ForcedServiceTier applies equality check predicate on the "forced_service_tier" field. It's identical to ForcedServiceTierEQ. +func ForcedServiceTier(v string) predicate.Group { + return predicate.Group(sql.FieldEQ(FieldForcedServiceTier, v)) } // FreeOpenaiFast applies equality check predicate on the "free_openai_fast" field. It's identical to FreeOpenaiFastEQ. @@ -300,6 +300,11 @@ func FreeOpenaiFast(v bool) predicate.Group { return predicate.Group(sql.FieldEQ(FieldFreeOpenaiFast, v)) } +// FreeOpenaiUltrafast applies equality check predicate on the "free_openai_ultrafast" field. It's identical to FreeOpenaiUltrafastEQ. +func FreeOpenaiUltrafast(v bool) predicate.Group { + return predicate.Group(sql.FieldEQ(FieldFreeOpenaiUltrafast, v)) +} + // RequireOauthOnly applies equality check predicate on the "require_oauth_only" field. It's identical to RequireOauthOnlyEQ. func RequireOauthOnly(v bool) predicate.Group { return predicate.Group(sql.FieldEQ(FieldRequireOauthOnly, v)) @@ -2285,14 +2290,69 @@ func AllowLiveNEQ(v bool) predicate.Group { return predicate.Group(sql.FieldNEQ(FieldAllowLive, v)) } -// ForceOpenaiFastEQ applies the EQ predicate on the "force_openai_fast" field. -func ForceOpenaiFastEQ(v bool) predicate.Group { - return predicate.Group(sql.FieldEQ(FieldForceOpenaiFast, v)) +// ForcedServiceTierEQ applies the EQ predicate on the "forced_service_tier" field. +func ForcedServiceTierEQ(v string) predicate.Group { + return predicate.Group(sql.FieldEQ(FieldForcedServiceTier, v)) +} + +// ForcedServiceTierNEQ applies the NEQ predicate on the "forced_service_tier" field. +func ForcedServiceTierNEQ(v string) predicate.Group { + return predicate.Group(sql.FieldNEQ(FieldForcedServiceTier, v)) +} + +// ForcedServiceTierIn applies the In predicate on the "forced_service_tier" field. +func ForcedServiceTierIn(vs ...string) predicate.Group { + return predicate.Group(sql.FieldIn(FieldForcedServiceTier, vs...)) +} + +// ForcedServiceTierNotIn applies the NotIn predicate on the "forced_service_tier" field. +func ForcedServiceTierNotIn(vs ...string) predicate.Group { + return predicate.Group(sql.FieldNotIn(FieldForcedServiceTier, vs...)) +} + +// ForcedServiceTierGT applies the GT predicate on the "forced_service_tier" field. +func ForcedServiceTierGT(v string) predicate.Group { + return predicate.Group(sql.FieldGT(FieldForcedServiceTier, v)) +} + +// ForcedServiceTierGTE applies the GTE predicate on the "forced_service_tier" field. +func ForcedServiceTierGTE(v string) predicate.Group { + return predicate.Group(sql.FieldGTE(FieldForcedServiceTier, v)) +} + +// ForcedServiceTierLT applies the LT predicate on the "forced_service_tier" field. +func ForcedServiceTierLT(v string) predicate.Group { + return predicate.Group(sql.FieldLT(FieldForcedServiceTier, v)) } -// ForceOpenaiFastNEQ applies the NEQ predicate on the "force_openai_fast" field. -func ForceOpenaiFastNEQ(v bool) predicate.Group { - return predicate.Group(sql.FieldNEQ(FieldForceOpenaiFast, v)) +// ForcedServiceTierLTE applies the LTE predicate on the "forced_service_tier" field. +func ForcedServiceTierLTE(v string) predicate.Group { + return predicate.Group(sql.FieldLTE(FieldForcedServiceTier, v)) +} + +// ForcedServiceTierContains applies the Contains predicate on the "forced_service_tier" field. +func ForcedServiceTierContains(v string) predicate.Group { + return predicate.Group(sql.FieldContains(FieldForcedServiceTier, v)) +} + +// ForcedServiceTierHasPrefix applies the HasPrefix predicate on the "forced_service_tier" field. +func ForcedServiceTierHasPrefix(v string) predicate.Group { + return predicate.Group(sql.FieldHasPrefix(FieldForcedServiceTier, v)) +} + +// ForcedServiceTierHasSuffix applies the HasSuffix predicate on the "forced_service_tier" field. +func ForcedServiceTierHasSuffix(v string) predicate.Group { + return predicate.Group(sql.FieldHasSuffix(FieldForcedServiceTier, v)) +} + +// ForcedServiceTierEqualFold applies the EqualFold predicate on the "forced_service_tier" field. +func ForcedServiceTierEqualFold(v string) predicate.Group { + return predicate.Group(sql.FieldEqualFold(FieldForcedServiceTier, v)) +} + +// ForcedServiceTierContainsFold applies the ContainsFold predicate on the "forced_service_tier" field. +func ForcedServiceTierContainsFold(v string) predicate.Group { + return predicate.Group(sql.FieldContainsFold(FieldForcedServiceTier, v)) } // FreeOpenaiFastEQ applies the EQ predicate on the "free_openai_fast" field. @@ -2305,6 +2365,16 @@ func FreeOpenaiFastNEQ(v bool) predicate.Group { return predicate.Group(sql.FieldNEQ(FieldFreeOpenaiFast, v)) } +// FreeOpenaiUltrafastEQ applies the EQ predicate on the "free_openai_ultrafast" field. +func FreeOpenaiUltrafastEQ(v bool) predicate.Group { + return predicate.Group(sql.FieldEQ(FieldFreeOpenaiUltrafast, v)) +} + +// FreeOpenaiUltrafastNEQ applies the NEQ predicate on the "free_openai_ultrafast" field. +func FreeOpenaiUltrafastNEQ(v bool) predicate.Group { + return predicate.Group(sql.FieldNEQ(FieldFreeOpenaiUltrafast, v)) +} + // RequireOauthOnlyEQ applies the EQ predicate on the "require_oauth_only" field. func RequireOauthOnlyEQ(v bool) predicate.Group { return predicate.Group(sql.FieldEQ(FieldRequireOauthOnly, v)) diff --git a/backend/ent/group_create.go b/backend/ent/group_create.go index d2a3a0d0e310..72d49e0c260a 100644 --- a/backend/ent/group_create.go +++ b/backend/ent/group_create.go @@ -710,16 +710,16 @@ func (_c *GroupCreate) SetNillableAllowLive(v *bool) *GroupCreate { return _c } -// SetForceOpenaiFast sets the "force_openai_fast" field. -func (_c *GroupCreate) SetForceOpenaiFast(v bool) *GroupCreate { - _c.mutation.SetForceOpenaiFast(v) +// SetForcedServiceTier sets the "forced_service_tier" field. +func (_c *GroupCreate) SetForcedServiceTier(v string) *GroupCreate { + _c.mutation.SetForcedServiceTier(v) return _c } -// SetNillableForceOpenaiFast sets the "force_openai_fast" field if the given value is not nil. -func (_c *GroupCreate) SetNillableForceOpenaiFast(v *bool) *GroupCreate { +// SetNillableForcedServiceTier sets the "forced_service_tier" field if the given value is not nil. +func (_c *GroupCreate) SetNillableForcedServiceTier(v *string) *GroupCreate { if v != nil { - _c.SetForceOpenaiFast(*v) + _c.SetForcedServiceTier(*v) } return _c } @@ -738,6 +738,20 @@ func (_c *GroupCreate) SetNillableFreeOpenaiFast(v *bool) *GroupCreate { return _c } +// SetFreeOpenaiUltrafast sets the "free_openai_ultrafast" field. +func (_c *GroupCreate) SetFreeOpenaiUltrafast(v bool) *GroupCreate { + _c.mutation.SetFreeOpenaiUltrafast(v) + return _c +} + +// SetNillableFreeOpenaiUltrafast sets the "free_openai_ultrafast" field if the given value is not nil. +func (_c *GroupCreate) SetNillableFreeOpenaiUltrafast(v *bool) *GroupCreate { + if v != nil { + _c.SetFreeOpenaiUltrafast(*v) + } + return _c +} + // SetRequireOauthOnly sets the "require_oauth_only" field. func (_c *GroupCreate) SetRequireOauthOnly(v bool) *GroupCreate { _c.mutation.SetRequireOauthOnly(v) @@ -1161,14 +1175,18 @@ func (_c *GroupCreate) defaults() error { v := group.DefaultAllowLive _c.mutation.SetAllowLive(v) } - if _, ok := _c.mutation.ForceOpenaiFast(); !ok { - v := group.DefaultForceOpenaiFast - _c.mutation.SetForceOpenaiFast(v) + if _, ok := _c.mutation.ForcedServiceTier(); !ok { + v := group.DefaultForcedServiceTier + _c.mutation.SetForcedServiceTier(v) } if _, ok := _c.mutation.FreeOpenaiFast(); !ok { v := group.DefaultFreeOpenaiFast _c.mutation.SetFreeOpenaiFast(v) } + if _, ok := _c.mutation.FreeOpenaiUltrafast(); !ok { + v := group.DefaultFreeOpenaiUltrafast + _c.mutation.SetFreeOpenaiUltrafast(v) + } if _, ok := _c.mutation.RequireOauthOnly(); !ok { v := group.DefaultRequireOauthOnly _c.mutation.SetRequireOauthOnly(v) @@ -1371,12 +1389,15 @@ func (_c *GroupCreate) check() error { if _, ok := _c.mutation.AllowLive(); !ok { return &ValidationError{Name: "allow_live", err: errors.New(`ent: missing required field "Group.allow_live"`)} } - if _, ok := _c.mutation.ForceOpenaiFast(); !ok { - return &ValidationError{Name: "force_openai_fast", err: errors.New(`ent: missing required field "Group.force_openai_fast"`)} + if _, ok := _c.mutation.ForcedServiceTier(); !ok { + return &ValidationError{Name: "forced_service_tier", err: errors.New(`ent: missing required field "Group.forced_service_tier"`)} } if _, ok := _c.mutation.FreeOpenaiFast(); !ok { return &ValidationError{Name: "free_openai_fast", err: errors.New(`ent: missing required field "Group.free_openai_fast"`)} } + if _, ok := _c.mutation.FreeOpenaiUltrafast(); !ok { + return &ValidationError{Name: "free_openai_ultrafast", err: errors.New(`ent: missing required field "Group.free_openai_ultrafast"`)} + } if _, ok := _c.mutation.RequireOauthOnly(); !ok { return &ValidationError{Name: "require_oauth_only", err: errors.New(`ent: missing required field "Group.require_oauth_only"`)} } @@ -1666,14 +1687,18 @@ func (_c *GroupCreate) createSpec() (*Group, *sqlgraph.CreateSpec) { _spec.SetField(group.FieldAllowLive, field.TypeBool, value) _node.AllowLive = value } - if value, ok := _c.mutation.ForceOpenaiFast(); ok { - _spec.SetField(group.FieldForceOpenaiFast, field.TypeBool, value) - _node.ForceOpenaiFast = value + if value, ok := _c.mutation.ForcedServiceTier(); ok { + _spec.SetField(group.FieldForcedServiceTier, field.TypeString, value) + _node.ForcedServiceTier = value } if value, ok := _c.mutation.FreeOpenaiFast(); ok { _spec.SetField(group.FieldFreeOpenaiFast, field.TypeBool, value) _node.FreeOpenaiFast = value } + if value, ok := _c.mutation.FreeOpenaiUltrafast(); ok { + _spec.SetField(group.FieldFreeOpenaiUltrafast, field.TypeBool, value) + _node.FreeOpenaiUltrafast = value + } if value, ok := _c.mutation.RequireOauthOnly(); ok { _spec.SetField(group.FieldRequireOauthOnly, field.TypeBool, value) _node.RequireOauthOnly = value @@ -2752,15 +2777,15 @@ func (u *GroupUpsert) UpdateAllowLive() *GroupUpsert { return u } -// SetForceOpenaiFast sets the "force_openai_fast" field. -func (u *GroupUpsert) SetForceOpenaiFast(v bool) *GroupUpsert { - u.Set(group.FieldForceOpenaiFast, v) +// SetForcedServiceTier sets the "forced_service_tier" field. +func (u *GroupUpsert) SetForcedServiceTier(v string) *GroupUpsert { + u.Set(group.FieldForcedServiceTier, v) return u } -// UpdateForceOpenaiFast sets the "force_openai_fast" field to the value that was provided on create. -func (u *GroupUpsert) UpdateForceOpenaiFast() *GroupUpsert { - u.SetExcluded(group.FieldForceOpenaiFast) +// UpdateForcedServiceTier sets the "forced_service_tier" field to the value that was provided on create. +func (u *GroupUpsert) UpdateForcedServiceTier() *GroupUpsert { + u.SetExcluded(group.FieldForcedServiceTier) return u } @@ -2776,6 +2801,18 @@ func (u *GroupUpsert) UpdateFreeOpenaiFast() *GroupUpsert { return u } +// SetFreeOpenaiUltrafast sets the "free_openai_ultrafast" field. +func (u *GroupUpsert) SetFreeOpenaiUltrafast(v bool) *GroupUpsert { + u.Set(group.FieldFreeOpenaiUltrafast, v) + return u +} + +// UpdateFreeOpenaiUltrafast sets the "free_openai_ultrafast" field to the value that was provided on create. +func (u *GroupUpsert) UpdateFreeOpenaiUltrafast() *GroupUpsert { + u.SetExcluded(group.FieldFreeOpenaiUltrafast) + return u +} + // SetRequireOauthOnly sets the "require_oauth_only" field. func (u *GroupUpsert) SetRequireOauthOnly(v bool) *GroupUpsert { u.Set(group.FieldRequireOauthOnly, v) @@ -4013,17 +4050,17 @@ func (u *GroupUpsertOne) UpdateAllowLive() *GroupUpsertOne { }) } -// SetForceOpenaiFast sets the "force_openai_fast" field. -func (u *GroupUpsertOne) SetForceOpenaiFast(v bool) *GroupUpsertOne { +// SetForcedServiceTier sets the "forced_service_tier" field. +func (u *GroupUpsertOne) SetForcedServiceTier(v string) *GroupUpsertOne { return u.Update(func(s *GroupUpsert) { - s.SetForceOpenaiFast(v) + s.SetForcedServiceTier(v) }) } -// UpdateForceOpenaiFast sets the "force_openai_fast" field to the value that was provided on create. -func (u *GroupUpsertOne) UpdateForceOpenaiFast() *GroupUpsertOne { +// UpdateForcedServiceTier sets the "forced_service_tier" field to the value that was provided on create. +func (u *GroupUpsertOne) UpdateForcedServiceTier() *GroupUpsertOne { return u.Update(func(s *GroupUpsert) { - s.UpdateForceOpenaiFast() + s.UpdateForcedServiceTier() }) } @@ -4041,6 +4078,20 @@ func (u *GroupUpsertOne) UpdateFreeOpenaiFast() *GroupUpsertOne { }) } +// SetFreeOpenaiUltrafast sets the "free_openai_ultrafast" field. +func (u *GroupUpsertOne) SetFreeOpenaiUltrafast(v bool) *GroupUpsertOne { + return u.Update(func(s *GroupUpsert) { + s.SetFreeOpenaiUltrafast(v) + }) +} + +// UpdateFreeOpenaiUltrafast sets the "free_openai_ultrafast" field to the value that was provided on create. +func (u *GroupUpsertOne) UpdateFreeOpenaiUltrafast() *GroupUpsertOne { + return u.Update(func(s *GroupUpsert) { + s.UpdateFreeOpenaiUltrafast() + }) +} + // SetRequireOauthOnly sets the "require_oauth_only" field. func (u *GroupUpsertOne) SetRequireOauthOnly(v bool) *GroupUpsertOne { return u.Update(func(s *GroupUpsert) { @@ -5473,17 +5524,17 @@ func (u *GroupUpsertBulk) UpdateAllowLive() *GroupUpsertBulk { }) } -// SetForceOpenaiFast sets the "force_openai_fast" field. -func (u *GroupUpsertBulk) SetForceOpenaiFast(v bool) *GroupUpsertBulk { +// SetForcedServiceTier sets the "forced_service_tier" field. +func (u *GroupUpsertBulk) SetForcedServiceTier(v string) *GroupUpsertBulk { return u.Update(func(s *GroupUpsert) { - s.SetForceOpenaiFast(v) + s.SetForcedServiceTier(v) }) } -// UpdateForceOpenaiFast sets the "force_openai_fast" field to the value that was provided on create. -func (u *GroupUpsertBulk) UpdateForceOpenaiFast() *GroupUpsertBulk { +// UpdateForcedServiceTier sets the "forced_service_tier" field to the value that was provided on create. +func (u *GroupUpsertBulk) UpdateForcedServiceTier() *GroupUpsertBulk { return u.Update(func(s *GroupUpsert) { - s.UpdateForceOpenaiFast() + s.UpdateForcedServiceTier() }) } @@ -5501,6 +5552,20 @@ func (u *GroupUpsertBulk) UpdateFreeOpenaiFast() *GroupUpsertBulk { }) } +// SetFreeOpenaiUltrafast sets the "free_openai_ultrafast" field. +func (u *GroupUpsertBulk) SetFreeOpenaiUltrafast(v bool) *GroupUpsertBulk { + return u.Update(func(s *GroupUpsert) { + s.SetFreeOpenaiUltrafast(v) + }) +} + +// UpdateFreeOpenaiUltrafast sets the "free_openai_ultrafast" field to the value that was provided on create. +func (u *GroupUpsertBulk) UpdateFreeOpenaiUltrafast() *GroupUpsertBulk { + return u.Update(func(s *GroupUpsert) { + s.UpdateFreeOpenaiUltrafast() + }) +} + // SetRequireOauthOnly sets the "require_oauth_only" field. func (u *GroupUpsertBulk) SetRequireOauthOnly(v bool) *GroupUpsertBulk { return u.Update(func(s *GroupUpsert) { diff --git a/backend/ent/group_update.go b/backend/ent/group_update.go index d4d8b8572913..2c09991fed75 100644 --- a/backend/ent/group_update.go +++ b/backend/ent/group_update.go @@ -995,16 +995,16 @@ func (_u *GroupUpdate) SetNillableAllowLive(v *bool) *GroupUpdate { return _u } -// SetForceOpenaiFast sets the "force_openai_fast" field. -func (_u *GroupUpdate) SetForceOpenaiFast(v bool) *GroupUpdate { - _u.mutation.SetForceOpenaiFast(v) +// SetForcedServiceTier sets the "forced_service_tier" field. +func (_u *GroupUpdate) SetForcedServiceTier(v string) *GroupUpdate { + _u.mutation.SetForcedServiceTier(v) return _u } -// SetNillableForceOpenaiFast sets the "force_openai_fast" field if the given value is not nil. -func (_u *GroupUpdate) SetNillableForceOpenaiFast(v *bool) *GroupUpdate { +// SetNillableForcedServiceTier sets the "forced_service_tier" field if the given value is not nil. +func (_u *GroupUpdate) SetNillableForcedServiceTier(v *string) *GroupUpdate { if v != nil { - _u.SetForceOpenaiFast(*v) + _u.SetForcedServiceTier(*v) } return _u } @@ -1023,6 +1023,20 @@ func (_u *GroupUpdate) SetNillableFreeOpenaiFast(v *bool) *GroupUpdate { return _u } +// SetFreeOpenaiUltrafast sets the "free_openai_ultrafast" field. +func (_u *GroupUpdate) SetFreeOpenaiUltrafast(v bool) *GroupUpdate { + _u.mutation.SetFreeOpenaiUltrafast(v) + return _u +} + +// SetNillableFreeOpenaiUltrafast sets the "free_openai_ultrafast" field if the given value is not nil. +func (_u *GroupUpdate) SetNillableFreeOpenaiUltrafast(v *bool) *GroupUpdate { + if v != nil { + _u.SetFreeOpenaiUltrafast(*v) + } + return _u +} + // SetRequireOauthOnly sets the "require_oauth_only" field. func (_u *GroupUpdate) SetRequireOauthOnly(v bool) *GroupUpdate { _u.mutation.SetRequireOauthOnly(v) @@ -1867,12 +1881,15 @@ func (_u *GroupUpdate) sqlSave(ctx context.Context) (_node int, err error) { if value, ok := _u.mutation.AllowLive(); ok { _spec.SetField(group.FieldAllowLive, field.TypeBool, value) } - if value, ok := _u.mutation.ForceOpenaiFast(); ok { - _spec.SetField(group.FieldForceOpenaiFast, field.TypeBool, value) + if value, ok := _u.mutation.ForcedServiceTier(); ok { + _spec.SetField(group.FieldForcedServiceTier, field.TypeString, value) } if value, ok := _u.mutation.FreeOpenaiFast(); ok { _spec.SetField(group.FieldFreeOpenaiFast, field.TypeBool, value) } + if value, ok := _u.mutation.FreeOpenaiUltrafast(); ok { + _spec.SetField(group.FieldFreeOpenaiUltrafast, field.TypeBool, value) + } if value, ok := _u.mutation.RequireOauthOnly(); ok { _spec.SetField(group.FieldRequireOauthOnly, field.TypeBool, value) } @@ -3198,16 +3215,16 @@ func (_u *GroupUpdateOne) SetNillableAllowLive(v *bool) *GroupUpdateOne { return _u } -// SetForceOpenaiFast sets the "force_openai_fast" field. -func (_u *GroupUpdateOne) SetForceOpenaiFast(v bool) *GroupUpdateOne { - _u.mutation.SetForceOpenaiFast(v) +// SetForcedServiceTier sets the "forced_service_tier" field. +func (_u *GroupUpdateOne) SetForcedServiceTier(v string) *GroupUpdateOne { + _u.mutation.SetForcedServiceTier(v) return _u } -// SetNillableForceOpenaiFast sets the "force_openai_fast" field if the given value is not nil. -func (_u *GroupUpdateOne) SetNillableForceOpenaiFast(v *bool) *GroupUpdateOne { +// SetNillableForcedServiceTier sets the "forced_service_tier" field if the given value is not nil. +func (_u *GroupUpdateOne) SetNillableForcedServiceTier(v *string) *GroupUpdateOne { if v != nil { - _u.SetForceOpenaiFast(*v) + _u.SetForcedServiceTier(*v) } return _u } @@ -3226,6 +3243,20 @@ func (_u *GroupUpdateOne) SetNillableFreeOpenaiFast(v *bool) *GroupUpdateOne { return _u } +// SetFreeOpenaiUltrafast sets the "free_openai_ultrafast" field. +func (_u *GroupUpdateOne) SetFreeOpenaiUltrafast(v bool) *GroupUpdateOne { + _u.mutation.SetFreeOpenaiUltrafast(v) + return _u +} + +// SetNillableFreeOpenaiUltrafast sets the "free_openai_ultrafast" field if the given value is not nil. +func (_u *GroupUpdateOne) SetNillableFreeOpenaiUltrafast(v *bool) *GroupUpdateOne { + if v != nil { + _u.SetFreeOpenaiUltrafast(*v) + } + return _u +} + // SetRequireOauthOnly sets the "require_oauth_only" field. func (_u *GroupUpdateOne) SetRequireOauthOnly(v bool) *GroupUpdateOne { _u.mutation.SetRequireOauthOnly(v) @@ -4100,12 +4131,15 @@ func (_u *GroupUpdateOne) sqlSave(ctx context.Context) (_node *Group, err error) if value, ok := _u.mutation.AllowLive(); ok { _spec.SetField(group.FieldAllowLive, field.TypeBool, value) } - if value, ok := _u.mutation.ForceOpenaiFast(); ok { - _spec.SetField(group.FieldForceOpenaiFast, field.TypeBool, value) + if value, ok := _u.mutation.ForcedServiceTier(); ok { + _spec.SetField(group.FieldForcedServiceTier, field.TypeString, value) } if value, ok := _u.mutation.FreeOpenaiFast(); ok { _spec.SetField(group.FieldFreeOpenaiFast, field.TypeBool, value) } + if value, ok := _u.mutation.FreeOpenaiUltrafast(); ok { + _spec.SetField(group.FieldFreeOpenaiUltrafast, field.TypeBool, value) + } if value, ok := _u.mutation.RequireOauthOnly(); ok { _spec.SetField(group.FieldRequireOauthOnly, field.TypeBool, value) } diff --git a/backend/ent/migrate/schema.go b/backend/ent/migrate/schema.go index f12c75839142..efc84bd673eb 100644 --- a/backend/ent/migrate/schema.go +++ b/backend/ent/migrate/schema.go @@ -955,8 +955,9 @@ var ( {Name: "sort_order", Type: field.TypeInt, Default: 0}, {Name: "allow_messages_dispatch", Type: field.TypeBool, Default: false}, {Name: "allow_live", Type: field.TypeBool, Default: false}, - {Name: "force_openai_fast", Type: field.TypeBool, Default: false}, + {Name: "forced_service_tier", Type: field.TypeString, Default: ""}, {Name: "free_openai_fast", Type: field.TypeBool, Default: false}, + {Name: "free_openai_ultrafast", Type: field.TypeBool, Default: false}, {Name: "require_oauth_only", Type: field.TypeBool, Default: false}, {Name: "require_privacy_set", Type: field.TypeBool, Default: false}, {Name: "default_mapped_model", Type: field.TypeString, Size: 100, Default: ""}, diff --git a/backend/ent/mutation.go b/backend/ent/mutation.go index b4442aef4453..6eab629b9165 100644 --- a/backend/ent/mutation.go +++ b/backend/ent/mutation.go @@ -22159,8 +22159,9 @@ type GroupMutation struct { addsort_order *int allow_messages_dispatch *bool allow_live *bool - force_openai_fast *bool + forced_service_tier *string free_openai_fast *bool + free_openai_ultrafast *bool require_oauth_only *bool require_privacy_set *bool default_mapped_model *string @@ -24985,40 +24986,40 @@ func (m *GroupMutation) ResetAllowLive() { m.allow_live = nil } -// SetForceOpenaiFast sets the "force_openai_fast" field. -func (m *GroupMutation) SetForceOpenaiFast(b bool) { - m.force_openai_fast = &b +// SetForcedServiceTier sets the "forced_service_tier" field. +func (m *GroupMutation) SetForcedServiceTier(s string) { + m.forced_service_tier = &s } -// ForceOpenaiFast returns the value of the "force_openai_fast" field in the mutation. -func (m *GroupMutation) ForceOpenaiFast() (r bool, exists bool) { - v := m.force_openai_fast +// ForcedServiceTier returns the value of the "forced_service_tier" field in the mutation. +func (m *GroupMutation) ForcedServiceTier() (r string, exists bool) { + v := m.forced_service_tier if v == nil { return } return *v, true } -// OldForceOpenaiFast returns the old "force_openai_fast" field's value of the Group entity. +// OldForcedServiceTier returns the old "forced_service_tier" field's value of the Group entity. // If the Group object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *GroupMutation) OldForceOpenaiFast(ctx context.Context) (v bool, err error) { +func (m *GroupMutation) OldForcedServiceTier(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldForceOpenaiFast is only allowed on UpdateOne operations") + return v, errors.New("OldForcedServiceTier is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { - return v, errors.New("OldForceOpenaiFast requires an ID field in the mutation") + return v, errors.New("OldForcedServiceTier requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { - return v, fmt.Errorf("querying old value for OldForceOpenaiFast: %w", err) + return v, fmt.Errorf("querying old value for OldForcedServiceTier: %w", err) } - return oldValue.ForceOpenaiFast, nil + return oldValue.ForcedServiceTier, nil } -// ResetForceOpenaiFast resets all changes to the "force_openai_fast" field. -func (m *GroupMutation) ResetForceOpenaiFast() { - m.force_openai_fast = nil +// ResetForcedServiceTier resets all changes to the "forced_service_tier" field. +func (m *GroupMutation) ResetForcedServiceTier() { + m.forced_service_tier = nil } // SetFreeOpenaiFast sets the "free_openai_fast" field. @@ -25057,6 +25058,42 @@ func (m *GroupMutation) ResetFreeOpenaiFast() { m.free_openai_fast = nil } +// SetFreeOpenaiUltrafast sets the "free_openai_ultrafast" field. +func (m *GroupMutation) SetFreeOpenaiUltrafast(b bool) { + m.free_openai_ultrafast = &b +} + +// FreeOpenaiUltrafast returns the value of the "free_openai_ultrafast" field in the mutation. +func (m *GroupMutation) FreeOpenaiUltrafast() (r bool, exists bool) { + v := m.free_openai_ultrafast + if v == nil { + return + } + return *v, true +} + +// OldFreeOpenaiUltrafast returns the old "free_openai_ultrafast" field's value of the Group entity. +// If the Group object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *GroupMutation) OldFreeOpenaiUltrafast(ctx context.Context) (v bool, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldFreeOpenaiUltrafast is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldFreeOpenaiUltrafast requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldFreeOpenaiUltrafast: %w", err) + } + return oldValue.FreeOpenaiUltrafast, nil +} + +// ResetFreeOpenaiUltrafast resets all changes to the "free_openai_ultrafast" field. +func (m *GroupMutation) ResetFreeOpenaiUltrafast() { + m.free_openai_ultrafast = nil +} + // SetRequireOauthOnly sets the "require_oauth_only" field. func (m *GroupMutation) SetRequireOauthOnly(b bool) { m.require_oauth_only = &b @@ -25958,7 +25995,7 @@ func (m *GroupMutation) Type() string { // order to get all numeric fields that were incremented/decremented, call // AddedFields(). func (m *GroupMutation) Fields() []string { - fields := make([]string, 0, 67) + fields := make([]string, 0, 68) if m.created_at != nil { fields = append(fields, group.FieldCreatedAt) } @@ -26115,12 +26152,15 @@ func (m *GroupMutation) Fields() []string { if m.allow_live != nil { fields = append(fields, group.FieldAllowLive) } - if m.force_openai_fast != nil { - fields = append(fields, group.FieldForceOpenaiFast) + if m.forced_service_tier != nil { + fields = append(fields, group.FieldForcedServiceTier) } if m.free_openai_fast != nil { fields = append(fields, group.FieldFreeOpenaiFast) } + if m.free_openai_ultrafast != nil { + fields = append(fields, group.FieldFreeOpenaiUltrafast) + } if m.require_oauth_only != nil { fields = append(fields, group.FieldRequireOauthOnly) } @@ -26272,10 +26312,12 @@ func (m *GroupMutation) Field(name string) (ent.Value, bool) { return m.AllowMessagesDispatch() case group.FieldAllowLive: return m.AllowLive() - case group.FieldForceOpenaiFast: - return m.ForceOpenaiFast() + case group.FieldForcedServiceTier: + return m.ForcedServiceTier() case group.FieldFreeOpenaiFast: return m.FreeOpenaiFast() + case group.FieldFreeOpenaiUltrafast: + return m.FreeOpenaiUltrafast() case group.FieldRequireOauthOnly: return m.RequireOauthOnly() case group.FieldRequirePrivacySet: @@ -26415,10 +26457,12 @@ func (m *GroupMutation) OldField(ctx context.Context, name string) (ent.Value, e return m.OldAllowMessagesDispatch(ctx) case group.FieldAllowLive: return m.OldAllowLive(ctx) - case group.FieldForceOpenaiFast: - return m.OldForceOpenaiFast(ctx) + case group.FieldForcedServiceTier: + return m.OldForcedServiceTier(ctx) case group.FieldFreeOpenaiFast: return m.OldFreeOpenaiFast(ctx) + case group.FieldFreeOpenaiUltrafast: + return m.OldFreeOpenaiUltrafast(ctx) case group.FieldRequireOauthOnly: return m.OldRequireOauthOnly(ctx) case group.FieldRequirePrivacySet: @@ -26818,12 +26862,12 @@ func (m *GroupMutation) SetField(name string, value ent.Value) error { } m.SetAllowLive(v) return nil - case group.FieldForceOpenaiFast: - v, ok := value.(bool) + case group.FieldForcedServiceTier: + v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } - m.SetForceOpenaiFast(v) + m.SetForcedServiceTier(v) return nil case group.FieldFreeOpenaiFast: v, ok := value.(bool) @@ -26832,6 +26876,13 @@ func (m *GroupMutation) SetField(name string, value ent.Value) error { } m.SetFreeOpenaiFast(v) return nil + case group.FieldFreeOpenaiUltrafast: + v, ok := value.(bool) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetFreeOpenaiUltrafast(v) + return nil case group.FieldRequireOauthOnly: v, ok := value.(bool) if !ok { @@ -27590,12 +27641,15 @@ func (m *GroupMutation) ResetField(name string) error { case group.FieldAllowLive: m.ResetAllowLive() return nil - case group.FieldForceOpenaiFast: - m.ResetForceOpenaiFast() + case group.FieldForcedServiceTier: + m.ResetForcedServiceTier() return nil case group.FieldFreeOpenaiFast: m.ResetFreeOpenaiFast() return nil + case group.FieldFreeOpenaiUltrafast: + m.ResetFreeOpenaiUltrafast() + return nil case group.FieldRequireOauthOnly: m.ResetRequireOauthOnly() return nil diff --git a/backend/ent/runtime/runtime.go b/backend/ent/runtime/runtime.go index d36bddb44670..cffab85d3c3e 100644 --- a/backend/ent/runtime/runtime.go +++ b/backend/ent/runtime/runtime.go @@ -1175,70 +1175,74 @@ func init() { groupDescAllowLive := groupFields[48].Descriptor() // group.DefaultAllowLive holds the default value on creation for the allow_live field. group.DefaultAllowLive = groupDescAllowLive.Default.(bool) - // groupDescForceOpenaiFast is the schema descriptor for force_openai_fast field. - groupDescForceOpenaiFast := groupFields[49].Descriptor() - // group.DefaultForceOpenaiFast holds the default value on creation for the force_openai_fast field. - group.DefaultForceOpenaiFast = groupDescForceOpenaiFast.Default.(bool) + // groupDescForcedServiceTier is the schema descriptor for forced_service_tier field. + groupDescForcedServiceTier := groupFields[49].Descriptor() + // group.DefaultForcedServiceTier holds the default value on creation for the forced_service_tier field. + group.DefaultForcedServiceTier = groupDescForcedServiceTier.Default.(string) // groupDescFreeOpenaiFast is the schema descriptor for free_openai_fast field. groupDescFreeOpenaiFast := groupFields[50].Descriptor() // group.DefaultFreeOpenaiFast holds the default value on creation for the free_openai_fast field. group.DefaultFreeOpenaiFast = groupDescFreeOpenaiFast.Default.(bool) + // groupDescFreeOpenaiUltrafast is the schema descriptor for free_openai_ultrafast field. + groupDescFreeOpenaiUltrafast := groupFields[51].Descriptor() + // group.DefaultFreeOpenaiUltrafast holds the default value on creation for the free_openai_ultrafast field. + group.DefaultFreeOpenaiUltrafast = groupDescFreeOpenaiUltrafast.Default.(bool) // groupDescRequireOauthOnly is the schema descriptor for require_oauth_only field. - groupDescRequireOauthOnly := groupFields[51].Descriptor() + groupDescRequireOauthOnly := groupFields[52].Descriptor() // group.DefaultRequireOauthOnly holds the default value on creation for the require_oauth_only field. group.DefaultRequireOauthOnly = groupDescRequireOauthOnly.Default.(bool) // groupDescRequirePrivacySet is the schema descriptor for require_privacy_set field. - groupDescRequirePrivacySet := groupFields[52].Descriptor() + groupDescRequirePrivacySet := groupFields[53].Descriptor() // group.DefaultRequirePrivacySet holds the default value on creation for the require_privacy_set field. group.DefaultRequirePrivacySet = groupDescRequirePrivacySet.Default.(bool) // groupDescDefaultMappedModel is the schema descriptor for default_mapped_model field. - groupDescDefaultMappedModel := groupFields[53].Descriptor() + groupDescDefaultMappedModel := groupFields[54].Descriptor() // group.DefaultDefaultMappedModel holds the default value on creation for the default_mapped_model field. group.DefaultDefaultMappedModel = groupDescDefaultMappedModel.Default.(string) // group.DefaultMappedModelValidator is a validator for the "default_mapped_model" field. It is called by the builders before save. group.DefaultMappedModelValidator = groupDescDefaultMappedModel.Validators[0].(func(string) error) // groupDescMessagesDispatchModelConfig is the schema descriptor for messages_dispatch_model_config field. - groupDescMessagesDispatchModelConfig := groupFields[54].Descriptor() + groupDescMessagesDispatchModelConfig := groupFields[55].Descriptor() // group.DefaultMessagesDispatchModelConfig holds the default value on creation for the messages_dispatch_model_config field. group.DefaultMessagesDispatchModelConfig = groupDescMessagesDispatchModelConfig.Default.(domain.OpenAIMessagesDispatchModelConfig) // groupDescModelAllowlist is the schema descriptor for model_allowlist field. - groupDescModelAllowlist := groupFields[55].Descriptor() + groupDescModelAllowlist := groupFields[56].Descriptor() // group.DefaultModelAllowlist holds the default value on creation for the model_allowlist field. group.DefaultModelAllowlist = groupDescModelAllowlist.Default.(domain.GroupModelAllowlist) // groupDescCodexModelsManifestConfig is the schema descriptor for codex_models_manifest_config field. - groupDescCodexModelsManifestConfig := groupFields[56].Descriptor() + groupDescCodexModelsManifestConfig := groupFields[57].Descriptor() // group.DefaultCodexModelsManifestConfig holds the default value on creation for the codex_models_manifest_config field. group.DefaultCodexModelsManifestConfig = groupDescCodexModelsManifestConfig.Default.(domain.GroupCodexModelsManifestConfig) // groupDescRpmLimit is the schema descriptor for rpm_limit field. - groupDescRpmLimit := groupFields[57].Descriptor() + groupDescRpmLimit := groupFields[58].Descriptor() // group.DefaultRpmLimit holds the default value on creation for the rpm_limit field. group.DefaultRpmLimit = groupDescRpmLimit.Default.(int) // groupDescMaxReasoningEffort is the schema descriptor for max_reasoning_effort field. - groupDescMaxReasoningEffort := groupFields[58].Descriptor() + groupDescMaxReasoningEffort := groupFields[59].Descriptor() // group.DefaultMaxReasoningEffort holds the default value on creation for the max_reasoning_effort field. group.DefaultMaxReasoningEffort = groupDescMaxReasoningEffort.Default.(string) // group.MaxReasoningEffortValidator is a validator for the "max_reasoning_effort" field. It is called by the builders before save. group.MaxReasoningEffortValidator = groupDescMaxReasoningEffort.Validators[0].(func(string) error) // groupDescMaxReasoningEffortOverLimit is the schema descriptor for max_reasoning_effort_over_limit field. - groupDescMaxReasoningEffortOverLimit := groupFields[59].Descriptor() + groupDescMaxReasoningEffortOverLimit := groupFields[60].Descriptor() // group.DefaultMaxReasoningEffortOverLimit holds the default value on creation for the max_reasoning_effort_over_limit field. group.DefaultMaxReasoningEffortOverLimit = groupDescMaxReasoningEffortOverLimit.Default.(string) // group.MaxReasoningEffortOverLimitValidator is a validator for the "max_reasoning_effort_over_limit" field. It is called by the builders before save. group.MaxReasoningEffortOverLimitValidator = groupDescMaxReasoningEffortOverLimit.Validators[0].(func(string) error) // groupDescReasoningEffortMappings is the schema descriptor for reasoning_effort_mappings field. - groupDescReasoningEffortMappings := groupFields[60].Descriptor() + groupDescReasoningEffortMappings := groupFields[61].Descriptor() // group.DefaultReasoningEffortMappings holds the default value on creation for the reasoning_effort_mappings field. group.DefaultReasoningEffortMappings = groupDescReasoningEffortMappings.Default.([]domain.ReasoningEffortMapping) // groupDescProfitControlEnabled is the schema descriptor for profit_control_enabled field. - groupDescProfitControlEnabled := groupFields[61].Descriptor() + groupDescProfitControlEnabled := groupFields[62].Descriptor() // group.DefaultProfitControlEnabled holds the default value on creation for the profit_control_enabled field. group.DefaultProfitControlEnabled = groupDescProfitControlEnabled.Default.(bool) // groupDescProfitMinMargin is the schema descriptor for profit_min_margin field. - groupDescProfitMinMargin := groupFields[62].Descriptor() + groupDescProfitMinMargin := groupFields[63].Descriptor() // group.DefaultProfitMinMargin holds the default value on creation for the profit_min_margin field. group.DefaultProfitMinMargin = groupDescProfitMinMargin.Default.(float64) // groupDescProfitSafetyBuffer is the schema descriptor for profit_safety_buffer field. - groupDescProfitSafetyBuffer := groupFields[63].Descriptor() + groupDescProfitSafetyBuffer := groupFields[64].Descriptor() // group.DefaultProfitSafetyBuffer holds the default value on creation for the profit_safety_buffer field. group.DefaultProfitSafetyBuffer = groupDescProfitSafetyBuffer.Default.(float64) idempotencyrecordMixin := schema.IdempotencyRecord{}.Mixin() diff --git a/backend/ent/schema/group.go b/backend/ent/schema/group.go index 48222c69c552..ce50b7c2f381 100644 --- a/backend/ent/schema/group.go +++ b/backend/ent/schema/group.go @@ -247,12 +247,15 @@ func (Group) Fields() []ent.Field { field.Bool("allow_live"). Default(false). Comment("是否允许此 OpenAI 分组访问 Live 接口"), - field.Bool("force_openai_fast"). - Default(false). - Comment("是否强制此 OpenAI/Composite 分组请求使用 service_tier=priority"), + field.String("forced_service_tier"). + Default(""). + Comment("强制此 OpenAI/Composite 分组请求使用的 service_tier;空字符串表示跟随请求"), field.Bool("free_openai_fast"). Default(false). Comment("是否让此 OpenAI/Composite 分组的 Fast 请求按 Standard 价格计费"), + field.Bool("free_openai_ultrafast"). + Default(false). + Comment("是否让此 OpenAI/Composite 分组的 UltraFast 请求按 Standard 价格计费"), field.Bool("require_oauth_only"). Default(false). Comment("仅允许非 apikey 类型账号关联到此分组"), diff --git a/backend/internal/handler/admin/group_handler.go b/backend/internal/handler/admin/group_handler.go index 4fed4b2bb4fb..258475f6f9b7 100644 --- a/backend/internal/handler/admin/group_handler.go +++ b/backend/internal/handler/admin/group_handler.go @@ -183,6 +183,34 @@ func sanitizeUpdateGroupRequestForSimpleMode(req *UpdateGroupRequest) { *req = UpdateGroupRequest{Name: req.Name, Description: req.Description, I18n: req.I18n} } +// resolveCreateForcedServiceTier applies the deprecated force_openai_fast +// alias only when the explicit tier is empty. +func resolveCreateForcedServiceTier(explicit string, legacy *bool) string { + if explicit != "" { + return explicit + } + if legacy != nil && *legacy { + return service.OpenAIFastTierPriority + } + return "" +} + +// resolveUpdateForcedServiceTier returns nil when neither field is present, so +// an omitted update keeps the stored tier. +func resolveUpdateForcedServiceTier(explicit *string, legacy *bool) *string { + if explicit != nil { + return explicit + } + if legacy == nil { + return nil + } + tier := "" + if *legacy { + tier = service.OpenAIFastTierPriority + } + return &tier +} + // CreateGroupRequest represents create group request type CreateGroupRequest struct { Name string `json:"name" binding:"required"` @@ -235,10 +263,13 @@ type CreateGroupRequest struct { // 支持的模型系列(仅 antigravity 平台使用) SupportedModelScopes []string `json:"supported_model_scopes"` // OpenAI Messages 调度配置(仅 openai 平台使用) - AllowMessagesDispatch bool `json:"allow_messages_dispatch"` - AllowLive bool `json:"allow_live"` - ForceOpenAIFast bool `json:"force_openai_fast"` + AllowMessagesDispatch bool `json:"allow_messages_dispatch"` + AllowLive bool `json:"allow_live"` + ForcedServiceTier string `json:"forced_service_tier"` + // ForceOpenAIFast 是已弃用的兼容字段;优先使用 ForcedServiceTier。 + ForceOpenAIFast *bool `json:"force_openai_fast"` FreeOpenAIFast bool `json:"free_openai_fast"` + FreeOpenAIUltrafast bool `json:"free_openai_ultrafast"` RequireOAuthOnly bool `json:"require_oauth_only"` RequirePrivacySet bool `json:"require_privacy_set"` DefaultMappedModel string `json:"default_mapped_model"` @@ -311,10 +342,13 @@ type UpdateGroupRequest struct { // 支持的模型系列(仅 antigravity 平台使用) SupportedModelScopes *[]string `json:"supported_model_scopes"` // OpenAI Messages 调度配置(仅 openai 平台使用) - AllowMessagesDispatch *bool `json:"allow_messages_dispatch"` - AllowLive *bool `json:"allow_live"` + AllowMessagesDispatch *bool `json:"allow_messages_dispatch"` + AllowLive *bool `json:"allow_live"` + ForcedServiceTier *string `json:"forced_service_tier"` + // ForceOpenAIFast 是已弃用的兼容字段;优先使用 ForcedServiceTier。 ForceOpenAIFast *bool `json:"force_openai_fast"` FreeOpenAIFast *bool `json:"free_openai_fast"` + FreeOpenAIUltrafast *bool `json:"free_openai_ultrafast"` RequireOAuthOnly *bool `json:"require_oauth_only"` RequirePrivacySet *bool `json:"require_privacy_set"` DefaultMappedModel *string `json:"default_mapped_model"` @@ -715,8 +749,9 @@ func (h *GroupHandler) Create(c *gin.Context) { SupportedModelScopes: req.SupportedModelScopes, AllowMessagesDispatch: req.AllowMessagesDispatch, AllowLive: req.AllowLive, - ForceOpenAIFast: req.ForceOpenAIFast, + ForcedServiceTier: resolveCreateForcedServiceTier(req.ForcedServiceTier, req.ForceOpenAIFast), FreeOpenAIFast: req.FreeOpenAIFast, + FreeOpenAIUltrafast: req.FreeOpenAIUltrafast, RequireOAuthOnly: req.RequireOAuthOnly, RequirePrivacySet: req.RequirePrivacySet, DefaultMappedModel: req.DefaultMappedModel, @@ -862,8 +897,9 @@ func (h *GroupHandler) Update(c *gin.Context) { SupportedModelScopes: req.SupportedModelScopes, AllowMessagesDispatch: req.AllowMessagesDispatch, AllowLive: req.AllowLive, - ForceOpenAIFast: req.ForceOpenAIFast, + ForcedServiceTier: resolveUpdateForcedServiceTier(req.ForcedServiceTier, req.ForceOpenAIFast), FreeOpenAIFast: req.FreeOpenAIFast, + FreeOpenAIUltrafast: req.FreeOpenAIUltrafast, RequireOAuthOnly: req.RequireOAuthOnly, RequirePrivacySet: req.RequirePrivacySet, DefaultMappedModel: req.DefaultMappedModel, diff --git a/backend/internal/handler/admin/group_handler_openai_fast_test.go b/backend/internal/handler/admin/group_handler_openai_fast_test.go index 54ff52f9f9be..c196ccb94075 100644 --- a/backend/internal/handler/admin/group_handler_openai_fast_test.go +++ b/backend/internal/handler/admin/group_handler_openai_fast_test.go @@ -4,24 +4,34 @@ import ( "encoding/json" "testing" + "github.com/Wei-Shaw/sub2api/internal/service" "github.com/stretchr/testify/require" ) func TestGroupRequestsDecodeForceOpenAIFast(t *testing.T) { var createReq CreateGroupRequest - require.NoError(t, json.Unmarshal([]byte(`{"name":"fast","force_openai_fast":true,"free_openai_fast":true}`), &createReq)) - require.True(t, createReq.ForceOpenAIFast) + require.NoError(t, json.Unmarshal([]byte(`{"name":"fast","forced_service_tier":"priority","free_openai_fast":true}`), &createReq)) + require.Equal(t, service.OpenAIFastTierPriority, createReq.ForcedServiceTier) require.True(t, createReq.FreeOpenAIFast) var updateReq UpdateGroupRequest - require.NoError(t, json.Unmarshal([]byte(`{"force_openai_fast":false,"free_openai_fast":false}`), &updateReq)) - require.NotNil(t, updateReq.ForceOpenAIFast) - require.False(t, *updateReq.ForceOpenAIFast) + require.NoError(t, json.Unmarshal([]byte(`{"forced_service_tier":"","free_openai_fast":false}`), &updateReq)) + require.NotNil(t, updateReq.ForcedServiceTier) + require.Empty(t, *updateReq.ForcedServiceTier) require.NotNil(t, updateReq.FreeOpenAIFast) require.False(t, *updateReq.FreeOpenAIFast) - var omitted UpdateGroupRequest - require.NoError(t, json.Unmarshal([]byte(`{}`), &omitted)) - require.Nil(t, omitted.ForceOpenAIFast) - require.Nil(t, omitted.FreeOpenAIFast) + var legacy CreateGroupRequest + require.NoError(t, json.Unmarshal([]byte(`{"name":"fast","force_openai_fast":true}`), &legacy)) + require.NotNil(t, legacy.ForceOpenAIFast) + require.True(t, *legacy.ForceOpenAIFast) + require.Equal(t, service.OpenAIFastTierPriority, resolveCreateForcedServiceTier(legacy.ForcedServiceTier, legacy.ForceOpenAIFast)) + require.Equal(t, service.OpenAIFastTierDefault, resolveCreateForcedServiceTier(service.OpenAIFastTierDefault, legacy.ForceOpenAIFast)) + + var legacyUpdate UpdateGroupRequest + require.NoError(t, json.Unmarshal([]byte(`{"force_openai_fast":false}`), &legacyUpdate)) + resolved := resolveUpdateForcedServiceTier(legacyUpdate.ForcedServiceTier, legacyUpdate.ForceOpenAIFast) + require.NotNil(t, resolved) + require.Empty(t, *resolved) + require.Nil(t, resolveUpdateForcedServiceTier(nil, nil)) } diff --git a/backend/internal/handler/dto/group_mapper_openai_fast_test.go b/backend/internal/handler/dto/group_mapper_openai_fast_test.go index 6ad07b061f66..2a4c85bf4f19 100644 --- a/backend/internal/handler/dto/group_mapper_openai_fast_test.go +++ b/backend/internal/handler/dto/group_mapper_openai_fast_test.go @@ -10,16 +10,17 @@ import ( func TestGroupMapperExposesForceOpenAIFastOnlyToAdmins(t *testing.T) { group := &service.Group{ - ID: 7, Name: "fast", Platform: service.PlatformOpenAI, Status: service.StatusActive, ForceOpenAIFast: true, FreeOpenAIFast: true, + ID: 7, Name: "fast", Platform: service.PlatformOpenAI, Status: service.StatusActive, ForcedServiceTier: service.OpenAIFastTierPriority, FreeOpenAIFast: true, } userJSON, err := json.Marshal(GroupFromService(group)) require.NoError(t, err) - require.NotContains(t, string(userJSON), "force_openai_fast") + require.NotContains(t, string(userJSON), "forced_service_tier") require.NotContains(t, string(userJSON), "free_openai_fast") adminJSON, err := json.Marshal(GroupFromServiceAdmin(group)) require.NoError(t, err) - require.Contains(t, string(adminJSON), `"force_openai_fast":true`) + require.Contains(t, string(adminJSON), `"forced_service_tier":"priority"`) require.Contains(t, string(adminJSON), `"free_openai_fast":true`) + require.Contains(t, string(adminJSON), `"force_openai_fast":true`) } diff --git a/backend/internal/handler/dto/mappers.go b/backend/internal/handler/dto/mappers.go index d0c7de939ac2..a159c580460d 100644 --- a/backend/internal/handler/dto/mappers.go +++ b/backend/internal/handler/dto/mappers.go @@ -148,8 +148,10 @@ func GroupFromServiceAdmin(g *service.Group) *AdminGroup { } out := &AdminGroup{ Group: groupFromServiceBase(g), - ForceOpenAIFast: g.ForceOpenAIFast, + ForcedServiceTier: g.ForcedServiceTier, + ForceOpenAIFast: g.ForcedServiceTier == service.OpenAIFastTierPriority, FreeOpenAIFast: g.FreeOpenAIFast, + FreeOpenAIUltrafast: g.FreeOpenAIUltrafast, ProfitControlEnabled: g.ProfitControlEnabled, ProfitMinMargin: g.ProfitMinMargin, ProfitSafetyBuffer: g.ProfitSafetyBuffer, diff --git a/backend/internal/handler/dto/types.go b/backend/internal/handler/dto/types.go index 1c79cc49c085..cf2c73be1b65 100644 --- a/backend/internal/handler/dto/types.go +++ b/backend/internal/handler/dto/types.go @@ -170,10 +170,14 @@ type Group struct { // 注意:普通用户接口不得返回 model_routing/account_count/account_groups 等内部信息。 type AdminGroup struct { Group - // ForceOpenAIFast 是管理端请求策略,用户侧分组 DTO 无需暴露。 + // ForcedServiceTier 是管理端请求策略,用户侧分组 DTO 无需暴露。 + ForcedServiceTier string `json:"forced_service_tier"` + // ForceOpenAIFast 是已弃用的只读兼容字段,等价于 ForcedServiceTier=="priority"。 ForceOpenAIFast bool `json:"force_openai_fast"` // FreeOpenAIFast 是管理端计费策略,用户侧分组 DTO 无需暴露。 FreeOpenAIFast bool `json:"free_openai_fast"` + // FreeOpenAIUltrafast 是管理端计费策略,用户侧分组 DTO 无需暴露。 + FreeOpenAIUltrafast bool `json:"free_openai_ultrafast"` // 分组利润控制(五个 token 平台分组可启用;margin/buffer 为小数存储)。 // 仅管理员可见:这三个字段与同响应中的 rate_multiplier 相乘即可反推出 diff --git a/backend/internal/repository/api_key_repo.go b/backend/internal/repository/api_key_repo.go index cc47f5e62335..5aab6d02c963 100644 --- a/backend/internal/repository/api_key_repo.go +++ b/backend/internal/repository/api_key_repo.go @@ -210,8 +210,9 @@ func (r *apiKeyRepository) GetByKeyForAuth(ctx context.Context, key string) (*se group.FieldSupportedModelScopes, group.FieldAllowMessagesDispatch, group.FieldAllowLive, - group.FieldForceOpenaiFast, + group.FieldForcedServiceTier, group.FieldFreeOpenaiFast, + group.FieldFreeOpenaiUltrafast, group.FieldDefaultMappedModel, group.FieldMessagesDispatchModelConfig, group.FieldModelAllowlist, @@ -1023,8 +1024,9 @@ func groupEntityToService(g *dbent.Group) *service.Group { SortOrder: g.SortOrder, AllowMessagesDispatch: g.AllowMessagesDispatch, AllowLive: g.AllowLive, - ForceOpenAIFast: g.ForceOpenaiFast, + ForcedServiceTier: g.ForcedServiceTier, FreeOpenAIFast: g.FreeOpenaiFast, + FreeOpenAIUltrafast: g.FreeOpenaiUltrafast, RequireOAuthOnly: g.RequireOauthOnly, RequirePrivacySet: g.RequirePrivacySet, DefaultMappedModel: g.DefaultMappedModel, diff --git a/backend/internal/repository/api_key_repo_openai_fast_projection_integration_test.go b/backend/internal/repository/api_key_repo_openai_fast_projection_integration_test.go index f3687c307032..7ed84c1271d0 100644 --- a/backend/internal/repository/api_key_repo_openai_fast_projection_integration_test.go +++ b/backend/internal/repository/api_key_repo_openai_fast_projection_integration_test.go @@ -17,7 +17,7 @@ func TestGetByKeyForAuthCarriesGroupForceOpenAIFast(t *testing.T) { suffix := time.Now().UnixNano() group := mustCreateGroup(t, integrationEntClient, &service.Group{ Name: fmt.Sprintf("fast-proj-group-%d", suffix), Platform: service.PlatformOpenAI, - RateMultiplier: 1, ForceOpenAIFast: true, FreeOpenAIFast: true, + RateMultiplier: 1, ForcedServiceTier: service.OpenAIFastTierPriority, FreeOpenAIFast: true, FreeOpenAIUltrafast: true, }) user := mustCreateUser(t, integrationEntClient, &service.User{ Email: fmt.Sprintf("fast-proj-%d@example.com", suffix), Concurrency: 5, @@ -41,6 +41,7 @@ func TestGetByKeyForAuthCarriesGroupForceOpenAIFast(t *testing.T) { got, err := apiKeyRepo.GetByKeyForAuth(ctx, keyValue) require.NoError(t, err) require.NotNil(t, got.Group) - require.True(t, got.Group.ForceOpenAIFast) + require.Equal(t, service.OpenAIFastTierPriority, got.Group.ForcedServiceTier) require.True(t, got.Group.FreeOpenAIFast) + require.True(t, got.Group.FreeOpenAIUltrafast) } diff --git a/backend/internal/repository/fixtures_integration_test.go b/backend/internal/repository/fixtures_integration_test.go index 98e0085e2fea..7fb2f76e8e36 100644 --- a/backend/internal/repository/fixtures_integration_test.go +++ b/backend/internal/repository/fixtures_integration_test.go @@ -90,8 +90,9 @@ func mustCreateGroup(t *testing.T, client *dbent.Client, g *service.Group) *serv SetSubscriptionType(g.SubscriptionType). SetRateMultiplier(g.RateMultiplier). SetIsExclusive(g.IsExclusive). - SetForceOpenaiFast(g.ForceOpenAIFast). + SetForcedServiceTier(g.ForcedServiceTier). SetFreeOpenaiFast(g.FreeOpenAIFast). + SetFreeOpenaiUltrafast(g.FreeOpenAIUltrafast). SetModelAllowlist(service.DomainGroupModelAllowlist(g.ModelAllowlist)). SetCodexModelsManifestConfig(g.CodexModelsManifestConfig). SetProfitControlEnabled(g.ProfitControlEnabled). diff --git a/backend/internal/repository/group_repo.go b/backend/internal/repository/group_repo.go index 3f619ba9b088..3188448ebd64 100644 --- a/backend/internal/repository/group_repo.go +++ b/backend/internal/repository/group_repo.go @@ -136,8 +136,9 @@ func createGroupRecord(ctx context.Context, client *dbent.Client, groupIn *servi SetMcpXMLInject(groupIn.MCPXMLInject). SetAllowMessagesDispatch(groupIn.AllowMessagesDispatch). SetAllowLive(groupIn.AllowLive). - SetForceOpenaiFast(groupIn.ForceOpenAIFast). + SetForcedServiceTier(groupIn.ForcedServiceTier). SetFreeOpenaiFast(groupIn.FreeOpenAIFast). + SetFreeOpenaiUltrafast(groupIn.FreeOpenAIUltrafast). SetRequireOauthOnly(groupIn.RequireOAuthOnly). SetRequirePrivacySet(groupIn.RequirePrivacySet). SetDefaultMappedModel(groupIn.DefaultMappedModel). @@ -320,8 +321,9 @@ func (r *groupRepository) Update(ctx context.Context, groupIn *service.Group) er SetMcpXMLInject(groupIn.MCPXMLInject). SetAllowMessagesDispatch(groupIn.AllowMessagesDispatch). SetAllowLive(groupIn.AllowLive). - SetForceOpenaiFast(groupIn.ForceOpenAIFast). + SetForcedServiceTier(groupIn.ForcedServiceTier). SetFreeOpenaiFast(groupIn.FreeOpenAIFast). + SetFreeOpenaiUltrafast(groupIn.FreeOpenAIUltrafast). SetRequireOauthOnly(groupIn.RequireOAuthOnly). SetRequirePrivacySet(groupIn.RequirePrivacySet). SetDefaultMappedModel(groupIn.DefaultMappedModel). diff --git a/backend/internal/repository/migrations_schema_integration_test.go b/backend/internal/repository/migrations_schema_integration_test.go index 57f06fd2a8f3..8210b856c78d 100644 --- a/backend/internal/repository/migrations_schema_integration_test.go +++ b/backend/internal/repository/migrations_schema_integration_test.go @@ -59,6 +59,8 @@ func TestMigrationsRunner_IsIdempotent_AndSchemaIsUpToDate(t *testing.T) { requireColumn(t, tx, "groups", "allow_live", "boolean", 0, false) requireColumn(t, tx, "groups", "force_openai_fast", "boolean", 0, false) requireColumn(t, tx, "groups", "free_openai_fast", "boolean", 0, false) + requireColumn(t, tx, "groups", "forced_service_tier", "text", 0, false) + requireColumn(t, tx, "groups", "free_openai_ultrafast", "boolean", 0, false) // api_keys: key length should be 128 requireColumn(t, tx, "api_keys", "key", "character varying", 128, false) diff --git a/backend/internal/service/admin_group.go b/backend/internal/service/admin_group.go index 96c8092a8ec0..737b99c68072 100644 --- a/backend/internal/service/admin_group.go +++ b/backend/internal/service/admin_group.go @@ -359,11 +359,30 @@ func groupSupportsOpenAIFast(platform string) bool { } func sanitizeGroupOpenAIFast(group *Group) { - if group == nil || !groupSupportsOpenAIFast(group.Platform) { - if group != nil { - group.ForceOpenAIFast = false - group.FreeOpenAIFast = false - } + if group == nil { + return + } + if !groupSupportsOpenAIFast(group.Platform) { + group.ForcedServiceTier = "" + group.FreeOpenAIFast = false + group.FreeOpenAIUltrafast = false + return + } + group.ForcedServiceTier = normalizeGroupForcedServiceTierValue(group.ForcedServiceTier) +} + +// normalizeGroupForcedServiceTierValue maps the accepted override spellings to +// the canonical service_tier values. Unknown values fall back to follow-request. +func normalizeGroupForcedServiceTierValue(raw string) string { + switch strings.ToLower(strings.TrimSpace(raw)) { + case OpenAIFastTierDefault: + return OpenAIFastTierDefault + case OpenAIFastTierPriority, "fast": + return OpenAIFastTierPriority + case OpenAIFastTierUltrafast: + return OpenAIFastTierUltrafast + default: + return "" } } @@ -615,8 +634,9 @@ func (s *adminServiceImpl) CreateGroup(ctx context.Context, input *CreateGroupIn SupportedModelScopes: input.SupportedModelScopes, AllowMessagesDispatch: input.AllowMessagesDispatch, AllowLive: input.AllowLive, - ForceOpenAIFast: input.ForceOpenAIFast, + ForcedServiceTier: input.ForcedServiceTier, FreeOpenAIFast: input.FreeOpenAIFast, + FreeOpenAIUltrafast: input.FreeOpenAIUltrafast, RequireOAuthOnly: input.RequireOAuthOnly, RequirePrivacySet: input.RequirePrivacySet, DefaultMappedModel: input.DefaultMappedModel, @@ -1000,12 +1020,15 @@ func (s *adminServiceImpl) UpdateGroup(ctx context.Context, id int64, input *Upd if input.AllowLive != nil { group.AllowLive = *input.AllowLive } - if input.ForceOpenAIFast != nil { - group.ForceOpenAIFast = *input.ForceOpenAIFast + if input.ForcedServiceTier != nil { + group.ForcedServiceTier = *input.ForcedServiceTier } if input.FreeOpenAIFast != nil { group.FreeOpenAIFast = *input.FreeOpenAIFast } + if input.FreeOpenAIUltrafast != nil { + group.FreeOpenAIUltrafast = *input.FreeOpenAIUltrafast + } if input.RequireOAuthOnly != nil { group.RequireOAuthOnly = *input.RequireOAuthOnly } @@ -1059,7 +1082,7 @@ func (s *adminServiceImpl) UpdateGroup(ctx context.Context, id int64, input *Upd } sanitizeGroupReasoningEffortPolicy(group) // 固定账号 manifest 配置:按最终平台归一化(切出 openai 平台时静默归零, - // 与 ForceOpenAIFast 同一收口);校验仅在本次显式携带配置时进行, + // 与 ForcedServiceTier 同一收口);校验仅在本次显式携带配置时进行, // 避免脏 ID 阻塞无关字段更新。 group.CodexModelsManifestConfig = normalizeCodexModelsManifestConfig(group.Platform, group.CodexModelsManifestConfig) if input.CodexModelsManifestConfig != nil { diff --git a/backend/internal/service/admin_group_duplicate.go b/backend/internal/service/admin_group_duplicate.go index b31a4ab33733..15435d66b441 100644 --- a/backend/internal/service/admin_group_duplicate.go +++ b/backend/internal/service/admin_group_duplicate.go @@ -145,8 +145,9 @@ func cloneGroupForDuplicate(source *Group, operationID string) *Group { SortOrder: source.SortOrder, AllowMessagesDispatch: source.AllowMessagesDispatch, AllowLive: source.AllowLive, - ForceOpenAIFast: source.ForceOpenAIFast, + ForcedServiceTier: source.ForcedServiceTier, FreeOpenAIFast: source.FreeOpenAIFast, + FreeOpenAIUltrafast: source.FreeOpenAIUltrafast, RequireOAuthOnly: source.RequireOAuthOnly, RequirePrivacySet: source.RequirePrivacySet, DefaultMappedModel: source.DefaultMappedModel, diff --git a/backend/internal/service/admin_group_duplicate_test.go b/backend/internal/service/admin_group_duplicate_test.go index d991590a0649..fc75c81f3d8b 100644 --- a/backend/internal/service/admin_group_duplicate_test.go +++ b/backend/internal/service/admin_group_duplicate_test.go @@ -166,7 +166,7 @@ func TestDuplicateGroupCopiesConfigurationDeeplyAndResetsRuntimeState(t *testing SortOrder: 9, AllowMessagesDispatch: true, AllowLive: true, - ForceOpenAIFast: true, + ForcedServiceTier: OpenAIFastTierPriority, FreeOpenAIFast: true, RequireOAuthOnly: true, RequirePrivacySet: true, @@ -215,7 +215,7 @@ func TestDuplicateGroupCopiesConfigurationDeeplyAndResetsRuntimeState(t *testing require.Equal(t, source.FallbackGroupID, duplicate.FallbackGroupID) require.Equal(t, source.ModelRouting, duplicate.ModelRouting) require.Equal(t, source.MessagesDispatchModelConfig, duplicate.MessagesDispatchModelConfig) - require.Equal(t, source.ForceOpenAIFast, duplicate.ForceOpenAIFast) + require.Equal(t, source.ForcedServiceTier, duplicate.ForcedServiceTier) require.Equal(t, source.FreeOpenAIFast, duplicate.FreeOpenAIFast) require.Equal(t, source.ModelAllowlist, duplicate.ModelAllowlist) require.Equal(t, source.RPMLimit, duplicate.RPMLimit) diff --git a/backend/internal/service/admin_service.go b/backend/internal/service/admin_service.go index a5e0ad635c30..082b0d9502a7 100644 --- a/backend/internal/service/admin_service.go +++ b/backend/internal/service/admin_service.go @@ -288,8 +288,9 @@ type CreateGroupInput struct { // OpenAI Messages 调度配置(仅 openai 平台使用) AllowMessagesDispatch bool AllowLive bool - ForceOpenAIFast bool + ForcedServiceTier string FreeOpenAIFast bool + FreeOpenAIUltrafast bool DefaultMappedModel string RequireOAuthOnly bool RequirePrivacySet bool @@ -370,8 +371,9 @@ type UpdateGroupInput struct { // OpenAI Messages 调度配置(仅 openai 平台使用) AllowMessagesDispatch *bool AllowLive *bool - ForceOpenAIFast *bool + ForcedServiceTier *string FreeOpenAIFast *bool + FreeOpenAIUltrafast *bool DefaultMappedModel *string RequireOAuthOnly *bool RequirePrivacySet *bool diff --git a/backend/internal/service/admin_service_group_test.go b/backend/internal/service/admin_service_group_test.go index 56ef6d7e3d71..55c2a6f5bf4d 100644 --- a/backend/internal/service/admin_service_group_test.go +++ b/backend/internal/service/admin_service_group_test.go @@ -224,7 +224,7 @@ func TestAdminServiceSimpleModeNormalizesAllUnsupportedCreateFieldsDirectly(t *t ImagePrice1K: &one, VideoPrice720P: &one, WebSearchPricePerCall: &one, SearchPricePer1k: &one, AudioRealtimePricePerMin: &one, ClaudeCodeOnly: true, FallbackGroupID: &fallbackID, ModelRouting: map[string][]int64{"claude": {1}}, ModelRoutingEnabled: true, - AllowMessagesDispatch: true, AllowLive: true, ForceOpenAIFast: true, RequireOAuthOnly: true, + AllowMessagesDispatch: true, AllowLive: true, ForcedServiceTier: OpenAIFastTierPriority, RequireOAuthOnly: true, RPMLimit: 99, MaxReasoningEffort: "high", ProfitControlEnabled: true, ProfitMinMargin: &one, CopyAccountsFromGroupIDs: []int64{2}, } @@ -262,7 +262,7 @@ func TestAdminServiceSimpleModeNormalizesAllUnsupportedUpdateFieldsDirectly(t *t ImagePrice1K: &one, VideoPrice720P: &one, WebSearchPricePerCall: &one, SearchPricePer1k: &one, AudioRealtimePricePerMin: &one, ClaudeCodeOnly: &truth, FallbackGroupID: &fallbackID, ModelRouting: map[string][]int64{"claude": {1}}, ModelRoutingEnabled: &truth, - AllowMessagesDispatch: &truth, AllowLive: &truth, ForceOpenAIFast: &truth, RequireOAuthOnly: &truth, + AllowMessagesDispatch: &truth, AllowLive: &truth, ForcedServiceTier: ptrString(OpenAIFastTierPriority), RequireOAuthOnly: &truth, RPMLimit: new(int), MaxReasoningEffort: ptrString("high"), ProfitControlEnabled: &truth, ProfitMinMargin: &one, CopyAccountsFromGroupIDs: []int64{2}, } @@ -1340,31 +1340,31 @@ func TestAdminService_CreateGroup_NormalizesForceOpenAIFastByPlatform(t *testing for _, tt := range []struct { name string platform string - want bool + wantTier string }{ - {name: "openai", platform: PlatformOpenAI, want: true}, - {name: "composite", platform: PlatformComposite, want: true}, - {name: "anthropic", platform: PlatformAnthropic, want: false}, + {name: "openai", platform: PlatformOpenAI, wantTier: OpenAIFastTierPriority}, + {name: "composite", platform: PlatformComposite, wantTier: OpenAIFastTierPriority}, + {name: "anthropic", platform: PlatformAnthropic, wantTier: ""}, } { t.Run(tt.name, func(t *testing.T) { repo := &groupRepoStubForAdmin{} svc := &adminServiceImpl{groupRepo: repo} group, err := svc.CreateGroup(context.Background(), &CreateGroupInput{ - Name: "fast-" + tt.name, Platform: tt.platform, RateMultiplier: 1, ForceOpenAIFast: true, FreeOpenAIFast: true, + Name: "fast-" + tt.name, Platform: tt.platform, RateMultiplier: 1, ForcedServiceTier: OpenAIFastTierPriority, FreeOpenAIFast: true, }) require.NoError(t, err) require.NotNil(t, group) - require.Equal(t, tt.want, repo.created.ForceOpenAIFast) - require.Equal(t, tt.want, repo.created.FreeOpenAIFast) + require.Equal(t, tt.wantTier, repo.created.ForcedServiceTier) + require.Equal(t, tt.wantTier == OpenAIFastTierPriority, repo.created.FreeOpenAIFast) }) } } func TestAdminService_UpdateGroup_ClearsForceOpenAIFastWhenPlatformChanges(t *testing.T) { existingGroup := &Group{ - ID: 1, Name: "existing-fast", Platform: PlatformOpenAI, Status: StatusActive, ForceOpenAIFast: true, FreeOpenAIFast: true, + ID: 1, Name: "existing-fast", Platform: PlatformOpenAI, Status: StatusActive, ForcedServiceTier: OpenAIFastTierPriority, FreeOpenAIFast: true, } repo := &groupRepoStubForAdmin{getByID: existingGroup} svc := &adminServiceImpl{groupRepo: repo} @@ -1375,7 +1375,7 @@ func TestAdminService_UpdateGroup_ClearsForceOpenAIFastWhenPlatformChanges(t *te require.NoError(t, err) require.NotNil(t, group) - require.False(t, repo.updated.ForceOpenAIFast) + require.Empty(t, repo.updated.ForcedServiceTier) require.False(t, repo.updated.FreeOpenAIFast) } @@ -1389,13 +1389,13 @@ func TestAdminService_UpdateGroup_ForceOpenAIFastInvalidatesAuthCache(t *testing enabled := true group, err := svc.UpdateGroup(context.Background(), existingGroup.ID, &UpdateGroupInput{ - ForceOpenAIFast: &enabled, - FreeOpenAIFast: &enabled, + ForcedServiceTier: ptrString(OpenAIFastTierPriority), + FreeOpenAIFast: &enabled, }) require.NoError(t, err) require.NotNil(t, group) - require.True(t, repo.updated.ForceOpenAIFast) + require.Equal(t, OpenAIFastTierPriority, repo.updated.ForcedServiceTier) require.True(t, repo.updated.FreeOpenAIFast) require.Equal(t, []int64{existingGroup.ID}, invalidator.groupIDs) } diff --git a/backend/internal/service/api_key_auth_cache.go b/backend/internal/service/api_key_auth_cache.go index 474c0d85f1a2..aed379c97a76 100644 --- a/backend/internal/service/api_key_auth_cache.go +++ b/backend/internal/service/api_key_auth_cache.go @@ -103,8 +103,9 @@ type APIKeyAuthGroupSnapshot struct { // OpenAI Messages 调度配置(仅 openai 平台使用) AllowMessagesDispatch bool `json:"allow_messages_dispatch"` AllowLive bool `json:"allow_live"` - ForceOpenAIFast bool `json:"force_openai_fast"` + ForcedServiceTier string `json:"forced_service_tier"` FreeOpenAIFast bool `json:"free_openai_fast"` + FreeOpenAIUltrafast bool `json:"free_openai_ultrafast"` DefaultMappedModel string `json:"default_mapped_model,omitempty"` MessagesDispatchModelConfig OpenAIMessagesDispatchModelConfig `json:"messages_dispatch_model_config,omitempty"` ModelAllowlist GroupModelAllowlist `json:"model_allowlist,omitempty"` diff --git a/backend/internal/service/api_key_auth_cache_impl.go b/backend/internal/service/api_key_auth_cache_impl.go index 836edbb2b530..e04c3c546c0a 100644 --- a/backend/internal/service/api_key_auth_cache_impl.go +++ b/backend/internal/service/api_key_auth_cache_impl.go @@ -14,7 +14,7 @@ import ( "github.com/dgraph-io/ristretto" ) -const apiKeyAuthSnapshotVersion = 24 // v24: group model_allowlist field (renamed from models_list_config, enforcing semantics) +const apiKeyAuthSnapshotVersion = 25 // v25: group forced_service_tier and free_openai_ultrafast fields type apiKeyAuthCacheConfig struct { l1Size int @@ -418,8 +418,9 @@ func (s *APIKeyService) snapshotFromAPIKey(ctx context.Context, apiKey *APIKey) SupportedModelScopes: apiKey.Group.SupportedModelScopes, AllowMessagesDispatch: apiKey.Group.AllowMessagesDispatch, AllowLive: apiKey.Group.AllowLive, - ForceOpenAIFast: apiKey.Group.ForceOpenAIFast, + ForcedServiceTier: apiKey.Group.ForcedServiceTier, FreeOpenAIFast: apiKey.Group.FreeOpenAIFast, + FreeOpenAIUltrafast: apiKey.Group.FreeOpenAIUltrafast, DefaultMappedModel: apiKey.Group.DefaultMappedModel, MessagesDispatchModelConfig: apiKey.Group.MessagesDispatchModelConfig, ModelAllowlist: apiKey.Group.ModelAllowlist, @@ -520,8 +521,9 @@ func (s *APIKeyService) snapshotToAPIKey(key string, snapshot *APIKeyAuthSnapsho SupportedModelScopes: snapshot.Group.SupportedModelScopes, AllowMessagesDispatch: snapshot.Group.AllowMessagesDispatch, AllowLive: snapshot.Group.AllowLive, - ForceOpenAIFast: snapshot.Group.ForceOpenAIFast, + ForcedServiceTier: snapshot.Group.ForcedServiceTier, FreeOpenAIFast: snapshot.Group.FreeOpenAIFast, + FreeOpenAIUltrafast: snapshot.Group.FreeOpenAIUltrafast, DefaultMappedModel: snapshot.Group.DefaultMappedModel, MessagesDispatchModelConfig: snapshot.Group.MessagesDispatchModelConfig, ModelAllowlist: snapshot.Group.ModelAllowlist, diff --git a/backend/internal/service/api_key_auth_cache_openai_fast_test.go b/backend/internal/service/api_key_auth_cache_openai_fast_test.go index 2c39d5bca936..ac29d7f844e4 100644 --- a/backend/internal/service/api_key_auth_cache_openai_fast_test.go +++ b/backend/internal/service/api_key_auth_cache_openai_fast_test.go @@ -15,7 +15,7 @@ func TestAPIKeyAuthSnapshotGroupForceOpenAIFastRoundtrip(t *testing.T) { User: &User{ID: 40, Status: StatusActive}, Group: &Group{ ID: groupID, Name: "fast-roundtrip", Platform: PlatformOpenAI, Status: StatusActive, - Hydrated: true, ForceOpenAIFast: true, FreeOpenAIFast: true, + Hydrated: true, ForcedServiceTier: OpenAIFastTierPriority, FreeOpenAIFast: true, FreeOpenAIUltrafast: true, }, } svc := &APIKeyService{} @@ -30,7 +30,8 @@ func TestAPIKeyAuthSnapshotGroupForceOpenAIFastRoundtrip(t *testing.T) { require.True(t, used) require.NotNil(t, materialized.Group) require.True(t, materialized.Group.Hydrated) - require.True(t, materialized.Group.ForceOpenAIFast) + require.Equal(t, OpenAIFastTierPriority, materialized.Group.ForcedServiceTier) require.True(t, materialized.Group.FreeOpenAIFast) + require.True(t, materialized.Group.FreeOpenAIUltrafast) require.Equal(t, apiKeyAuthSnapshotVersion, cached.Snapshot.Version) } diff --git a/backend/internal/service/billing_service.go b/backend/internal/service/billing_service.go index 7341fb123dfb..96c33d477ccd 100644 --- a/backend/internal/service/billing_service.go +++ b/backend/internal/service/billing_service.go @@ -1942,7 +1942,7 @@ func (s *BillingService) applyModelSpecificPricingPolicyEx(model string, pricing return pricing } cloned := *pricing - if isOpenAIGPT6AstraModel(normalized) { + if isOpenAIGPT6AstraModel(normalized) || openai.IsGPT61SolModelSpelling(normalized) { cloned.UltrafastMultiplier = 6 } if needsOpus55FastMultiplier { diff --git a/backend/internal/service/billing_service_test.go b/backend/internal/service/billing_service_test.go index 45544508ec1c..c46858c8efb0 100644 --- a/backend/internal/service/billing_service_test.go +++ b/backend/internal/service/billing_service_test.go @@ -2155,6 +2155,23 @@ func TestAstraUltrafastPricingUsesSixTimesStandard(t *testing.T) { } } } + for _, svc := range []*BillingService{newTestBillingService(), NewBillingService(&config.Config{}, &PricingService{}), NewBillingService(&config.Config{}, catalog)} { + for _, model := range []string{"gpt-6.1-sol", "gpt-6.1-sol-max", "openai/gpt-6.1-sol"} { + for _, n := range []int{271999, 272000, 272001} { + tokens := UsageTokens{InputTokens: n - 3000, CacheReadTokens: 2000, CacheCreationTokens: 1000, OutputTokens: 500} + cost, err := svc.CalculateCostWithServiceTier(model, tokens, 1, "ultrafast") + require.NoError(t, err) + im, om := 1.0, 1.0 + if n > 272000 { + im, om = 2, 1.5 + } + require.InDelta(t, float64(n-3000)*12e-6*im, cost.InputCost, 1e-10) + require.InDelta(t, 2000*0.6e-6*im, cost.CacheReadCost, 1e-10) + require.InDelta(t, 1000*15e-6*im, cost.CacheCreationCost, 1e-10) + require.InDelta(t, 500*60e-6*om, cost.OutputCost, 1e-10) + } + } + } svc := newTestBillingService() for _, custom := range []float64{0, 1e-6} { fast := 3.0 diff --git a/backend/internal/service/group.go b/backend/internal/service/group.go index 0489fe35ccc9..cb5b77e6b04a 100644 --- a/backend/internal/service/group.go +++ b/backend/internal/service/group.go @@ -101,10 +101,11 @@ type Group struct { // OpenAI Messages 调度配置(仅 openai 平台使用) AllowMessagesDispatch bool AllowLive bool - ForceOpenAIFast bool // 强制 OpenAI 网关请求使用 service_tier=priority - FreeOpenAIFast bool // OpenAI Fast 请求按 Standard 价格向用户计费 - RequireOAuthOnly bool // 仅允许非 apikey 类型账号关联(OpenAI/Antigravity/Anthropic/Gemini) - RequirePrivacySet bool // 调度时仅允许 privacy 已成功设置的账号(OpenAI/Antigravity/Anthropic/Gemini) + ForcedServiceTier string // 强制 OpenAI 网关请求使用的 service_tier;空字符串表示跟随请求 + FreeOpenAIFast bool // OpenAI Fast 请求按 Standard 价格向用户计费 + FreeOpenAIUltrafast bool // OpenAI UltraFast 请求按 Standard 价格向用户计费 + RequireOAuthOnly bool // 仅允许非 apikey 类型账号关联(OpenAI/Antigravity/Anthropic/Gemini) + RequirePrivacySet bool // 调度时仅允许 privacy 已成功设置的账号(OpenAI/Antigravity/Anthropic/Gemini) DefaultMappedModel string MessagesDispatchModelConfig OpenAIMessagesDispatchModelConfig ModelAllowlist GroupModelAllowlist diff --git a/backend/internal/service/openai_fast_policy_test.go b/backend/internal/service/openai_fast_policy_test.go index 6a0a2080a5df..d207ba694a21 100644 --- a/backend/internal/service/openai_fast_policy_test.go +++ b/backend/internal/service/openai_fast_policy_test.go @@ -339,11 +339,46 @@ func TestApplyOpenAIFastPolicyToBody_ForcePriorityRewritesKnownTier(t *testing.T } } +func TestApplyOpenAIFastPolicyToBody_ForcedTierModes(t *testing.T) { + svc := newOpenAIGatewayServiceWithSettings(t, DefaultOpenAIFastPolicySettings()) + account := &Account{Platform: PlatformOpenAI, Type: AccountTypeOAuth} + for _, tt := range []struct { + name string + forced string + model string + body string + want string + wantGone bool + }{ + {name: "standard overrides ultrafast", forced: OpenAIFastTierDefault, model: "gpt-6.1-sol", body: `{"service_tier":"ultrafast"}`, want: OpenAIFastTierDefault}, + {name: "fast overrides standard", forced: OpenAIFastTierPriority, model: "gpt-6-astra", body: `{"service_tier":"default"}`, want: OpenAIFastTierPriority}, + {name: "ultrafast on astra", forced: OpenAIFastTierUltrafast, model: "gpt-6-astra", body: `{"service_tier":"priority"}`, want: OpenAIFastTierUltrafast}, + {name: "ultrafast on gpt-6.1-sol", forced: OpenAIFastTierUltrafast, model: "gpt-6.1-sol", body: `{}`, want: OpenAIFastTierUltrafast}, + {name: "ultrafast on gpt-5.6-sol", forced: OpenAIFastTierUltrafast, model: "gpt-5.6-sol", body: `{}`, want: OpenAIFastTierUltrafast}, + {name: "ultrafast unsupported model passes client tier", forced: OpenAIFastTierUltrafast, model: "gpt-6-luna", body: `{"service_tier":"flex"}`, want: OpenAIFastTierFlex}, + {name: "ultrafast unsupported model keeps missing", forced: OpenAIFastTierUltrafast, model: "gpt-5.4", body: `{}`, wantGone: true}, + {name: "follow keeps client tier", forced: "", model: "gpt-6-astra", body: `{"service_tier":"flex"}`, want: OpenAIFastTierFlex}, + } { + t.Run(tt.name, func(t *testing.T) { + ctx := context.WithValue(context.Background(), ctxkey.Group, &Group{ + ID: 7, Platform: PlatformOpenAI, Status: StatusActive, Hydrated: true, ForcedServiceTier: tt.forced, + }) + updated, err := svc.applyOpenAIFastPolicyToBody(ctx, account, tt.model, []byte(tt.body)) + require.NoError(t, err) + if tt.wantGone { + require.False(t, gjson.GetBytes(updated, "service_tier").Exists()) + return + } + require.Equal(t, tt.want, gjson.GetBytes(updated, "service_tier").String()) + }) + } +} + func TestApplyOpenAIFastPolicyToBody_GroupForceInjectsAndOverridesTier(t *testing.T) { svc := newOpenAIGatewayServiceWithSettings(t, DefaultOpenAIFastPolicySettings()) account := &Account{Platform: PlatformOpenAI, Type: AccountTypeOAuth} ctx := context.WithValue(context.Background(), ctxkey.Group, &Group{ - ID: 7, Platform: PlatformOpenAI, Status: StatusActive, Hydrated: true, ForceOpenAIFast: true, + ID: 7, Platform: PlatformOpenAI, Status: StatusActive, Hydrated: true, ForcedServiceTier: OpenAIFastTierPriority, }) for _, body := range [][]byte{ @@ -362,7 +397,7 @@ func TestApplyOpenAIFastPolicyToBody_GroupForceStillHonorsGlobalPolicy(t *testin svc := newOpenAIGatewayServiceWithSettings(t, openAIFastFilterPriorityPolicy()) account := &Account{Platform: PlatformOpenAI, Type: AccountTypeOAuth} ctx := context.WithValue(context.Background(), ctxkey.Group, &Group{ - ID: 7, Platform: PlatformOpenAI, Status: StatusActive, Hydrated: true, ForceOpenAIFast: true, + ID: 7, Platform: PlatformOpenAI, Status: StatusActive, Hydrated: true, ForcedServiceTier: OpenAIFastTierPriority, }) updated, err := svc.applyOpenAIFastPolicyToBody(ctx, account, "gpt-5.6-sol", []byte(`{"model":"gpt-5.6-sol"}`)) @@ -376,7 +411,7 @@ func TestApplyOpenAIFastPolicyToBody_GroupForceRequiresHydratedGroup(t *testing. account := &Account{Platform: PlatformOpenAI, Type: AccountTypeOAuth} body := []byte(`{"model":"gpt-5.6-sol"}`) ctx := context.WithValue(context.Background(), ctxkey.Group, &Group{ - ID: 7, Platform: PlatformOpenAI, Status: StatusActive, ForceOpenAIFast: true, + ID: 7, Platform: PlatformOpenAI, Status: StatusActive, ForcedServiceTier: OpenAIFastTierPriority, }) updated, err := svc.applyOpenAIFastPolicyToBody(ctx, account, "gpt-5.6-sol", body) @@ -388,7 +423,7 @@ func TestApplyOpenAIFastPolicyToBody_GroupForceOnlyTargetsOpenAIAccounts(t *test svc := newOpenAIGatewayServiceWithSettings(t, DefaultOpenAIFastPolicySettings()) body := []byte(`{"model":"grok-4.1"}`) ctx := context.WithValue(context.Background(), ctxkey.Group, &Group{ - ID: 7, Platform: PlatformComposite, Status: StatusActive, Hydrated: true, ForceOpenAIFast: true, + ID: 7, Platform: PlatformComposite, Status: StatusActive, Hydrated: true, ForcedServiceTier: OpenAIFastTierPriority, }) updated, err := svc.applyOpenAIFastPolicyToBody( @@ -405,7 +440,7 @@ func TestApplyOpenAIFastPolicyToBody_GroupForceRequiresSupportedGroupPlatform(t svc := newOpenAIGatewayServiceWithSettings(t, DefaultOpenAIFastPolicySettings()) body := []byte(`{"model":"gpt-5.6-sol"}`) ctx := context.WithValue(context.Background(), ctxkey.Group, &Group{ - ID: 7, Platform: PlatformAnthropic, Status: StatusActive, Hydrated: true, ForceOpenAIFast: true, + ID: 7, Platform: PlatformAnthropic, Status: StatusActive, Hydrated: true, ForcedServiceTier: OpenAIFastTierPriority, }) updated, err := svc.applyOpenAIFastPolicyToBody( diff --git a/backend/internal/service/openai_fast_policy_ws_test.go b/backend/internal/service/openai_fast_policy_ws_test.go index 7bc2d5f4dc48..6d9ca90b7274 100644 --- a/backend/internal/service/openai_fast_policy_ws_test.go +++ b/backend/internal/service/openai_fast_policy_ws_test.go @@ -122,11 +122,37 @@ func TestWSResponseCreate_ForcePriorityRewritesKnownTier(t *testing.T) { } } +func TestWSResponseCreate_ForcedTierModes(t *testing.T) { + svc := newOpenAIGatewayServiceWithSettings(t, DefaultOpenAIFastPolicySettings()) + account := &Account{Platform: PlatformOpenAI, Type: AccountTypeOAuth} + for _, tt := range []struct { + name string + forced string + model string + frame string + want string + }{ + {name: "standard", forced: OpenAIFastTierDefault, model: "gpt-6.1-sol", frame: `{"type":"response.create","service_tier":"ultrafast"}`, want: OpenAIFastTierDefault}, + {name: "ultrafast supported", forced: OpenAIFastTierUltrafast, model: "gpt-6.1-sol", frame: `{"type":"response.create"}`, want: OpenAIFastTierUltrafast}, + {name: "ultrafast passes unsupported model", forced: OpenAIFastTierUltrafast, model: "gpt-6-luna", frame: `{"type":"response.create","service_tier":"flex"}`, want: OpenAIFastTierFlex}, + } { + t.Run(tt.name, func(t *testing.T) { + ctx := context.WithValue(context.Background(), ctxkey.Group, &Group{ + ID: 7, Platform: PlatformOpenAI, Status: StatusActive, Hydrated: true, ForcedServiceTier: tt.forced, + }) + updated, blocked, err := svc.applyOpenAIFastPolicyToWSResponseCreate(ctx, account, tt.model, []byte(tt.frame)) + require.NoError(t, err) + require.Nil(t, blocked) + require.Equal(t, tt.want, gjson.GetBytes(updated, "service_tier").String()) + }) + } +} + func TestWSResponseCreate_GroupForceInjectsMissingTier(t *testing.T) { svc := newOpenAIGatewayServiceWithSettings(t, DefaultOpenAIFastPolicySettings()) account := &Account{Platform: PlatformOpenAI, Type: AccountTypeOAuth} ctx := context.WithValue(context.Background(), ctxkey.Group, &Group{ - ID: 7, Platform: PlatformOpenAI, Status: StatusActive, Hydrated: true, ForceOpenAIFast: true, + ID: 7, Platform: PlatformOpenAI, Status: StatusActive, Hydrated: true, ForcedServiceTier: OpenAIFastTierPriority, }) frame := []byte(`{"type":"response.create","model":"gpt-5.6-sol","input":[]}`) @@ -158,7 +184,7 @@ func TestWSResponseCreate_GroupForceDoesNotTouchOtherFrames(t *testing.T) { svc := newOpenAIGatewayServiceWithSettings(t, DefaultOpenAIFastPolicySettings()) account := &Account{Platform: PlatformOpenAI, Type: AccountTypeOAuth} ctx := context.WithValue(context.Background(), ctxkey.Group, &Group{ - ID: 7, Platform: PlatformOpenAI, Status: StatusActive, Hydrated: true, ForceOpenAIFast: true, + ID: 7, Platform: PlatformOpenAI, Status: StatusActive, Hydrated: true, ForcedServiceTier: OpenAIFastTierPriority, }) frame := []byte(`{"type":"response.cancel","model":"gpt-5.6-sol"}`) diff --git a/backend/internal/service/openai_gateway_forward.go b/backend/internal/service/openai_gateway_forward.go index 27aafd997da4..c4af583dbf36 100644 --- a/backend/internal/service/openai_gateway_forward.go +++ b/backend/internal/service/openai_gateway_forward.go @@ -647,10 +647,11 @@ func (s *OpenAIGatewayService) Forward(ctx context.Context, c *gin.Context, acco } rawTier := requestView.ServiceTier - if openAIGroupForcesFast(ctx, account) { - rawTier = OpenAIFastTierPriority - if requestView.ServiceTier != OpenAIFastTierPriority { - markPatchSet("service_tier", OpenAIFastTierPriority) + forcedTier := openAIGroupForcedServiceTier(ctx, account, upstreamModel) + if forcedTier != "" { + rawTier = forcedTier + if requestView.ServiceTier != forcedTier { + markPatchSet("service_tier", forcedTier) } } if rawTier != "" { diff --git a/backend/internal/service/openai_gateway_record_usage_test.go b/backend/internal/service/openai_gateway_record_usage_test.go index be4cf1850cf0..be74aad3383b 100644 --- a/backend/internal/service/openai_gateway_record_usage_test.go +++ b/backend/internal/service/openai_gateway_record_usage_test.go @@ -3258,12 +3258,12 @@ func TestOpenAIGatewayServiceRecordUsage_FreeOpenAIFastMissingPricingRecordsZero func TestGroupBillsOpenAIFastAtStandardRequiresOpenAIAccount(t *testing.T) { apiKey := &APIKey{Group: &Group{Platform: PlatformComposite, FreeOpenAIFast: true}} - require.True(t, groupBillsOpenAIFastAtStandard( + require.True(t, groupBillsOpenAITierAtStandard( apiKey, &Account{Platform: PlatformOpenAI, Type: AccountTypeOAuth}, "priority", )) - require.False(t, groupBillsOpenAIFastAtStandard( + require.False(t, groupBillsOpenAITierAtStandard( apiKey, &Account{Platform: PlatformGrok, Type: AccountTypeAPIKey}, "priority", diff --git a/backend/internal/service/openai_gateway_request_body.go b/backend/internal/service/openai_gateway_request_body.go index 52ca1d57b467..fa09deed0794 100644 --- a/backend/internal/service/openai_gateway_request_body.go +++ b/backend/internal/service/openai_gateway_request_body.go @@ -2043,12 +2043,37 @@ func openAIFastPolicySettingsFromContext(ctx context.Context) *OpenAIFastPolicyS return nil } -func openAIGroupForcesFast(ctx context.Context, account *Account) bool { +// openAIGroupForcedServiceTier returns the service tier this request's group +// forces, or "" when the group follows the client. Ultra Fast is returned only +// for models that advertise it; other models keep the client's tier. The value +// is validated when the group is saved; unknown values are ignored here. +func openAIGroupForcedServiceTier(ctx context.Context, account *Account, model string) string { if ctx == nil || account == nil || account.Platform != PlatformOpenAI { - return false + return "" } group, _ := ctx.Value(ctxkey.Group).(*Group) - return IsGroupContextValid(group) && groupSupportsOpenAIFast(group.Platform) && group.ForceOpenAIFast + if !IsGroupContextValid(group) || !groupSupportsOpenAIFast(group.Platform) { + return "" + } + tier := normalizedOpenAIServiceTierValue(group.ForcedServiceTier) + switch tier { + case OpenAIFastTierPriority, OpenAIFastTierDefault: + return tier + case OpenAIFastTierUltrafast: + if openAIModelSupportsForcedUltrafast(model) { + return tier + } + } + return "" +} + +// openAIModelSupportsForcedUltrafast reports whether the model advertises +// Ultrafast. Groups force the tier only for these models; other models keep +// whatever tier the client sent. +func openAIModelSupportsForcedUltrafast(model string) bool { + return isOpenAIGPT6AstraModel(model) || + openai.IsGPT61SolModelSpelling(model) || + getNormalizedCodexModel(model) == "gpt-5.6-sol" } // applyOpenAIFastPolicyToBody applies the OpenAI fast policy to a raw request @@ -2056,8 +2081,9 @@ func openAIGroupForcesFast(ctx context.Context, account *Account) bool { // action=block it returns (body, *OpenAIFastBlockedError). On pass it // normalizes the service_tier value (e.g. client alias "fast" → "priority"). // action=force_priority rewrites any matched known tier to "priority". Before -// the global policy is evaluated, a trusted request Group with -// ForceOpenAIFast enabled unconditionally sets service_tier to "priority". +// the global policy is evaluated, a trusted request Group with a forced tier +// writes that tier into the body; Ultra Fast is written only for models that +// advertise it. // The global policy remains authoritative and may still pass, filter, or block // that final value. // @@ -2070,10 +2096,11 @@ func (s *OpenAIGatewayService) applyOpenAIFastPolicyToBody(ctx context.Context, if len(body) == 0 { return body, nil } - if openAIGroupForcesFast(ctx, account) { - updated, err := sjson.SetBytes(body, "service_tier", OpenAIFastTierPriority) + forcedTier := openAIGroupForcedServiceTier(ctx, account, model) + if forcedTier != "" { + updated, err := sjson.SetBytes(body, "service_tier", forcedTier) if err != nil { - return body, fmt.Errorf("force group service_tier priority on body: %w", err) + return body, fmt.Errorf("force group service_tier %s on body: %w", forcedTier, err) } body = updated } @@ -2157,7 +2184,7 @@ func writeOpenAIFastPolicyBlockedResponse(c *gin.Context, err *OpenAIFastBlocked // - filter: returns a copy with top-level service_tier removed // - force_priority: keeps service_tier and rewrites it to "priority" // - block: returns (frame, *OpenAIFastBlockedError) -// - Group ForceOpenAIFast: sets priority first, then applies the global rule +// - Forced group tier: writes the group's tier first, then applies the global rule // // Only frames whose "type" field strictly equals "response.create" are // inspected/mutated. Any other frame type — including the empty string — @@ -2196,10 +2223,11 @@ func (s *OpenAIGatewayService) applyOpenAIFastPolicyToWSResponseCreate( if frameType != "response.create" { return frame, nil, nil } - if openAIGroupForcesFast(ctx, account) { - updated, err := sjson.SetBytes(frame, "service_tier", OpenAIFastTierPriority) + forcedTier := openAIGroupForcedServiceTier(ctx, account, model) + if forcedTier != "" { + updated, err := sjson.SetBytes(frame, "service_tier", forcedTier) if err != nil { - return frame, nil, fmt.Errorf("force group service_tier priority in ws frame: %w", err) + return frame, nil, fmt.Errorf("force group service_tier %s in ws frame: %w", forcedTier, err) } frame = updated } diff --git a/backend/internal/service/openai_gateway_usage.go b/backend/internal/service/openai_gateway_usage.go index ce2e276a7e15..d6b148e2f448 100644 --- a/backend/internal/service/openai_gateway_usage.go +++ b/backend/internal/service/openai_gateway_usage.go @@ -133,8 +133,8 @@ func openAIUsagePricingAt(input *OpenAIRecordUsageInput) time.Time { return timezone.Now() } -func groupBillsOpenAIFastAtStandard(apiKey *APIKey, account *Account, serviceTier string) bool { - if apiKey == nil || apiKey.Group == nil || !apiKey.Group.FreeOpenAIFast { +func groupBillsOpenAITierAtStandard(apiKey *APIKey, account *Account, serviceTier string) bool { + if apiKey == nil || apiKey.Group == nil { return false } if account == nil || !account.IsOpenAI() { @@ -145,7 +145,9 @@ func groupBillsOpenAIFastAtStandard(apiKey *APIKey, account *Account, serviceTie } switch normalizeBillingServiceTier(serviceTier) { case "priority", "fast": - return true + return apiKey.Group.FreeOpenAIFast + case OpenAIFastTierUltrafast: + return apiKey.Group.FreeOpenAIUltrafast default: return false } @@ -302,7 +304,7 @@ func (s *OpenAIGatewayService) RecordUsage(ctx context.Context, input *OpenAIRec // Free Fast changes only the customer charge. Keep priority TotalCost and // service_tier for upstream accounting, but evaluate ActualCost once more at // the Standard tier using the same channel, peak, and long-context policy. - if groupBillsOpenAIFastAtStandard(apiKey, billingAccount, serviceTier) { + if groupBillsOpenAITierAtStandard(apiKey, billingAccount, serviceTier) { standardCost, standardErr := s.calculateOpenAIRecordUsageCost( ctx, result, diff --git a/backend/internal/service/openai_gateway_usage_openai_fast_test.go b/backend/internal/service/openai_gateway_usage_openai_fast_test.go new file mode 100644 index 000000000000..409925f3e8f9 --- /dev/null +++ b/backend/internal/service/openai_gateway_usage_openai_fast_test.go @@ -0,0 +1,38 @@ +package service + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestGroupBillsOpenAITierAtStandard(t *testing.T) { + account := &Account{Platform: PlatformOpenAI} + for _, tt := range []struct { + name string + platform string + freeFast bool + freeUltrafast bool + tier string + want bool + }{ + {name: "free fast bills priority", platform: PlatformOpenAI, freeFast: true, tier: OpenAIFastTierPriority, want: true}, + {name: "free fast accepts fast alias", platform: PlatformOpenAI, freeFast: true, tier: "fast", want: true}, + {name: "free fast leaves ultrafast charged", platform: PlatformOpenAI, freeFast: true, tier: OpenAIFastTierUltrafast}, + {name: "free ultrafast bills ultrafast", platform: PlatformOpenAI, freeUltrafast: true, tier: OpenAIFastTierUltrafast, want: true}, + {name: "free ultrafast leaves fast charged", platform: PlatformOpenAI, freeUltrafast: true, tier: OpenAIFastTierPriority}, + {name: "both flags cover both tiers", platform: PlatformOpenAI, freeFast: true, freeUltrafast: true, tier: OpenAIFastTierUltrafast, want: true}, + {name: "standard is never rewritten", platform: PlatformOpenAI, freeFast: true, freeUltrafast: true, tier: OpenAIFastTierDefault}, + {name: "composite group is supported", platform: PlatformComposite, freeFast: true, tier: OpenAIFastTierPriority, want: true}, + } { + t.Run(tt.name, func(t *testing.T) { + apiKey := &APIKey{Group: &Group{ + Platform: tt.platform, + FreeOpenAIFast: tt.freeFast, + FreeOpenAIUltrafast: tt.freeUltrafast, + }} + require.Equal(t, tt.want, groupBillsOpenAITierAtStandard(apiKey, account, tt.tier)) + }) + } + require.False(t, groupBillsOpenAITierAtStandard(&APIKey{Group: &Group{Platform: PlatformOpenAI, FreeOpenAIFast: true}}, &Account{Platform: PlatformAnthropic}, OpenAIFastTierPriority)) +} diff --git a/backend/internal/service/openai_oauth_passthrough_test.go b/backend/internal/service/openai_oauth_passthrough_test.go index d871fcd901eb..217fb4d1dd1d 100644 --- a/backend/internal/service/openai_oauth_passthrough_test.go +++ b/backend/internal/service/openai_oauth_passthrough_test.go @@ -494,7 +494,7 @@ func TestOpenAIGatewayService_OAuthPassthrough_GroupForceOpenAIFastInjectsMissin RateMultiplier: f64p(1), } ctx := context.WithValue(context.Background(), ctxkey.Group, &Group{ - ID: 7, Platform: PlatformOpenAI, Status: StatusActive, Hydrated: true, ForceOpenAIFast: true, + ID: 7, Platform: PlatformOpenAI, Status: StatusActive, Hydrated: true, ForcedServiceTier: OpenAIFastTierPriority, }) result, err := svc.Forward(ctx, c, account, body) @@ -1041,7 +1041,7 @@ func TestOpenAIGatewayService_OAuthLegacy_GroupForceOpenAIFastInjectsMissingTier Status: StatusActive, Schedulable: true, RateMultiplier: f64p(1), } ctx := context.WithValue(context.Background(), ctxkey.Group, &Group{ - ID: 7, Platform: PlatformOpenAI, Status: StatusActive, Hydrated: true, ForceOpenAIFast: true, + ID: 7, Platform: PlatformOpenAI, Status: StatusActive, Hydrated: true, ForcedServiceTier: OpenAIFastTierPriority, }) result, err := svc.Forward(ctx, c, account, inputBody) @@ -1075,7 +1075,7 @@ func TestOpenAIGatewayService_OAuthLegacy_GroupForceStillHonorsGlobalFilter(t *t Status: StatusActive, Schedulable: true, RateMultiplier: f64p(1), } ctx := context.WithValue(context.Background(), ctxkey.Group, &Group{ - ID: 7, Platform: PlatformOpenAI, Status: StatusActive, Hydrated: true, ForceOpenAIFast: true, + ID: 7, Platform: PlatformOpenAI, Status: StatusActive, Hydrated: true, ForcedServiceTier: OpenAIFastTierPriority, }) result, err := svc.Forward(ctx, c, account, inputBody) diff --git a/backend/internal/service/settings_view.go b/backend/internal/service/settings_view.go index bf2e840baaff..e7333a011635 100644 --- a/backend/internal/service/settings_view.go +++ b/backend/internal/service/settings_view.go @@ -693,6 +693,7 @@ func DefaultBetaPolicySettings() *BetaPolicySettings { // anthropic-beta header 换成 body 的 service_tier 字段。 const ( OpenAIFastTierAny = "all" // 匹配任意已识别的 service_tier + OpenAIFastTierDefault = "default" // OpenAI 标准档位 OpenAIFastTierPriority = "priority" // 仅匹配 fast(priority) OpenAIFastTierUltrafast = "ultrafast" // 仅匹配 ultrafast OpenAIFastTierFlex = "flex" // 仅匹配 flex diff --git a/backend/migrations/246_group_forced_service_tier.sql b/backend/migrations/246_group_forced_service_tier.sql new file mode 100644 index 000000000000..ed36d96cf0ca --- /dev/null +++ b/backend/migrations/246_group_forced_service_tier.sql @@ -0,0 +1,19 @@ +-- Replace the boolean "force Fast" flag with an explicit service-tier override. +-- '' follows the client; 'default', 'priority' and 'ultrafast' force that tier. +ALTER TABLE groups + ADD COLUMN IF NOT EXISTS forced_service_tier TEXT NOT NULL DEFAULT ''; + +UPDATE groups +SET forced_service_tier = 'priority' +WHERE force_openai_fast + AND forced_service_tier = ''; + +COMMENT ON COLUMN groups.forced_service_tier IS + 'Forced OpenAI service_tier for this group; empty follows the request'; + +-- Independent operator subsidy for UltraFast billing. +ALTER TABLE groups + ADD COLUMN IF NOT EXISTS free_openai_ultrafast BOOLEAN NOT NULL DEFAULT FALSE; + +COMMENT ON COLUMN groups.free_openai_ultrafast IS + 'Bill this group''s UltraFast requests at Standard prices'; diff --git a/backend/migrations/group_forced_service_tier_migration_test.go b/backend/migrations/group_forced_service_tier_migration_test.go new file mode 100644 index 000000000000..1bb7ab2c382f --- /dev/null +++ b/backend/migrations/group_forced_service_tier_migration_test.go @@ -0,0 +1,19 @@ +package migrations + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestGroupForcedServiceTierMigration(t *testing.T) { + content, err := FS.ReadFile("246_group_forced_service_tier.sql") + require.NoError(t, err) + + sql := strings.Join(strings.Fields(string(content)), " ") + require.Contains(t, sql, "ADD COLUMN IF NOT EXISTS forced_service_tier TEXT NOT NULL DEFAULT ''") + require.Contains(t, sql, "ADD COLUMN IF NOT EXISTS free_openai_ultrafast BOOLEAN NOT NULL DEFAULT FALSE") + require.Contains(t, sql, "SET forced_service_tier = 'priority'") + require.Contains(t, sql, "WHERE force_openai_fast") +} diff --git a/frontend/src/i18n/locales/en/admin/overview.ts b/frontend/src/i18n/locales/en/admin/overview.ts index 1b79627bd016..a15232ca13f8 100644 --- a/frontend/src/i18n/locales/en/admin/overview.ts +++ b/frontend/src/i18n/locales/en/admin/overview.ts @@ -1269,11 +1269,17 @@ export default { enableAnyway: 'Enable anyway' }, openaiFast: { - title: 'OpenAI Fast mode', - force: 'Force Fast (priority)', - hint: 'Forces service_tier=priority on OpenAI requests in this group. The global Fast/Flex policy can still filter or block it. New requests update immediately after saving; existing WebSocket sessions must reconnect.', + title: 'OpenAI service tier', + tier: 'Handling', + tierFollow: 'Follow request', + tierStandard: 'Force Standard', + tierFast: 'Force Fast', + tierUltrafast: 'Force Ultra Fast', + hint: 'Follow request forwards the client service_tier unchanged. A forced tier overrides the request; the global Fast/Flex policy can still filter or block it. Forced Ultra Fast applies only to gpt-6-astra, gpt-6.1-sol and gpt-5.6-sol; other models pass through with the request value. New requests update immediately after saving; existing WebSocket sessions must reconnect.', free: 'Free Fast', - freeHint: 'Fast requests in this group still use the priority tier, but customers are charged the equivalent Standard price.' + freeHint: 'Fast requests in this group still use the priority tier, but customers are charged the equivalent Standard price.', + freeUltrafast: 'Free Ultra Fast', + freeUltrafastHint: 'Ultra Fast requests in this group still use the ultrafast tier, but customers are charged the Standard price.' }, invalidRequestFallback: { title: 'Invalid Request Fallback Group', diff --git a/frontend/src/i18n/locales/zh/admin/overview.ts b/frontend/src/i18n/locales/zh/admin/overview.ts index 9088319bc6c3..8a6436085930 100644 --- a/frontend/src/i18n/locales/zh/admin/overview.ts +++ b/frontend/src/i18n/locales/zh/admin/overview.ts @@ -1267,11 +1267,17 @@ export default { enableAnyway: '仍然开启' }, openaiFast: { - title: 'OpenAI Fast 模式', - force: '强制使用 Fast(priority)', - hint: '开启后,此分组的 OpenAI 请求会强制写入 service_tier=priority;全局 Fast/Flex 策略仍可过滤或拦截。保存后新请求立即生效,已建立的 WebSocket 会话需重连。', + title: 'OpenAI 服务档位', + tier: '处理方式', + tierFollow: '跟随请求', + tierStandard: '强制 Standard', + tierFast: '强制 Fast', + tierUltrafast: '强制 Ultra Fast', + hint: '跟随请求时按客户端传入的 service_tier 转发;选择强制档位会覆盖请求值,全局 Fast/Flex 策略仍可过滤或拦截。强制 Ultra Fast 仅对 gpt-6-astra、gpt-6.1-sol 与 gpt-5.6-sol 生效,其余模型按请求原样透传。保存后新请求立即生效,已建立的 WebSocket 会话需重连。', free: '免费 Fast', - freeHint: '该分组的 Fast 请求仍使用 priority 档位,但客户实际费用按同一请求的 Standard 价格计算。' + freeHint: '该分组的 Fast 请求仍使用 priority 档位,但客户实际费用按同一请求的 Standard 价格计算。', + freeUltrafast: '免费 Ultra Fast', + freeUltrafastHint: '该分组的 Ultra Fast 请求仍使用 ultrafast 档位,但客户实际费用按 Standard 价格计算。' }, invalidRequestFallback: { title: '无效请求兜底分组', diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts index 1eac972ce914..c16ae375e9e5 100644 --- a/frontend/src/types/index.ts +++ b/frontend/src/types/index.ts @@ -636,11 +636,15 @@ export interface Group { updated_at: string } +// Empty follows the client; the other values force that OpenAI tier. +export type ForcedServiceTier = '' | 'default' | 'priority' | 'ultrafast' + export interface AdminGroup extends Group { // 管理端的 name / description 始终是原字段,各语言译文另放在 i18n 里 i18n?: GroupI18n - force_openai_fast: boolean + forced_service_tier: ForcedServiceTier free_openai_fast: boolean + free_openai_ultrafast: boolean model_pricing: import('@/api/admin/channels').ChannelModelPricing[] // 分组利润控制(openai/anthropic/gemini/grok/antigravity 分组可启用;margin/buffer 为小数存储)。 // 仅管理员可见:与 rate_multiplier 相乘即可反推上游成本上限,不得下放到 Group。 @@ -814,8 +818,9 @@ export interface CreateGroupRequest { weekly_limit_usd?: number | null monthly_limit_usd?: number | null long_context_pricing_enabled?: boolean - force_openai_fast?: boolean + forced_service_tier?: ForcedServiceTier free_openai_fast?: boolean + free_openai_ultrafast?: boolean model_pricing?: import('@/api/admin/channels').ChannelModelPricing[] allow_image_generation?: boolean allow_batch_image_generation?: boolean @@ -881,8 +886,9 @@ export interface UpdateGroupRequest { weekly_limit_usd?: number | null monthly_limit_usd?: number | null long_context_pricing_enabled?: boolean - force_openai_fast?: boolean + forced_service_tier?: ForcedServiceTier free_openai_fast?: boolean + free_openai_ultrafast?: boolean model_pricing?: import('@/api/admin/channels').ChannelModelPricing[] allow_image_generation?: boolean allow_batch_image_generation?: boolean diff --git a/frontend/src/views/admin/GroupsView.vue b/frontend/src/views/admin/GroupsView.vue index 3fb2c95cd52b..be031248691b 100644 --- a/frontend/src/views/admin/GroupsView.vue +++ b/frontend/src/views/admin/GroupsView.vue @@ -1557,7 +1557,7 @@ - +
@@ -1591,6 +1592,19 @@
{{ t("admin.groups.openaiFast.freeHint") }}
++ {{ t("admin.groups.openaiFast.freeUltrafastHint") }} +
@@ -3241,6 +3256,19 @@
{{ t("admin.groups.openaiFast.freeHint") }}
++ {{ t("admin.groups.openaiFast.freeUltrafastHint") }} +