diff --git a/backend/internal/handler/admin/setting_handler.go b/backend/internal/handler/admin/setting_handler.go
index f57244fb..397526a7 100644
--- a/backend/internal/handler/admin/setting_handler.go
+++ b/backend/internal/handler/admin/setting_handler.go
@@ -1594,18 +1594,26 @@ func (h *SettingHandler) GetRectifierSettings(c *gin.Context) {
return
}
+ patterns := settings.APIKeySignaturePatterns
+ if patterns == nil {
+ patterns = []string{}
+ }
response.Success(c, dto.RectifierSettings{
Enabled: settings.Enabled,
ThinkingSignatureEnabled: settings.ThinkingSignatureEnabled,
ThinkingBudgetEnabled: settings.ThinkingBudgetEnabled,
+ APIKeySignatureEnabled: settings.APIKeySignatureEnabled,
+ APIKeySignaturePatterns: patterns,
})
}
// UpdateRectifierSettingsRequest 更新整流器配置请求
type UpdateRectifierSettingsRequest struct {
- Enabled bool `json:"enabled"`
- ThinkingSignatureEnabled bool `json:"thinking_signature_enabled"`
- ThinkingBudgetEnabled bool `json:"thinking_budget_enabled"`
+ Enabled bool `json:"enabled"`
+ ThinkingSignatureEnabled bool `json:"thinking_signature_enabled"`
+ ThinkingBudgetEnabled bool `json:"thinking_budget_enabled"`
+ APIKeySignatureEnabled bool `json:"apikey_signature_enabled"`
+ APIKeySignaturePatterns []string `json:"apikey_signature_patterns"`
}
// UpdateRectifierSettings 更新请求整流器配置
@@ -1617,10 +1625,32 @@ func (h *SettingHandler) UpdateRectifierSettings(c *gin.Context) {
return
}
+ // 校验并清理自定义匹配关键词
+ const maxPatterns = 50
+ const maxPatternLen = 500
+ if len(req.APIKeySignaturePatterns) > maxPatterns {
+ response.BadRequest(c, "Too many signature patterns (max 50)")
+ return
+ }
+ var cleanedPatterns []string
+ for _, p := range req.APIKeySignaturePatterns {
+ p = strings.TrimSpace(p)
+ if p == "" {
+ continue
+ }
+ if len(p) > maxPatternLen {
+ response.BadRequest(c, "Signature pattern too long (max 500 characters)")
+ return
+ }
+ cleanedPatterns = append(cleanedPatterns, p)
+ }
+
settings := &service.RectifierSettings{
Enabled: req.Enabled,
ThinkingSignatureEnabled: req.ThinkingSignatureEnabled,
ThinkingBudgetEnabled: req.ThinkingBudgetEnabled,
+ APIKeySignatureEnabled: req.APIKeySignatureEnabled,
+ APIKeySignaturePatterns: cleanedPatterns,
}
if err := h.settingService.SetRectifierSettings(c.Request.Context(), settings); err != nil {
@@ -1635,10 +1665,16 @@ func (h *SettingHandler) UpdateRectifierSettings(c *gin.Context) {
return
}
+ updatedPatterns := updatedSettings.APIKeySignaturePatterns
+ if updatedPatterns == nil {
+ updatedPatterns = []string{}
+ }
response.Success(c, dto.RectifierSettings{
Enabled: updatedSettings.Enabled,
ThinkingSignatureEnabled: updatedSettings.ThinkingSignatureEnabled,
ThinkingBudgetEnabled: updatedSettings.ThinkingBudgetEnabled,
+ APIKeySignatureEnabled: updatedSettings.APIKeySignatureEnabled,
+ APIKeySignaturePatterns: updatedPatterns,
})
}
diff --git a/backend/internal/handler/dto/settings.go b/backend/internal/handler/dto/settings.go
index 59d7f688..47bab091 100644
--- a/backend/internal/handler/dto/settings.go
+++ b/backend/internal/handler/dto/settings.go
@@ -188,9 +188,11 @@ type StreamTimeoutSettings struct {
// RectifierSettings 请求整流器配置 DTO
type RectifierSettings struct {
- Enabled bool `json:"enabled"`
- ThinkingSignatureEnabled bool `json:"thinking_signature_enabled"`
- ThinkingBudgetEnabled bool `json:"thinking_budget_enabled"`
+ Enabled bool `json:"enabled"`
+ ThinkingSignatureEnabled bool `json:"thinking_signature_enabled"`
+ ThinkingBudgetEnabled bool `json:"thinking_budget_enabled"`
+ APIKeySignatureEnabled bool `json:"apikey_signature_enabled"`
+ APIKeySignaturePatterns []string `json:"apikey_signature_patterns"`
}
// BetaPolicyRule Beta 策略规则 DTO
diff --git a/backend/internal/service/gateway_service.go b/backend/internal/service/gateway_service.go
index ae66ae4a..5de6dcae 100644
--- a/backend/internal/service/gateway_service.go
+++ b/backend/internal/service/gateway_service.go
@@ -4188,7 +4188,7 @@ func (s *GatewayService) Forward(ctx context.Context, c *gin.Context, account *A
if readErr == nil {
_ = resp.Body.Close()
- if s.isThinkingBlockSignatureError(respBody) && s.settingService.IsSignatureRectifierEnabled(ctx) {
+ if s.shouldRectifySignatureError(ctx, account, respBody) {
appendOpsUpstreamError(c, OpsUpstreamErrorEvent{
Platform: account.Platform,
AccountID: account.ID,
@@ -4243,7 +4243,7 @@ func (s *GatewayService) Forward(ctx context.Context, c *gin.Context, account *A
retryRespBody, retryReadErr := io.ReadAll(io.LimitReader(retryResp.Body, 2<<20))
_ = retryResp.Body.Close()
- if retryReadErr == nil && retryResp.StatusCode == 400 && s.isThinkingBlockSignatureError(retryRespBody) {
+ if retryReadErr == nil && retryResp.StatusCode == 400 && s.isSignatureErrorPattern(ctx, account, retryRespBody) {
appendOpsUpstreamError(c, OpsUpstreamErrorEvent{
Platform: account.Platform,
AccountID: account.ID,
@@ -6145,6 +6145,59 @@ func truncateForLog(b []byte, maxBytes int) string {
return s
}
+// shouldRectifySignatureError 统一判断是否应触发签名整流(strip thinking blocks 并重试)。
+// 根据账号类型检查对应的开关和匹配模式。
+func (s *GatewayService) shouldRectifySignatureError(ctx context.Context, account *Account, respBody []byte) bool {
+ if account.Type == AccountTypeAPIKey {
+ // API Key 账号:独立开关,一次读取配置
+ settings, err := s.settingService.GetRectifierSettings(ctx)
+ if err != nil || !settings.Enabled || !settings.APIKeySignatureEnabled {
+ return false
+ }
+ // 先检查内置模式(同 OAuth),再检查自定义关键词
+ if s.isThinkingBlockSignatureError(respBody) {
+ return true
+ }
+ return matchSignaturePatterns(respBody, settings.APIKeySignaturePatterns)
+ }
+ // OAuth/SetupToken/Upstream/Bedrock 等:保持原有行为(内置模式 + 原开关)
+ return s.isThinkingBlockSignatureError(respBody) && s.settingService.IsSignatureRectifierEnabled(ctx)
+}
+
+// isSignatureErrorPattern 仅做模式匹配,不检查开关。
+// 用于已进入重试流程后的二阶段检测(此时开关已在首次调用时验证过)。
+func (s *GatewayService) isSignatureErrorPattern(ctx context.Context, account *Account, respBody []byte) bool {
+ if s.isThinkingBlockSignatureError(respBody) {
+ return true
+ }
+ if account.Type == AccountTypeAPIKey {
+ settings, err := s.settingService.GetRectifierSettings(ctx)
+ if err != nil {
+ return false
+ }
+ return matchSignaturePatterns(respBody, settings.APIKeySignaturePatterns)
+ }
+ return false
+}
+
+// matchSignaturePatterns 检查响应体是否匹配自定义关键词列表(不区分大小写)。
+func matchSignaturePatterns(respBody []byte, patterns []string) bool {
+ if len(patterns) == 0 {
+ return false
+ }
+ bodyLower := strings.ToLower(string(respBody))
+ for _, p := range patterns {
+ p = strings.TrimSpace(p)
+ if p == "" {
+ continue
+ }
+ if strings.Contains(bodyLower, strings.ToLower(p)) {
+ return true
+ }
+ }
+ return false
+}
+
// isThinkingBlockSignatureError 检测是否是thinking block相关错误
// 这类错误可以通过过滤thinking blocks并重试来解决
func (s *GatewayService) isThinkingBlockSignatureError(respBody []byte) bool {
@@ -8013,7 +8066,7 @@ func (s *GatewayService) ForwardCountTokens(ctx context.Context, c *gin.Context,
}
// 检测 thinking block 签名错误(400)并重试一次(过滤 thinking blocks)
- if resp.StatusCode == 400 && s.isThinkingBlockSignatureError(respBody) && s.settingService.IsSignatureRectifierEnabled(ctx) {
+ if resp.StatusCode == 400 && s.shouldRectifySignatureError(ctx, account, respBody) {
logger.LegacyPrintf("service.gateway", "Account %d: detected thinking block signature error on count_tokens, retrying with filtered thinking blocks", account.ID)
filteredBody := FilterThinkingBlocksForRetry(body)
diff --git a/backend/internal/service/settings_view.go b/backend/internal/service/settings_view.go
index 4e29dba5..411939bb 100644
--- a/backend/internal/service/settings_view.go
+++ b/backend/internal/service/settings_view.go
@@ -190,9 +190,11 @@ func DefaultStreamTimeoutSettings() *StreamTimeoutSettings {
// RectifierSettings 请求整流器配置
type RectifierSettings struct {
- Enabled bool `json:"enabled"` // 总开关
- ThinkingSignatureEnabled bool `json:"thinking_signature_enabled"` // Thinking 签名整流
- ThinkingBudgetEnabled bool `json:"thinking_budget_enabled"` // Thinking Budget 整流
+ Enabled bool `json:"enabled"` // 总开关
+ ThinkingSignatureEnabled bool `json:"thinking_signature_enabled"` // Thinking 签名整流
+ ThinkingBudgetEnabled bool `json:"thinking_budget_enabled"` // Thinking Budget 整流
+ APIKeySignatureEnabled bool `json:"apikey_signature_enabled"` // API Key 签名整流开关
+ APIKeySignaturePatterns []string `json:"apikey_signature_patterns"` // API Key 自定义匹配关键词
}
// DefaultRectifierSettings 返回默认的整流器配置(全部启用)
diff --git a/frontend/src/api/admin/settings.ts b/frontend/src/api/admin/settings.ts
index 196e3788..cabdd5aa 100644
--- a/frontend/src/api/admin/settings.ts
+++ b/frontend/src/api/admin/settings.ts
@@ -323,6 +323,8 @@ export interface RectifierSettings {
enabled: boolean
thinking_signature_enabled: boolean
thinking_budget_enabled: boolean
+ apikey_signature_enabled: boolean
+ apikey_signature_patterns: string[]
}
/**
diff --git a/frontend/src/i18n/locales/en.ts b/frontend/src/i18n/locales/en.ts
index 42f58a77..54d757bb 100644
--- a/frontend/src/i18n/locales/en.ts
+++ b/frontend/src/i18n/locales/en.ts
@@ -4473,6 +4473,14 @@ export default {
thinkingSignatureHint: 'Automatically strip signatures and retry when upstream returns thinking block signature validation errors',
thinkingBudget: 'Thinking Budget Rectifier',
thinkingBudgetHint: 'Automatically set budget to 32000 and retry when upstream returns budget_tokens constraint error (≥1024)',
+ apikeySignature: 'API Key Signature Rectifier',
+ apikeySignatureHint:
+ 'Automatically strip signatures and retry when API Key accounts receive signature-related errors (built-in patterns always apply)',
+ apikeyPatterns: 'Custom Match Patterns',
+ apikeyPatternsHint:
+ 'Additional keywords matched against the response body (case-insensitive). Built-in patterns always apply; use these for supplementary matching.',
+ apikeyPatternPlaceholder: 'e.g., thinking_error',
+ addPattern: 'Add Pattern',
saved: 'Rectifier settings saved',
saveFailed: 'Failed to save rectifier settings'
},
diff --git a/frontend/src/i18n/locales/zh.ts b/frontend/src/i18n/locales/zh.ts
index 7ca78373..ac75188d 100644
--- a/frontend/src/i18n/locales/zh.ts
+++ b/frontend/src/i18n/locales/zh.ts
@@ -4637,6 +4637,14 @@ export default {
thinkingSignatureHint: '当上游返回 thinking block 签名校验错误时,自动去除签名并重试',
thinkingBudget: 'Thinking Budget 整流',
thinkingBudgetHint: '当上游返回 budget_tokens 约束错误(≥1024)时,自动将 budget 设为 32000 并重试',
+ apikeySignature: 'API Key 签名整流',
+ apikeySignatureHint:
+ '当 API Key 账号的上游返回签名相关错误时,自动去除签名并重试(内置规则始终生效)',
+ apikeyPatterns: '自定义匹配关键词',
+ apikeyPatternsHint:
+ '额外的关键词,匹配响应体中的内容(不区分大小写)。内置规则始终生效,此处用于补充额外匹配。',
+ apikeyPatternPlaceholder: '例如:thinking_error 或 签名无效',
+ addPattern: '添加关键词',
saved: '整流器设置保存成功',
saveFailed: '保存整流器设置失败'
},
diff --git a/frontend/src/views/admin/SettingsView.vue b/frontend/src/views/admin/SettingsView.vue
index 0e510aa9..198d484b 100644
--- a/frontend/src/views/admin/SettingsView.vue
+++ b/frontend/src/views/admin/SettingsView.vue
@@ -454,6 +454,72 @@
+ {{ t('admin.settings.rectifier.apikeySignatureHint') }} +
++ {{ t('admin.settings.rectifier.apikeyPatternsHint') }} +
+