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,140 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type CompositeRouteResolver struct {
|
||||
repo CompositeModelRouteRepository
|
||||
}
|
||||
|
||||
func NewCompositeRouteResolver(repo CompositeModelRouteRepository) *CompositeRouteResolver {
|
||||
return &CompositeRouteResolver{repo: repo}
|
||||
}
|
||||
|
||||
func (r *CompositeRouteResolver) Resolve(ctx context.Context, groupID int64, model, endpoint string) (CompositeRouteDecision, error) {
|
||||
model = strings.TrimSpace(model)
|
||||
endpoint = normalizeCompositeRouteEndpoint(endpoint)
|
||||
decision := CompositeRouteDecision{
|
||||
GroupID: groupID,
|
||||
PublicModel: model,
|
||||
Endpoint: endpoint,
|
||||
}
|
||||
if model == "" {
|
||||
decision.Reason = "model is required"
|
||||
return decision, nil
|
||||
}
|
||||
|
||||
if r != nil && r.repo != nil && groupID > 0 {
|
||||
routes, err := r.repo.ListByGroup(ctx, groupID, false)
|
||||
if err != nil {
|
||||
return decision, fmt.Errorf("list composite routes: %w", err)
|
||||
}
|
||||
if route, ok := matchCompositeRoute(routes, model, endpoint); ok {
|
||||
upstreamModel := strings.TrimSpace(route.UpstreamModel)
|
||||
if upstreamModel == "" {
|
||||
upstreamModel = model
|
||||
}
|
||||
return CompositeRouteDecision{
|
||||
Matched: true,
|
||||
Source: CompositeRouteSourceExplicit,
|
||||
GroupID: groupID,
|
||||
PublicModel: model,
|
||||
TargetPlatform: route.TargetPlatform,
|
||||
UpstreamModel: upstreamModel,
|
||||
Endpoint: endpoint,
|
||||
Route: &route,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
if platform, ok := DetectModelPlatform(model); ok {
|
||||
return CompositeRouteDecision{
|
||||
Matched: true,
|
||||
Source: CompositeRouteSourceDetector,
|
||||
GroupID: groupID,
|
||||
PublicModel: model,
|
||||
TargetPlatform: platform,
|
||||
UpstreamModel: model,
|
||||
Endpoint: endpoint,
|
||||
}, nil
|
||||
}
|
||||
decision.Reason = "no explicit route or built-in detector match"
|
||||
return decision, nil
|
||||
}
|
||||
|
||||
func matchCompositeRoute(routes []CompositeModelRoute, model, endpoint string) (CompositeModelRoute, bool) {
|
||||
if len(routes) == 0 {
|
||||
return CompositeModelRoute{}, false
|
||||
}
|
||||
|
||||
type candidate struct {
|
||||
route CompositeModelRoute
|
||||
matchStrength int
|
||||
endpointWeight int
|
||||
prefixLen int
|
||||
}
|
||||
candidates := make([]candidate, 0, len(routes))
|
||||
for _, route := range routes {
|
||||
route.Endpoint = normalizeCompositeRouteEndpoint(route.Endpoint)
|
||||
if route.Endpoint != endpoint && route.Endpoint != CompositeRouteEndpointAny {
|
||||
continue
|
||||
}
|
||||
route.MatchType = normalizeCompositeRouteMatchType(route.MatchType)
|
||||
publicModel := strings.TrimSpace(route.PublicModel)
|
||||
if publicModel == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
matchStrength := 0
|
||||
prefixLen := len(publicModel)
|
||||
switch route.MatchType {
|
||||
case CompositeRouteMatchExact:
|
||||
if publicModel != model {
|
||||
continue
|
||||
}
|
||||
matchStrength = 2
|
||||
case CompositeRouteMatchPrefix:
|
||||
if !strings.HasPrefix(model, publicModel) {
|
||||
continue
|
||||
}
|
||||
matchStrength = 1
|
||||
default:
|
||||
continue
|
||||
}
|
||||
endpointWeight := 0
|
||||
if route.Endpoint == endpoint {
|
||||
endpointWeight = 1
|
||||
}
|
||||
candidates = append(candidates, candidate{
|
||||
route: route,
|
||||
matchStrength: matchStrength,
|
||||
endpointWeight: endpointWeight,
|
||||
prefixLen: prefixLen,
|
||||
})
|
||||
}
|
||||
if len(candidates) == 0 {
|
||||
return CompositeModelRoute{}, false
|
||||
}
|
||||
|
||||
sort.SliceStable(candidates, func(i, j int) bool {
|
||||
a, b := candidates[i], candidates[j]
|
||||
if a.matchStrength != b.matchStrength {
|
||||
return a.matchStrength > b.matchStrength
|
||||
}
|
||||
if a.endpointWeight != b.endpointWeight {
|
||||
return a.endpointWeight > b.endpointWeight
|
||||
}
|
||||
if a.prefixLen != b.prefixLen {
|
||||
return a.prefixLen > b.prefixLen
|
||||
}
|
||||
if a.route.Priority != b.route.Priority {
|
||||
return a.route.Priority < b.route.Priority
|
||||
}
|
||||
return a.route.ID < b.route.ID
|
||||
})
|
||||
return candidates[0].route, true
|
||||
}
|
||||
Reference in New Issue
Block a user