Sub2API v1.0 - AI API 网关(二开初始版本,基于上游 Wei-Shaw/sub2api)
Release / update-version (push) Has been cancelled
Release / build-frontend (push) Has been cancelled
Release / release (push) Has been cancelled
Release / sync-version-file (push) Has been cancelled
CI / shell (push) Canceled after 0s
CI / test (push) Canceled after 0s
CI / frontend (push) Canceled after 0s
CI / golangci-lint (push) Canceled after 0s
Security Scan / backend-security (push) Canceled after 0s
Security Scan / frontend-security (push) Canceled after 0s
Release / update-version (push) Has been cancelled
Release / build-frontend (push) Has been cancelled
Release / release (push) Has been cancelled
Release / sync-version-file (push) Has been cancelled
CI / shell (push) Canceled after 0s
CI / test (push) Canceled after 0s
CI / frontend (push) Canceled after 0s
CI / golangci-lint (push) Canceled after 0s
Security Scan / backend-security (push) Canceled after 0s
Security Scan / frontend-security (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/Wei-Shaw/sub2api/internal/config"
|
||||
)
|
||||
|
||||
// ModelAvailabilityDiagnosis describes whether the requested model can be
|
||||
// served by any persistently eligible account in the group (active with its
|
||||
// schedulable setting enabled), ignoring transient state such as rate limits,
|
||||
// overload, temporary unschedulability, and runtime blocks. Handlers use this
|
||||
// on the "no available accounts" error path to distinguish 404
|
||||
// model_not_found from 503 service_unavailable.
|
||||
type ModelAvailabilityDiagnosis struct {
|
||||
// HasAccountsInPool is true if the group has at least one persistently
|
||||
// eligible account on the queried platform (or, for Anthropic/Gemini, on
|
||||
// the platform plus mixed-scheduled Antigravity accounts).
|
||||
HasAccountsInPool bool
|
||||
// HasModelSupport is true if at least one account's model mapping admits
|
||||
// the requested model.
|
||||
HasModelSupport bool
|
||||
}
|
||||
|
||||
// ModelAvailabilityDiagnoser is implemented by gateway services that can
|
||||
// report whether the requested model is configured to be served by any
|
||||
// account. Both *GatewayService and *OpenAIGatewayService implement this so
|
||||
// handlers in either package can share a single classifier.
|
||||
type ModelAvailabilityDiagnoser interface {
|
||||
DiagnoseModelAvailabilityForPlatform(
|
||||
ctx context.Context,
|
||||
groupID *int64,
|
||||
requestedModel string,
|
||||
platform string,
|
||||
) ModelAvailabilityDiagnosis
|
||||
}
|
||||
|
||||
// DiagnoseModelAvailabilityForPlatform inspects accounts enabled for scheduling
|
||||
// by persistent configuration and returns whether the requested model is
|
||||
// configured to be served by any of them. The dedicated repository query
|
||||
// bypasses scheduler snapshots and deliberately ignores transient rate-limit,
|
||||
// overload, temporary-unschedulable, expiry, quota, and runtime-block state.
|
||||
//
|
||||
// Safe to call on the error path: returns {true,true} on any internal failure
|
||||
// or when the inputs preclude meaningful diagnosis (empty model, etc.), so
|
||||
// callers stay on the 503 fallback branch.
|
||||
func (s *GatewayService) DiagnoseModelAvailabilityForPlatform(
|
||||
ctx context.Context,
|
||||
groupID *int64,
|
||||
requestedModel string,
|
||||
platform string,
|
||||
) ModelAvailabilityDiagnosis {
|
||||
if s == nil {
|
||||
return ModelAvailabilityDiagnosis{HasAccountsInPool: true, HasModelSupport: true}
|
||||
}
|
||||
requestedModel = strings.TrimSpace(requestedModel)
|
||||
if requestedModel == "" {
|
||||
// No model specified — cannot decide model_not_found. Caller falls back to 503.
|
||||
return ModelAvailabilityDiagnosis{HasAccountsInPool: true, HasModelSupport: true}
|
||||
}
|
||||
if strings.TrimSpace(platform) == "" {
|
||||
// Without a platform we cannot scope the lookup; bail out to the
|
||||
// 503 branch rather than make an unscoped scan.
|
||||
return ModelAvailabilityDiagnosis{HasAccountsInPool: true, HasModelSupport: true}
|
||||
}
|
||||
|
||||
if s.accountRepo == nil {
|
||||
return ModelAvailabilityDiagnosis{HasAccountsInPool: true, HasModelSupport: true}
|
||||
}
|
||||
|
||||
useMixed := platform == PlatformAnthropic || platform == PlatformGemini
|
||||
platforms := []string{platform}
|
||||
if useMixed {
|
||||
platforms = append(platforms, PlatformAntigravity)
|
||||
}
|
||||
|
||||
queryGroupID := groupID
|
||||
includeGrouped := false
|
||||
if useMixed {
|
||||
// Preserve the generic scheduler's scope rules: an explicit group wins
|
||||
// for mixed scheduling, while group-less simple mode scans all accounts.
|
||||
if groupID == nil && s.cfg != nil && s.cfg.RunMode == config.RunModeSimple {
|
||||
includeGrouped = true
|
||||
}
|
||||
} else if s.cfg != nil && s.cfg.RunMode == config.RunModeSimple {
|
||||
queryGroupID = nil
|
||||
includeGrouped = true
|
||||
}
|
||||
|
||||
accounts, err := s.accountRepo.ListModelAvailabilityCandidates(ctx, queryGroupID, platforms, includeGrouped)
|
||||
if err != nil {
|
||||
// Conservative fallback: pretend everything is fine so the caller
|
||||
// returns 503 (we don't want to flip to 404 just because a lookup
|
||||
// hiccup'd).
|
||||
return ModelAvailabilityDiagnosis{HasAccountsInPool: true, HasModelSupport: true}
|
||||
}
|
||||
|
||||
diag := ModelAvailabilityDiagnosis{}
|
||||
for i := range accounts {
|
||||
if useMixed && accounts[i].Platform == PlatformAntigravity && !accounts[i].IsMixedSchedulingEnabled() {
|
||||
continue
|
||||
}
|
||||
diag.HasAccountsInPool = true
|
||||
if s.isModelSupportedByAccountWithContext(ctx, &accounts[i], requestedModel) {
|
||||
diag.HasModelSupport = true
|
||||
return diag
|
||||
}
|
||||
}
|
||||
return diag
|
||||
}
|
||||
Reference in New Issue
Block a user