@@ -1228,6 +1228,10 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro
modelScopeSkippedIDs = append ( modelScopeSkippedIDs , account . ID )
continue
}
// 配额检查
if ! s . isAccountSchedulableForQuota ( account ) {
continue
}
// 窗口费用检查(非粘性会话路径)
if ! s . isAccountSchedulableForWindowCost ( ctx , account , false ) {
filteredWindowCost ++
@@ -1260,6 +1264,7 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro
s . isAccountAllowedForPlatform ( stickyAccount , platform , useMixed ) &&
( requestedModel == "" || s . isModelSupportedByAccountWithContext ( ctx , stickyAccount , requestedModel ) ) &&
s . isAccountSchedulableForModelSelection ( ctx , stickyAccount , requestedModel ) &&
s . isAccountSchedulableForQuota ( stickyAccount ) &&
s . isAccountSchedulableForWindowCost ( ctx , stickyAccount , true ) &&
s . isAccountSchedulableForRPM ( ctx , stickyAccount , true ) { // 粘性会话窗口费用+RPM 检查
@@ -1416,6 +1421,7 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro
s . isAccountAllowedForPlatform ( account , platform , useMixed ) &&
( requestedModel == "" || s . isModelSupportedByAccountWithContext ( ctx , account , requestedModel ) ) &&
s . isAccountSchedulableForModelSelection ( ctx , account , requestedModel ) &&
s . isAccountSchedulableForQuota ( account ) &&
s . isAccountSchedulableForWindowCost ( ctx , account , true ) &&
s . isAccountSchedulableForRPM ( ctx , account , true ) { // 粘性会话窗口费用+RPM 检查
@@ -1480,6 +1486,10 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro
if ! s . isAccountSchedulableForModelSelection ( ctx , acc , requestedModel ) {
continue
}
// 配额检查
if ! s . isAccountSchedulableForQuota ( acc ) {
continue
}
// 窗口费用检查(非粘性会话路径)
if ! s . isAccountSchedulableForWindowCost ( ctx , acc , false ) {
continue
@@ -2113,6 +2123,15 @@ func (s *GatewayService) withWindowCostPrefetch(ctx context.Context, accounts []
return context . WithValue ( ctx , windowCostPrefetchContextKey , costs )
}
// isAccountSchedulableForQuota 检查 API Key 账号是否在配额限制内
// 仅适用于配置了 quota_limit 的 apikey 类型账号
func ( s * GatewayService ) isAccountSchedulableForQuota ( account * Account ) bool {
if account . Type != AccountTypeAPIKey {
return true
}
return ! account . IsQuotaExceeded ( )
}
// isAccountSchedulableForWindowCost 检查账号是否可根据窗口费用进行调度
// 仅适用于 Anthropic OAuth/SetupToken 账号
// 返回 true 表示可调度, false 表示不可调度
@@ -2590,7 +2609,7 @@ func (s *GatewayService) selectAccountForModelWithPlatform(ctx context.Context,
if clearSticky {
_ = s . cache . DeleteSessionAccountID ( ctx , derefGroupID ( groupID ) , sessionHash )
}
if ! clearSticky && s . isAccountInGroup ( account , groupID ) && account . Platform == platform && ( requestedModel == "" || s . isModelSupportedByAccountWithContext ( ctx , account , requestedModel ) ) && s . isAccountSchedulableForModelSelection ( ctx , account , requestedModel ) && s . isAccountSchedulableForWindowCost ( ctx , account , true ) && s . isAccountSchedulableForRPM ( ctx , account , true ) {
if ! clearSticky && s . isAccountInGroup ( account , groupID ) && account . Platform == platform && ( requestedModel == "" || s . isModelSupportedByAccountWithContext ( ctx , account , requestedModel ) ) && s . isAccountSchedulableForModelSelection ( ctx , account , requestedModel ) && s . isAccountSchedulableForQuota ( account ) && s . isAccountSchedulableForWindowCost( ctx , account , true ) && s . isAccountSchedulableForRPM ( ctx , account , true ) {
if s . debugModelRoutingEnabled ( ) {
logger . LegacyPrintf ( "service.gateway" , "[ModelRoutingDebug] legacy routed sticky hit: group_id=%v model=%s session=%s account=%d" , derefGroupID ( groupID ) , requestedModel , shortSessionHash ( sessionHash ) , accountID )
}
@@ -2644,6 +2663,9 @@ func (s *GatewayService) selectAccountForModelWithPlatform(ctx context.Context,
if ! s . isAccountSchedulableForModelSelection ( ctx , acc , requestedModel ) {
continue
}
if ! s . isAccountSchedulableForQuota ( acc ) {
continue
}
if ! s . isAccountSchedulableForWindowCost ( ctx , acc , false ) {
continue
}
@@ -2700,7 +2722,7 @@ func (s *GatewayService) selectAccountForModelWithPlatform(ctx context.Context,
if clearSticky {
_ = s . cache . DeleteSessionAccountID ( ctx , derefGroupID ( groupID ) , sessionHash )
}
if ! clearSticky && s . isAccountInGroup ( account , groupID ) && account . Platform == platform && ( requestedModel == "" || s . isModelSupportedByAccountWithContext ( ctx , account , requestedModel ) ) && s . isAccountSchedulableForModelSelection ( ctx , account , requestedModel ) && s . isAccountSchedulableForWindowCost ( ctx , account , true ) && s . isAccountSchedulableForRPM ( ctx , account , true ) {
if ! clearSticky && s . isAccountInGroup ( account , groupID ) && account . Platform == platform && ( requestedModel == "" || s . isModelSupportedByAccountWithContext ( ctx , account , requestedModel ) ) && s . isAccountSchedulableForModelSelection ( ctx , account , requestedModel ) && s . isAccountSchedulableForQuota ( account ) && s . isAccountSchedulableForWindowCost( ctx , account , true ) && s . isAccountSchedulableForRPM ( ctx , account , true ) {
return account , nil
}
}
@@ -2743,6 +2765,9 @@ func (s *GatewayService) selectAccountForModelWithPlatform(ctx context.Context,
if ! s . isAccountSchedulableForModelSelection ( ctx , acc , requestedModel ) {
continue
}
if ! s . isAccountSchedulableForQuota ( acc ) {
continue
}
if ! s . isAccountSchedulableForWindowCost ( ctx , acc , false ) {
continue
}
@@ -2818,7 +2843,7 @@ func (s *GatewayService) selectAccountWithMixedScheduling(ctx context.Context, g
if clearSticky {
_ = s . cache . DeleteSessionAccountID ( ctx , derefGroupID ( groupID ) , sessionHash )
}
if ! clearSticky && s . isAccountInGroup ( account , groupID ) && ( requestedModel == "" || s . isModelSupportedByAccountWithContext ( ctx , account , requestedModel ) ) && s . isAccountSchedulableForModelSelection ( ctx , account , requestedModel ) && s . isAccountSchedulableForWindowCost ( ctx , account , true ) && s . isAccountSchedulableForRPM ( ctx , account , true ) {
if ! clearSticky && s . isAccountInGroup ( account , groupID ) && ( requestedModel == "" || s . isModelSupportedByAccountWithContext ( ctx , account , requestedModel ) ) && s . isAccountSchedulableForModelSelection ( ctx , account , requestedModel ) && s . isAccountSchedulableForQuota ( account ) && s . isAccountSchedulableForWindowCost( ctx , account , true ) && s . isAccountSchedulableForRPM ( ctx , account , true ) {
if account . Platform == nativePlatform || ( account . Platform == PlatformAntigravity && account . IsMixedSchedulingEnabled ( ) ) {
if s . debugModelRoutingEnabled ( ) {
logger . LegacyPrintf ( "service.gateway" , "[ModelRoutingDebug] legacy mixed routed sticky hit: group_id=%v model=%s session=%s account=%d" , derefGroupID ( groupID ) , requestedModel , shortSessionHash ( sessionHash ) , accountID )
@@ -2874,6 +2899,9 @@ func (s *GatewayService) selectAccountWithMixedScheduling(ctx context.Context, g
if ! s . isAccountSchedulableForModelSelection ( ctx , acc , requestedModel ) {
continue
}
if ! s . isAccountSchedulableForQuota ( acc ) {
continue
}
if ! s . isAccountSchedulableForWindowCost ( ctx , acc , false ) {
continue
}
@@ -2930,7 +2958,7 @@ func (s *GatewayService) selectAccountWithMixedScheduling(ctx context.Context, g
if clearSticky {
_ = s . cache . DeleteSessionAccountID ( ctx , derefGroupID ( groupID ) , sessionHash )
}
if ! clearSticky && s . isAccountInGroup ( account , groupID ) && ( requestedModel == "" || s . isModelSupportedByAccountWithContext ( ctx , account , requestedModel ) ) && s . isAccountSchedulableForModelSelection ( ctx , account , requestedModel ) && s . isAccountSchedulableForWindowCost ( ctx , account , true ) && s . isAccountSchedulableForRPM ( ctx , account , true ) {
if ! clearSticky && s . isAccountInGroup ( account , groupID ) && ( requestedModel == "" || s . isModelSupportedByAccountWithContext ( ctx , account , requestedModel ) ) && s . isAccountSchedulableForModelSelection ( ctx , account , requestedModel ) && s . isAccountSchedulableForQuota ( account ) && s . isAccountSchedulableForWindowCost( ctx , account , true ) && s . isAccountSchedulableForRPM ( ctx , account , true ) {
if account . Platform == nativePlatform || ( account . Platform == PlatformAntigravity && account . IsMixedSchedulingEnabled ( ) ) {
return account , nil
}
@@ -2975,6 +3003,9 @@ func (s *GatewayService) selectAccountWithMixedScheduling(ctx context.Context, g
if ! s . isAccountSchedulableForModelSelection ( ctx , acc , requestedModel ) {
continue
}
if ! s . isAccountSchedulableForQuota ( acc ) {
continue
}
if ! s . isAccountSchedulableForWindowCost ( ctx , acc , false ) {
continue
}
@@ -6578,6 +6609,13 @@ func (s *GatewayService) RecordUsage(ctx context.Context, input *RecordUsageInpu
s . billingCacheService . QueueUpdateAPIKeyRateLimitUsage ( apiKey . ID , cost . ActualCost )
}
// 更新 API Key 账号配额用量
if shouldBill && cost . TotalCost > 0 && account . Type == AccountTypeAPIKey && account . GetQuotaLimit ( ) > 0 {
if err := s . accountRepo . IncrementQuotaUsed ( ctx , account . ID , cost . TotalCost ) ; err != nil {
slog . Error ( "increment account quota used failed" , "account_id" , account . ID , "cost" , cost . TotalCost , "error" , err )
}
}
// Schedule batch update for account last_used_at
s . deferredService . ScheduleLastUsedUpdate ( account . ID )
@@ -6775,6 +6813,13 @@ func (s *GatewayService) RecordUsageWithLongContext(ctx context.Context, input *
s . billingCacheService . QueueUpdateAPIKeyRateLimitUsage ( apiKey . ID , cost . ActualCost )
}
// 更新 API Key 账号配额用量
if shouldBill && cost . TotalCost > 0 && account . Type == AccountTypeAPIKey && account . GetQuotaLimit ( ) > 0 {
if err := s . accountRepo . IncrementQuotaUsed ( ctx , account . ID , cost . TotalCost ) ; err != nil {
slog . Error ( "increment account quota used failed" , "account_id" , account . ID , "cost" , cost . TotalCost , "error" , err )
}
}
// Schedule batch update for account last_used_at
s . deferredService . ScheduleLastUsedUpdate ( account . ID )