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
141 lines
3.6 KiB
Go
141 lines
3.6 KiB
Go
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
|
|
}
|