Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
陈曦
sub2api
Commits
f1f9640c
Commit
f1f9640c
authored
Apr 07, 2026
by
shaw
Committed by
陈曦
Apr 08, 2026
Browse files
feat: Beta策略支持按模型区分处理(模型白名单)
parent
b717956c
Changes
1
Hide whitespace changes
Inline
Side-by-side
backend/internal/service/gateway_service.go
View file @
f1f9640c
...
...
@@ -3955,7 +3955,7 @@ func (s *GatewayService) Forward(ctx context.Context, c *gin.Context, account *A
// Beta policy: evaluate once; block check + cache filter set for buildUpstreamRequest.
// Always overwrite the cache to prevent stale values from a previous retry with a different account.
if
account
.
Platform
==
PlatformAnthropic
&&
c
!=
nil
{
policy
:=
s
.
evaluateBetaPolicy
(
ctx
,
c
.
GetHeader
(
"anthropic-beta"
),
account
)
policy
:=
s
.
evaluateBetaPolicy
(
ctx
,
c
.
GetHeader
(
"anthropic-beta"
),
account
,
parsed
.
Model
)
if
policy
.
blockErr
!=
nil
{
return
nil
,
policy
.
blockErr
}
...
...
@@ -5617,7 +5617,7 @@ func (s *GatewayService) buildUpstreamRequest(ctx context.Context, c *gin.Contex
}
// Build effective drop set: merge static defaults with dynamic beta policy filter rules
policyFilterSet
:=
s
.
getBetaPolicyFilterSet
(
ctx
,
c
,
account
)
policyFilterSet
:=
s
.
getBetaPolicyFilterSet
(
ctx
,
c
,
account
,
modelID
)
effectiveDropSet
:=
mergeDropSets
(
policyFilterSet
)
effectiveDropWithClaudeCodeSet
:=
mergeDropSets
(
policyFilterSet
,
claude
.
BetaClaudeCode
)
...
...
@@ -5857,7 +5857,7 @@ type betaPolicyResult struct {
}
// evaluateBetaPolicy loads settings once and evaluates all rules against the given request.
func
(
s
*
GatewayService
)
evaluateBetaPolicy
(
ctx
context
.
Context
,
betaHeader
string
,
account
*
Account
)
betaPolicyResult
{
func
(
s
*
GatewayService
)
evaluateBetaPolicy
(
ctx
context
.
Context
,
betaHeader
string
,
account
*
Account
,
model
string
)
betaPolicyResult
{
if
s
.
settingService
==
nil
{
return
betaPolicyResult
{}
}
...
...
@@ -5872,10 +5872,11 @@ func (s *GatewayService) evaluateBetaPolicy(ctx context.Context, betaHeader stri
if
!
betaPolicyScopeMatches
(
rule
.
Scope
,
isOAuth
,
isBedrock
)
{
continue
}
switch
rule
.
Action
{
effectiveAction
,
effectiveErrMsg
:=
resolveRuleAction
(
rule
,
model
)
switch
effectiveAction
{
case
BetaPolicyActionBlock
:
if
result
.
blockErr
==
nil
&&
betaHeader
!=
""
&&
containsBetaToken
(
betaHeader
,
rule
.
BetaToken
)
{
msg
:=
rule
.
ErrorMessage
msg
:=
effectiveErrMsg
if
msg
==
""
{
msg
=
"beta feature "
+
rule
.
BetaToken
+
" is not allowed"
}
...
...
@@ -5917,7 +5918,7 @@ const betaPolicyFilterSetKey = "betaPolicyFilterSet"
// In the /v1/messages path, Forward() evaluates the policy first and caches the result;
// buildUpstreamRequest reuses it (zero extra DB calls). In the count_tokens path, this
// evaluates on demand (one DB call).
func
(
s
*
GatewayService
)
getBetaPolicyFilterSet
(
ctx
context
.
Context
,
c
*
gin
.
Context
,
account
*
Account
)
map
[
string
]
struct
{}
{
func
(
s
*
GatewayService
)
getBetaPolicyFilterSet
(
ctx
context
.
Context
,
c
*
gin
.
Context
,
account
*
Account
,
model
string
)
map
[
string
]
struct
{}
{
if
c
!=
nil
{
if
v
,
ok
:=
c
.
Get
(
betaPolicyFilterSetKey
);
ok
{
if
fs
,
ok
:=
v
.
(
map
[
string
]
struct
{});
ok
{
...
...
@@ -5925,7 +5926,7 @@ func (s *GatewayService) getBetaPolicyFilterSet(ctx context.Context, c *gin.Cont
}
}
}
return
s
.
evaluateBetaPolicy
(
ctx
,
""
,
account
)
.
filterSet
return
s
.
evaluateBetaPolicy
(
ctx
,
""
,
account
,
model
)
.
filterSet
}
// betaPolicyScopeMatches checks whether a rule's scope matches the current account type.
...
...
@@ -5944,6 +5945,33 @@ func betaPolicyScopeMatches(scope string, isOAuth bool, isBedrock bool) bool {
}
}
// matchModelWhitelist checks if a model matches any pattern in the whitelist.
// Reuses matchModelPattern from group.go which supports exact and wildcard prefix matching.
func
matchModelWhitelist
(
model
string
,
whitelist
[]
string
)
bool
{
for
_
,
pattern
:=
range
whitelist
{
if
matchModelPattern
(
pattern
,
model
)
{
return
true
}
}
return
false
}
// resolveRuleAction determines the effective action and error message for a rule given the request model.
// When ModelWhitelist is empty, the rule's primary Action/ErrorMessage applies unconditionally.
// When non-empty, Action applies to matching models; FallbackAction/FallbackErrorMessage applies to others.
func
resolveRuleAction
(
rule
BetaPolicyRule
,
model
string
)
(
action
,
errorMessage
string
)
{
if
len
(
rule
.
ModelWhitelist
)
==
0
{
return
rule
.
Action
,
rule
.
ErrorMessage
}
if
matchModelWhitelist
(
model
,
rule
.
ModelWhitelist
)
{
return
rule
.
Action
,
rule
.
ErrorMessage
}
if
rule
.
FallbackAction
!=
""
{
return
rule
.
FallbackAction
,
rule
.
FallbackErrorMessage
}
return
BetaPolicyActionPass
,
""
// default fallback: pass (fail-open)
}
// droppedBetaSet returns claude.DroppedBetas as a set, with optional extra tokens.
func
droppedBetaSet
(
extra
...
string
)
map
[
string
]
struct
{}
{
m
:=
make
(
map
[
string
]
struct
{},
len
(
defaultDroppedBetasSet
)
+
len
(
extra
))
...
...
@@ -5990,7 +6018,7 @@ func (s *GatewayService) resolveBedrockBetaTokensForRequest(
modelID
string
,
)
([]
string
,
error
)
{
// 1. 对原始 header 中的 beta token 做 block 检查(快速失败)
policy
:=
s
.
evaluateBetaPolicy
(
ctx
,
betaHeader
,
account
)
policy
:=
s
.
evaluateBetaPolicy
(
ctx
,
betaHeader
,
account
,
modelID
)
if
policy
.
blockErr
!=
nil
{
return
nil
,
policy
.
blockErr
}
...
...
@@ -6002,7 +6030,7 @@ func (s *GatewayService) resolveBedrockBetaTokensForRequest(
// 例如:管理员 block 了 interleaved-thinking,客户端不在 header 中带该 token,
// 但请求体中包含 thinking 字段 → autoInjectBedrockBetaTokens 会自动补齐 →
// 如果不做此检查,block 规则会被绕过。
if
blockErr
:=
s
.
checkBetaPolicyBlockForTokens
(
ctx
,
betaTokens
,
account
);
blockErr
!=
nil
{
if
blockErr
:=
s
.
checkBetaPolicyBlockForTokens
(
ctx
,
betaTokens
,
account
,
modelID
);
blockErr
!=
nil
{
return
nil
,
blockErr
}
...
...
@@ -6011,7 +6039,7 @@ func (s *GatewayService) resolveBedrockBetaTokensForRequest(
// checkBetaPolicyBlockForTokens 检查 token 列表中是否有被管理员 block 规则命中的 token。
// 用于补充 evaluateBetaPolicy 对 header 的检查,覆盖 body 自动注入的 token。
func
(
s
*
GatewayService
)
checkBetaPolicyBlockForTokens
(
ctx
context
.
Context
,
tokens
[]
string
,
account
*
Account
)
*
BetaBlockedError
{
func
(
s
*
GatewayService
)
checkBetaPolicyBlockForTokens
(
ctx
context
.
Context
,
tokens
[]
string
,
account
*
Account
,
model
string
)
*
BetaBlockedError
{
if
s
.
settingService
==
nil
||
len
(
tokens
)
==
0
{
return
nil
}
...
...
@@ -6023,14 +6051,15 @@ func (s *GatewayService) checkBetaPolicyBlockForTokens(ctx context.Context, toke
isBedrock
:=
account
.
IsBedrock
()
tokenSet
:=
buildBetaTokenSet
(
tokens
)
for
_
,
rule
:=
range
settings
.
Rules
{
if
rule
.
Action
!=
BetaPolicyActionBlock
{
effectiveAction
,
effectiveErrMsg
:=
resolveRuleAction
(
rule
,
model
)
if
effectiveAction
!=
BetaPolicyActionBlock
{
continue
}
if
!
betaPolicyScopeMatches
(
rule
.
Scope
,
isOAuth
,
isBedrock
)
{
continue
}
if
_
,
present
:=
tokenSet
[
rule
.
BetaToken
];
present
{
msg
:=
rule
.
ErrorMessage
msg
:=
effectiveErrMsg
if
msg
==
""
{
msg
=
"beta feature "
+
rule
.
BetaToken
+
" is not allowed"
}
...
...
@@ -8488,7 +8517,7 @@ func (s *GatewayService) buildCountTokensRequest(ctx context.Context, c *gin.Con
}
// Build effective drop set for count_tokens: merge static defaults with dynamic beta policy filter rules
ctEffectiveDropSet
:=
mergeDropSets
(
s
.
getBetaPolicyFilterSet
(
ctx
,
c
,
account
))
ctEffectiveDropSet
:=
mergeDropSets
(
s
.
getBetaPolicyFilterSet
(
ctx
,
c
,
account
,
modelID
))
// OAuth 账号:处理 anthropic-beta header
if
tokenType
==
"oauth"
{
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment