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,131 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
CompositeRouteMatchExact = "exact"
|
||||
CompositeRouteMatchPrefix = "prefix"
|
||||
|
||||
CompositeRouteEndpointAny = "any"
|
||||
CompositeRouteEndpointMessages = "messages"
|
||||
CompositeRouteEndpointCountTokens = "count_tokens"
|
||||
CompositeRouteEndpointResponses = "responses"
|
||||
CompositeRouteEndpointChatCompletions = "chat_completions"
|
||||
CompositeRouteEndpointEmbeddings = "embeddings"
|
||||
CompositeRouteEndpointImages = "images"
|
||||
CompositeRouteEndpointGemini = "gemini"
|
||||
|
||||
CompositeRouteSourceExplicit = "route"
|
||||
CompositeRouteSourceDetector = "detector"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrCompositeRouteNotFound = infraerrors.NotFound("COMPOSITE_ROUTE_NOT_FOUND", "composite route not found")
|
||||
ErrCompositeRouteExists = infraerrors.Conflict("COMPOSITE_ROUTE_EXISTS", "composite route already exists")
|
||||
)
|
||||
|
||||
// CompositeModelRoute maps one public model identifier in a composite group to
|
||||
// the concrete provider/model that should handle the request.
|
||||
type CompositeModelRoute struct {
|
||||
ID int64 `json:"id"`
|
||||
GroupID int64 `json:"group_id"`
|
||||
PublicModel string `json:"public_model"`
|
||||
MatchType string `json:"match_type"`
|
||||
TargetPlatform string `json:"target_platform"`
|
||||
UpstreamModel string `json:"upstream_model"`
|
||||
Endpoint string `json:"endpoint"`
|
||||
Priority int `json:"priority"`
|
||||
Enabled bool `json:"enabled"`
|
||||
Notes string `json:"notes"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
type CompositeRoutePreviewRequest struct {
|
||||
Model string `json:"model"`
|
||||
Endpoint string `json:"endpoint"`
|
||||
}
|
||||
|
||||
type CompositeRouteDecision struct {
|
||||
Matched bool `json:"matched"`
|
||||
Source string `json:"source"`
|
||||
GroupID int64 `json:"group_id"`
|
||||
PublicModel string `json:"public_model"`
|
||||
TargetPlatform string `json:"target_platform"`
|
||||
UpstreamModel string `json:"upstream_model"`
|
||||
Endpoint string `json:"endpoint"`
|
||||
Route *CompositeModelRoute `json:"route,omitempty"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
}
|
||||
|
||||
type CompositeRouteInput struct {
|
||||
PublicModel string
|
||||
MatchType string
|
||||
TargetPlatform string
|
||||
UpstreamModel string
|
||||
Endpoint string
|
||||
Priority int
|
||||
Enabled bool
|
||||
Notes string
|
||||
}
|
||||
|
||||
type CompositeModelRouteRepository interface {
|
||||
ListByGroup(ctx context.Context, groupID int64, includeDisabled bool) ([]CompositeModelRoute, error)
|
||||
Create(ctx context.Context, route *CompositeModelRoute) error
|
||||
Update(ctx context.Context, route *CompositeModelRoute) error
|
||||
Delete(ctx context.Context, id int64) error
|
||||
DeleteByGroup(ctx context.Context, groupID int64) error
|
||||
}
|
||||
|
||||
func normalizeCompositeRouteEndpoint(endpoint string) string {
|
||||
endpoint = strings.ToLower(strings.TrimSpace(endpoint))
|
||||
if endpoint == "" {
|
||||
return CompositeRouteEndpointAny
|
||||
}
|
||||
switch endpoint {
|
||||
case CompositeRouteEndpointMessages,
|
||||
CompositeRouteEndpointCountTokens,
|
||||
CompositeRouteEndpointResponses,
|
||||
CompositeRouteEndpointChatCompletions,
|
||||
CompositeRouteEndpointEmbeddings,
|
||||
CompositeRouteEndpointImages,
|
||||
CompositeRouteEndpointGemini:
|
||||
return endpoint
|
||||
default:
|
||||
return CompositeRouteEndpointAny
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeCompositeRouteMatchType(matchType string) string {
|
||||
matchType = strings.ToLower(strings.TrimSpace(matchType))
|
||||
switch matchType {
|
||||
case CompositeRouteMatchPrefix:
|
||||
return CompositeRouteMatchPrefix
|
||||
default:
|
||||
return CompositeRouteMatchExact
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeCompositeRouteInput(input CompositeRouteInput) CompositeRouteInput {
|
||||
input.PublicModel = strings.TrimSpace(input.PublicModel)
|
||||
input.MatchType = normalizeCompositeRouteMatchType(input.MatchType)
|
||||
input.TargetPlatform = strings.TrimSpace(input.TargetPlatform)
|
||||
input.UpstreamModel = strings.TrimSpace(input.UpstreamModel)
|
||||
input.Endpoint = normalizeCompositeRouteEndpoint(input.Endpoint)
|
||||
// 仅对 exact 路由把空 upstream_model 回填成 public_model:exact 命中时请求模型
|
||||
// 恒等于 public_model,回填只影响持久化/后台展示,保留原有契约不变。
|
||||
// prefix 路由留空则不回填——Resolve 会回退到具体请求模型,从而透传原始模型
|
||||
// (否则 public=deepseek-v4 的前缀路由会把 deepseek-v4-flash / deepseek-v4-pro
|
||||
// 都塌缩成固定的 deepseek-v4)。显式填写 upstream_model 时任何模式都原样固定转发。
|
||||
if input.UpstreamModel == "" && input.MatchType == CompositeRouteMatchExact {
|
||||
input.UpstreamModel = input.PublicModel
|
||||
}
|
||||
input.Notes = strings.TrimSpace(input.Notes)
|
||||
return input
|
||||
}
|
||||
Reference in New Issue
Block a user