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,201 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/Wei-Shaw/sub2api/internal/pkg/ctxkey"
|
||||
)
|
||||
|
||||
// WithResolvedTargetPlatform stores the concrete provider chosen for a request
|
||||
// made through a composite group.
|
||||
func WithResolvedTargetPlatform(ctx context.Context, platform string) context.Context {
|
||||
platform = strings.TrimSpace(platform)
|
||||
if ctx == nil || platform == "" {
|
||||
return ctx
|
||||
}
|
||||
return context.WithValue(ctx, ctxkey.ResolvedTargetPlatform, platform)
|
||||
}
|
||||
|
||||
// ResolvedTargetPlatformFromContext returns the concrete provider chosen for
|
||||
// the current request, if one was resolved.
|
||||
func ResolvedTargetPlatformFromContext(ctx context.Context) (string, bool) {
|
||||
if ctx == nil {
|
||||
return "", false
|
||||
}
|
||||
platform, ok := ctx.Value(ctxkey.ResolvedTargetPlatform).(string)
|
||||
platform = strings.TrimSpace(platform)
|
||||
if !ok || platform == "" {
|
||||
return "", false
|
||||
}
|
||||
return platform, true
|
||||
}
|
||||
|
||||
func WithCompositeRouteDecision(ctx context.Context, decision CompositeRouteDecision) context.Context {
|
||||
if ctx == nil || !decision.Matched {
|
||||
return ctx
|
||||
}
|
||||
ctx = WithResolvedTargetPlatform(ctx, decision.TargetPlatform)
|
||||
if model := strings.TrimSpace(decision.UpstreamModel); model != "" {
|
||||
ctx = context.WithValue(ctx, ctxkey.ResolvedUpstreamModel, model)
|
||||
}
|
||||
if model := strings.TrimSpace(decision.PublicModel); model != "" {
|
||||
ctx = context.WithValue(ctx, ctxkey.RequestedPublicModel, model)
|
||||
}
|
||||
if source := strings.TrimSpace(decision.Source); source != "" {
|
||||
ctx = context.WithValue(ctx, ctxkey.CompositeRouteSource, source)
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
|
||||
func ResolvedUpstreamModelFromContext(ctx context.Context) (string, bool) {
|
||||
if ctx == nil {
|
||||
return "", false
|
||||
}
|
||||
model, ok := ctx.Value(ctxkey.ResolvedUpstreamModel).(string)
|
||||
model = strings.TrimSpace(model)
|
||||
if !ok || model == "" {
|
||||
return "", false
|
||||
}
|
||||
return model, true
|
||||
}
|
||||
|
||||
func RequestedPublicModelFromContext(ctx context.Context) (string, bool) {
|
||||
if ctx == nil {
|
||||
return "", false
|
||||
}
|
||||
model, ok := ctx.Value(ctxkey.RequestedPublicModel).(string)
|
||||
model = strings.TrimSpace(model)
|
||||
if !ok || model == "" {
|
||||
return "", false
|
||||
}
|
||||
return model, true
|
||||
}
|
||||
|
||||
func CompositeRouteSourceFromContext(ctx context.Context) (string, bool) {
|
||||
if ctx == nil {
|
||||
return "", false
|
||||
}
|
||||
source, ok := ctx.Value(ctxkey.CompositeRouteSource).(string)
|
||||
source = strings.TrimSpace(source)
|
||||
if !ok || source == "" {
|
||||
return "", false
|
||||
}
|
||||
return source, true
|
||||
}
|
||||
|
||||
// DetectModelPlatform maps common public model IDs to the concrete provider
|
||||
// platform used by sub2api. It intentionally returns false for ambiguous model
|
||||
// names so composite groups fail closed instead of guessing.
|
||||
func DetectModelPlatform(model string) (string, bool) {
|
||||
normalized := strings.ToLower(strings.TrimSpace(model))
|
||||
if normalized == "" {
|
||||
return "", false
|
||||
}
|
||||
|
||||
normalized = strings.TrimPrefix(normalized, "models/")
|
||||
if slash := strings.IndexByte(normalized, '/'); slash > 0 {
|
||||
provider := strings.TrimSpace(normalized[:slash])
|
||||
rest := strings.TrimSpace(normalized[slash+1:])
|
||||
switch provider {
|
||||
case "anthropic", "claude":
|
||||
return PlatformAnthropic, true
|
||||
case "openai", "chatgpt":
|
||||
return PlatformOpenAI, true
|
||||
case "google", "google-ai-studio", "gemini":
|
||||
return PlatformGemini, true
|
||||
case "xai", "x-ai", "grok":
|
||||
return PlatformGrok, true
|
||||
case "kimi", "moonshot":
|
||||
return PlatformKimi, true
|
||||
case "zhipu", "glm", "bigmodel":
|
||||
return PlatformZhipu, true
|
||||
case "deepseek":
|
||||
return PlatformDeepseek, true
|
||||
}
|
||||
if rest != "" {
|
||||
normalized = strings.TrimPrefix(rest, "models/")
|
||||
}
|
||||
}
|
||||
|
||||
switch {
|
||||
case strings.HasPrefix(normalized, "anthropic.claude-"),
|
||||
strings.HasPrefix(normalized, "claude-"):
|
||||
return PlatformAnthropic, true
|
||||
case strings.HasPrefix(normalized, "gpt-"),
|
||||
strings.HasPrefix(normalized, "chatgpt-"),
|
||||
strings.HasPrefix(normalized, "codex-"),
|
||||
strings.HasPrefix(normalized, "text-embedding-"),
|
||||
strings.HasPrefix(normalized, "text-moderation-"),
|
||||
strings.HasPrefix(normalized, "omni-moderation-"),
|
||||
strings.HasPrefix(normalized, "dall-e-"),
|
||||
strings.HasPrefix(normalized, "gpt-image-"),
|
||||
strings.HasPrefix(normalized, "tts-"),
|
||||
strings.HasPrefix(normalized, "whisper-"),
|
||||
hasOpenAISeriesPrefix(normalized):
|
||||
return PlatformOpenAI, true
|
||||
case strings.HasPrefix(normalized, "gemini-"),
|
||||
strings.HasPrefix(normalized, "learnlm-"):
|
||||
return PlatformGemini, true
|
||||
case normalized == "grok" || strings.HasPrefix(normalized, "grok-"):
|
||||
return PlatformGrok, true
|
||||
case strings.HasPrefix(normalized, "kimi-"),
|
||||
strings.HasPrefix(normalized, "moonshot-"):
|
||||
return PlatformKimi, true
|
||||
case strings.HasPrefix(normalized, "glm-"):
|
||||
return PlatformZhipu, true
|
||||
case strings.HasPrefix(normalized, "deepseek-"):
|
||||
return PlatformDeepseek, true
|
||||
default:
|
||||
return "", false
|
||||
}
|
||||
}
|
||||
|
||||
func hasOpenAISeriesPrefix(model string) bool {
|
||||
for _, prefix := range []string{"o1", "o3", "o4", "o5"} {
|
||||
if model == prefix || strings.HasPrefix(model, prefix+"-") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *GatewayService) resolveCompositeRouteDecision(ctx context.Context, group *Group, requestedModel, endpoint string) (CompositeRouteDecision, bool, error) {
|
||||
if group == nil || group.Platform != PlatformComposite {
|
||||
return CompositeRouteDecision{}, false, nil
|
||||
}
|
||||
if platform, ok := ResolvedTargetPlatformFromContext(ctx); ok {
|
||||
upstreamModel := requestedModel
|
||||
if resolvedModel, modelOK := ResolvedUpstreamModelFromContext(ctx); modelOK {
|
||||
upstreamModel = resolvedModel
|
||||
}
|
||||
source := CompositeRouteSourceDetector
|
||||
if resolvedSource, sourceOK := CompositeRouteSourceFromContext(ctx); sourceOK {
|
||||
source = resolvedSource
|
||||
}
|
||||
return CompositeRouteDecision{
|
||||
Matched: true,
|
||||
Source: source,
|
||||
GroupID: group.ID,
|
||||
PublicModel: requestedModel,
|
||||
TargetPlatform: platform,
|
||||
UpstreamModel: upstreamModel,
|
||||
Endpoint: normalizeCompositeRouteEndpoint(endpoint),
|
||||
}, true, nil
|
||||
}
|
||||
decision, err := s.compositeResolver.Resolve(ctx, group.ID, requestedModel, endpoint)
|
||||
if err != nil {
|
||||
return decision, false, err
|
||||
}
|
||||
return decision, decision.Matched, nil
|
||||
}
|
||||
|
||||
func isConcreteRequestPlatform(platform string) bool {
|
||||
switch platform {
|
||||
case PlatformAnthropic, PlatformOpenAI, PlatformGemini, PlatformAntigravity, PlatformGrok,
|
||||
PlatformKimi, PlatformZhipu, PlatformDeepseek:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user