From e979990bf94516f10689c91c75f7b04a1f21f374 Mon Sep 17 00:00:00 2001 From: NellPoi Date: Tue, 7 Jul 2026 15:27:23 +0800 Subject: [PATCH] add force priority fast policy action --- .../service/openai_fast_policy_test.go | 23 ++++++++++++++++++- .../service/openai_fast_policy_ws_test.go | 21 +++++++++++++++++ .../service/openai_gateway_service.go | 21 +++++++++++++++-- backend/internal/service/setting_service.go | 1 + backend/internal/service/settings_view.go | 6 ++++- frontend/src/api/admin/settings.ts | 4 ++-- frontend/src/i18n/locales/en.ts | 1 + frontend/src/i18n/locales/zh.ts | 1 + frontend/src/views/admin/SettingsView.vue | 11 ++++++++- 9 files changed, 82 insertions(+), 7 deletions(-) diff --git a/backend/internal/service/openai_fast_policy_test.go b/backend/internal/service/openai_fast_policy_test.go index 70fcaffad7..d0be963e5e 100644 --- a/backend/internal/service/openai_fast_policy_test.go +++ b/backend/internal/service/openai_fast_policy_test.go @@ -179,6 +179,26 @@ func TestApplyOpenAIFastPolicyToBody_ExplicitFilterRemovesField(t *testing.T) { require.NotContains(t, string(updated), `"service_tier"`) } +func TestApplyOpenAIFastPolicyToBody_ForcePriorityRewritesKnownTier(t *testing.T) { + settings := &OpenAIFastPolicySettings{ + Rules: []OpenAIFastPolicyRule{{ + ServiceTier: OpenAIFastTierAny, + Action: OpenAIFastPolicyActionForcePriority, + Scope: BetaPolicyScopeAll, + }}, + } + svc := newOpenAIGatewayServiceWithSettings(t, settings) + account := &Account{Platform: PlatformOpenAI, Type: AccountTypeAPIKey} + + for _, tier := range []string{"flex", "auto", "default", "scale", "fast", "priority"} { + body := []byte(`{"model":"gpt-5.5","service_tier":"` + tier + `"}`) + updated, err := svc.applyOpenAIFastPolicyToBody(context.Background(), account, "gpt-5.5", body) + require.NoError(t, err) + require.Equal(t, OpenAIFastTierPriority, gjson.GetBytes(updated, "service_tier").String(), + "tier %q should be forced to priority", tier) + } +} + // TestApplyOpenAIFastPolicyToBody_OfficialTiersBypassDefaultRule 验证默认配置 // 下客户端显式发送的 OpenAI 官方合法 tier 能透传到上游而不被静默剥离。 func TestApplyOpenAIFastPolicyToBody_OfficialTiersBypassDefaultRule(t *testing.T) { @@ -293,7 +313,7 @@ func TestSetOpenAIFastPolicySettings_Validation(t *testing.T) { err = svc.SetOpenAIFastPolicySettings(context.Background(), &OpenAIFastPolicySettings{ Rules: []OpenAIFastPolicyRule{{ ServiceTier: OpenAIFastTierPriority, - Action: BetaPolicyActionFilter, + Action: OpenAIFastPolicyActionForcePriority, Scope: BetaPolicyScopeAll, }}, }) @@ -303,4 +323,5 @@ func TestSetOpenAIFastPolicySettings_Validation(t *testing.T) { require.NoError(t, err) require.Len(t, got.Rules, 1) require.Equal(t, OpenAIFastTierPriority, got.Rules[0].ServiceTier) + require.Equal(t, OpenAIFastPolicyActionForcePriority, got.Rules[0].Action) } diff --git a/backend/internal/service/openai_fast_policy_ws_test.go b/backend/internal/service/openai_fast_policy_ws_test.go index 4624e7a5c1..ac5b363cc0 100644 --- a/backend/internal/service/openai_fast_policy_ws_test.go +++ b/backend/internal/service/openai_fast_policy_ws_test.go @@ -67,6 +67,27 @@ func TestWSResponseCreate_ExplicitFilterStripsServiceTier(t *testing.T) { require.NotContains(t, string(updated), `"service_tier"`) } +func TestWSResponseCreate_ForcePriorityRewritesKnownTier(t *testing.T) { + settings := &OpenAIFastPolicySettings{ + Rules: []OpenAIFastPolicyRule{{ + ServiceTier: OpenAIFastTierAny, + Action: OpenAIFastPolicyActionForcePriority, + Scope: BetaPolicyScopeAll, + }}, + } + svc := newOpenAIGatewayServiceWithSettings(t, settings) + account := &Account{Platform: PlatformOpenAI, Type: AccountTypeAPIKey} + + for _, tier := range []string{"flex", "auto", "default", "scale", "fast", "priority"} { + frame := []byte(`{"type":"response.create","model":"gpt-5.5","service_tier":"` + tier + `"}`) + updated, blocked, err := svc.applyOpenAIFastPolicyToWSResponseCreate(context.Background(), account, "gpt-5.5", frame) + require.NoError(t, err) + require.Nil(t, blocked) + require.Equal(t, OpenAIFastTierPriority, gjson.GetBytes(updated, "service_tier").String(), + "tier %q should be forced to priority", tier) + } +} + func TestWSResponseCreate_FlexPassThrough(t *testing.T) { svc := newOpenAIGatewayServiceWithSettings(t, DefaultOpenAIFastPolicySettings()) account := &Account{Platform: PlatformOpenAI, Type: AccountTypeAPIKey} diff --git a/backend/internal/service/openai_gateway_service.go b/backend/internal/service/openai_gateway_service.go index fd104db8b6..de3192ae54 100644 --- a/backend/internal/service/openai_gateway_service.go +++ b/backend/internal/service/openai_gateway_service.go @@ -2973,6 +2973,10 @@ func (s *OpenAIGatewayService) Forward(ctx context.Context, c *gin.Context, acco return nil, blocked case BetaPolicyActionFilter: markPatchDelete("service_tier") + case OpenAIFastPolicyActionForcePriority: + if rawTier != OpenAIFastTierPriority { + markPatchSet("service_tier", OpenAIFastTierPriority) + } default: if normTier != rawTier { markPatchSet("service_tier", normTier) @@ -7365,8 +7369,8 @@ func openAIFastPolicySettingsFromContext(ctx context.Context) *OpenAIFastPolicyS // applyOpenAIFastPolicyToBody applies the OpenAI fast policy to a raw request // body. When action=filter it removes the service_tier field; when // action=block it returns (body, *OpenAIFastBlockedError). On pass it -// normalizes the service_tier value (e.g. client alias "fast" → "priority"), -// rewriting the body so the upstream receives a slug it recognizes. +// normalizes the service_tier value (e.g. client alias "fast" → "priority"). +// action=force_priority rewrites any matched known tier to "priority". // // Rationale for normalize-on-pass: chat-completions / messages 入口在调用本 // 函数之前已经通过 normalizeResponsesBodyServiceTier 把 service_tier 归一化 @@ -7399,6 +7403,12 @@ func (s *OpenAIGatewayService) applyOpenAIFastPolicyToBody(ctx context.Context, return body, fmt.Errorf("strip service_tier from body: %w", err) } return trimmed, nil + case OpenAIFastPolicyActionForcePriority: + updated, err := sjson.SetBytes(body, "service_tier", OpenAIFastTierPriority) + if err != nil { + return body, fmt.Errorf("force service_tier priority on body: %w", err) + } + return updated, nil default: // pass:把别名(如 "fast")写回为规范值("priority")。 if normTier == rawTier { @@ -7435,6 +7445,7 @@ func writeOpenAIFastPolicyBlockedResponse(c *gin.Context, err *OpenAIFastBlocked // // - pass: keeps service_tier, normalizing aliases such as "fast" to "priority" // - filter: returns a copy with top-level service_tier removed +// - force_priority: keeps service_tier and rewrites it to "priority" // - block: returns (frame, *OpenAIFastBlockedError) // // Only frames whose "type" field strictly equals "response.create" are @@ -7496,6 +7507,12 @@ func (s *OpenAIGatewayService) applyOpenAIFastPolicyToWSResponseCreate( return frame, nil, fmt.Errorf("strip service_tier from ws frame: %w", err) } return trimmed, nil, nil + case OpenAIFastPolicyActionForcePriority: + updated, err := sjson.SetBytes(frame, "service_tier", OpenAIFastTierPriority) + if err != nil { + return frame, nil, fmt.Errorf("force service_tier priority in ws frame: %w", err) + } + return updated, nil, nil default: if normTier == rawTier { return frame, nil, nil diff --git a/backend/internal/service/setting_service.go b/backend/internal/service/setting_service.go index 3aeb611418..2a6d821d3a 100644 --- a/backend/internal/service/setting_service.go +++ b/backend/internal/service/setting_service.go @@ -5336,6 +5336,7 @@ func (s *SettingService) SetOpenAIFastPolicySettings(ctx context.Context, settin validActions := map[string]bool{ BetaPolicyActionPass: true, BetaPolicyActionFilter: true, BetaPolicyActionBlock: true, + OpenAIFastPolicyActionForcePriority: true, } validScopes := map[string]bool{ BetaPolicyScopeAll: true, BetaPolicyScopeOAuth: true, BetaPolicyScopeAPIKey: true, BetaPolicyScopeBedrock: true, diff --git a/backend/internal/service/settings_view.go b/backend/internal/service/settings_view.go index 465760a445..9357c0c1e1 100644 --- a/backend/internal/service/settings_view.go +++ b/backend/internal/service/settings_view.go @@ -575,12 +575,16 @@ const ( OpenAIFastTierAny = "all" // 匹配任意已识别的 service_tier OpenAIFastTierPriority = "priority" // 仅匹配 fast(priority) OpenAIFastTierFlex = "flex" // 仅匹配 flex + + // OpenAIFastPolicyActionForcePriority 会保留 service_tier 字段并强制写成 + // priority,用于把 flex/auto/default/scale 等已识别 tier 收敛为 fast。 + OpenAIFastPolicyActionForcePriority = "force_priority" ) // OpenAIFastPolicyRule 单条 OpenAI fast/flex 策略规则 type OpenAIFastPolicyRule struct { ServiceTier string `json:"service_tier"` // "priority" | "flex" | "auto" | "default" | "scale" | "all" - Action string `json:"action"` // "pass" | "filter" | "block" + Action string `json:"action"` // "pass" | "filter" | "block" | "force_priority" Scope string `json:"scope"` // "all" | "oauth" | "apikey" | "bedrock" ErrorMessage string `json:"error_message,omitempty"` // 自定义错误消息 (action=block 时生效) ModelWhitelist []string `json:"model_whitelist,omitempty"` // 模型匹配模式列表(为空=对所有模型生效) diff --git a/frontend/src/api/admin/settings.ts b/frontend/src/api/admin/settings.ts index f5da990930..6f69163994 100644 --- a/frontend/src/api/admin/settings.ts +++ b/frontend/src/api/admin/settings.ts @@ -1273,11 +1273,11 @@ export async function updateRectifierSettings( */ export interface OpenAIFastPolicyRule { service_tier: "all" | "priority" | "flex"; - action: "pass" | "filter" | "block"; + action: "pass" | "filter" | "block" | "force_priority"; scope: "all" | "oauth" | "apikey" | "bedrock"; error_message?: string; model_whitelist?: string[]; - fallback_action?: "pass" | "filter" | "block"; + fallback_action?: "pass" | "filter" | "block" | "force_priority"; fallback_error_message?: string; } diff --git a/frontend/src/i18n/locales/en.ts b/frontend/src/i18n/locales/en.ts index 3a9fb5ad73..437cc89861 100644 --- a/frontend/src/i18n/locales/en.ts +++ b/frontend/src/i18n/locales/en.ts @@ -6686,6 +6686,7 @@ export default { action: 'Action', actionPass: 'Pass (keep service_tier)', actionFilter: 'Filter (remove service_tier)', + actionForcePriority: 'Force priority (fast)', actionBlock: 'Block (reject request)', scope: 'Scope', scopeAll: 'All accounts', diff --git a/frontend/src/i18n/locales/zh.ts b/frontend/src/i18n/locales/zh.ts index fdeaa86836..044ff714d3 100644 --- a/frontend/src/i18n/locales/zh.ts +++ b/frontend/src/i18n/locales/zh.ts @@ -6840,6 +6840,7 @@ export default { action: '处理方式', actionPass: '透传(保留 service_tier)', actionFilter: '过滤(移除 service_tier)', + actionForcePriority: '强制设置 priority(fast)', actionBlock: '拦截(拒绝请求)', scope: '生效范围', scopeAll: '全部账号', diff --git a/frontend/src/views/admin/SettingsView.vue b/frontend/src/views/admin/SettingsView.vue index c1072cddbb..71db9cd4b0 100644 --- a/frontend/src/views/admin/SettingsView.vue +++ b/frontend/src/views/admin/SettingsView.vue @@ -1158,7 +1158,11 @@