mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-20 00:18:55 +00:00
Merge pull request #7793 from wucm667/fix/issue-7784-composite-route-model-list
fix(models): list composite exact route aliases
This commit is contained in:
@@ -1166,7 +1166,7 @@ func (h *GatewayHandler) Models(c *gin.Context) {
|
||||
}
|
||||
|
||||
if platform == service.PlatformComposite {
|
||||
availableModels := h.compositeAvailableModels(c.Request.Context(), groupID, true)
|
||||
availableModels := h.compositeAvailableModels(c.Request.Context(), groupID, "", true)
|
||||
if apiKey != nil && apiKey.Group != nil && apiKey.Group.ModelAllowlistEnabled() {
|
||||
source := availableModels
|
||||
if len(source) == 0 {
|
||||
@@ -1265,19 +1265,19 @@ func (h *GatewayHandler) codexModelIDsForGroup(ctx context.Context, group *servi
|
||||
platform = group.Platform
|
||||
}
|
||||
if platform == service.PlatformComposite {
|
||||
availableModels := h.compositeAvailableModels(ctx, groupID, false)
|
||||
availableModels := h.compositeAvailableModels(ctx, groupID, service.CompositeRouteEndpointResponses, false)
|
||||
fallbackModels := defaultCodexModelIDsForPlatform(service.PlatformComposite)
|
||||
models := availableModels
|
||||
if len(models) == 0 {
|
||||
models = fallbackModels
|
||||
}
|
||||
if group.ModelAllowlistEnabled() {
|
||||
source := availableModels
|
||||
if len(source) == 0 {
|
||||
source = fallbackModels
|
||||
}
|
||||
return group.ModelAllowlist.FilterForListing(source)
|
||||
models = group.ModelAllowlist.FilterForListing(models)
|
||||
}
|
||||
if len(availableModels) > 0 {
|
||||
return availableModels
|
||||
if filtered, err := h.gatewayService.FilterCompositeCodexModels(ctx, group.ID, models); err == nil {
|
||||
return filtered
|
||||
}
|
||||
return fallbackModels
|
||||
return models
|
||||
}
|
||||
|
||||
availableModels := h.gatewayService.GetAvailableModels(ctx, groupID, platform)
|
||||
@@ -1294,7 +1294,7 @@ func (h *GatewayHandler) codexModelIDsForGroup(ctx context.Context, group *servi
|
||||
// compositeAvailableModels lists the models the composite group can serve.
|
||||
// includeSystemOne adds TypeSafe models, which only work through /v1/systemone;
|
||||
// LLM client catalogs (Codex) must exclude them.
|
||||
func (h *GatewayHandler) compositeAvailableModels(ctx context.Context, groupID *int64, includeSystemOne bool) []string {
|
||||
func (h *GatewayHandler) compositeAvailableModels(ctx context.Context, groupID *int64, endpoint string, includeSystemOne bool) []string {
|
||||
if h == nil || h.gatewayService == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -1325,6 +1325,16 @@ func (h *GatewayHandler) compositeAvailableModels(ctx context.Context, groupID *
|
||||
models = append(models, model)
|
||||
}
|
||||
}
|
||||
// A route can expose a public ID that no account model mapping contains.
|
||||
// On lookup failure, retain the existing account-derived catalog only.
|
||||
if routeModels, err := h.gatewayService.GetCompositeRouteModels(ctx, groupID, endpoint, includeSystemOne); err == nil {
|
||||
for _, model := range routeModels {
|
||||
if _, ok := seen[model]; !ok {
|
||||
seen[model] = struct{}{}
|
||||
models = append(models, model)
|
||||
}
|
||||
}
|
||||
}
|
||||
return models
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package handler
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
@@ -22,6 +23,16 @@ type gatewayModelsAccountRepoStub struct {
|
||||
byGroup map[int64][]service.Account
|
||||
}
|
||||
|
||||
type gatewayModelsRouteRepoStub struct {
|
||||
service.CompositeModelRouteRepository
|
||||
routes []service.CompositeModelRoute
|
||||
err error
|
||||
}
|
||||
|
||||
func (s *gatewayModelsRouteRepoStub) ListByGroup(_ context.Context, _ int64, _ bool) ([]service.CompositeModelRoute, error) {
|
||||
return s.routes, s.err
|
||||
}
|
||||
|
||||
type gatewayModelsResponseForTest struct {
|
||||
Object string `json:"object"`
|
||||
Data []gatewayModelItemForTest `json:"data"`
|
||||
@@ -81,16 +92,80 @@ func (s *gatewayModelsAccountRepoStub) ListModelAvailabilityCandidates(ctx conte
|
||||
return s.ListSchedulableByGroupID(ctx, *groupID)
|
||||
}
|
||||
|
||||
func newGatewayModelsHandlerForTest(repo service.AccountRepository) *GatewayHandler {
|
||||
func newGatewayModelsHandlerForTest(repo service.AccountRepository, routeRepo ...service.CompositeModelRouteRepository) *GatewayHandler {
|
||||
var resolver *service.CompositeRouteResolver
|
||||
if len(routeRepo) > 0 {
|
||||
resolver = service.NewCompositeRouteResolver(routeRepo[0])
|
||||
}
|
||||
return &GatewayHandler{
|
||||
gatewayService: service.NewGatewayService(
|
||||
repo,
|
||||
nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
|
||||
nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
|
||||
nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, resolver, nil, nil,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
func TestGatewayCompositeRouteModelsAppearInBothCatalogs(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
const groupID int64 = 7784
|
||||
accounts := &gatewayModelsAccountRepoStub{byGroup: map[int64][]service.Account{
|
||||
groupID: {{ID: 1, Platform: service.PlatformOpenAI, Credentials: map[string]any{
|
||||
"model_mapping": map[string]any{"mapped-model": "upstream"},
|
||||
}}},
|
||||
}}
|
||||
routes := &gatewayModelsRouteRepoStub{routes: []service.CompositeModelRoute{
|
||||
{PublicModel: "route-only", MatchType: service.CompositeRouteMatchExact, Enabled: true},
|
||||
{PublicModel: "route-only", MatchType: service.CompositeRouteMatchExact, Enabled: true},
|
||||
{PublicModel: "mapped-model", MatchType: service.CompositeRouteMatchExact, Enabled: true},
|
||||
{PublicModel: "image-only", MatchType: service.CompositeRouteMatchExact, Endpoint: service.CompositeRouteEndpointImages, Enabled: true},
|
||||
{PublicModel: "prefix-", MatchType: service.CompositeRouteMatchPrefix, Enabled: true},
|
||||
{PublicModel: "disabled", MatchType: service.CompositeRouteMatchExact, Enabled: false},
|
||||
}}
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
repo service.CompositeModelRouteRepository
|
||||
allowlist []string
|
||||
want []string
|
||||
}{
|
||||
{"routes", routes, nil, []string{"mapped-model", "route-only", "image-only"}},
|
||||
{"allowlist", routes, []string{"route-only", "disabled", "unknown"}, []string{"route-only"}},
|
||||
{"lookup failure", &gatewayModelsRouteRepoStub{err: errors.New("unavailable")}, nil, []string{"mapped-model"}},
|
||||
{"allowlisted lookup failure", &gatewayModelsRouteRepoStub{err: errors.New("unavailable")}, []string{"mapped-model", "unknown"}, []string{"mapped-model"}},
|
||||
{"no route repo", nil, nil, []string{"mapped-model"}},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
h := newGatewayModelsHandlerForTest(accounts, tc.repo)
|
||||
group := &service.Group{ID: groupID, Platform: service.PlatformComposite}
|
||||
if tc.allowlist != nil {
|
||||
group.ModelAllowlist = service.GroupModelAllowlist{Enabled: true, Models: tc.allowlist}
|
||||
}
|
||||
for _, endpoint := range []string{"models", "codex"} {
|
||||
rec := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(rec)
|
||||
c.Request = httptest.NewRequest(http.MethodGet, "/v1/models", nil)
|
||||
c.Set(string(middleware2.ContextKeyAPIKey), &service.APIKey{Group: group})
|
||||
if endpoint == "models" {
|
||||
h.Models(c)
|
||||
var got gatewayModelsResponseForTest
|
||||
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &got))
|
||||
require.Equal(t, tc.want, modelIDsForTest(got.Data))
|
||||
} else {
|
||||
h.CodexModels(c)
|
||||
var got codexModelsResponseForTest
|
||||
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &got))
|
||||
want := tc.want
|
||||
if tc.name == "routes" {
|
||||
want = []string{"mapped-model", "route-only"}
|
||||
}
|
||||
require.ElementsMatch(t, want, codexModelSlugsForTest(got.Models))
|
||||
}
|
||||
require.Equal(t, http.StatusOK, rec.Code)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultModelIDsForCompositeIncludesAntigravityDefaults(t *testing.T) {
|
||||
antigravityIDs := defaultModelIDsForPlatform(service.PlatformAntigravity)
|
||||
require.NotEmpty(t, antigravityIDs)
|
||||
@@ -1570,3 +1645,188 @@ func TestDefaultModelIDsForPlatform_CompositeFallbackExcludesTypeSafe(t *testing
|
||||
require.NotContains(t, defaultModelIDsForPlatform(service.PlatformComposite), "jev-latest")
|
||||
require.NotContains(t, defaultCodexModelIDsForPlatform(service.PlatformComposite), "jev-latest")
|
||||
}
|
||||
|
||||
func TestGatewayModels_CompositeExactRoutesRespectTypeSafeIsolation(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
const groupID int64 = 7784
|
||||
routes := &gatewayModelsRouteRepoStub{routes: []service.CompositeModelRoute{
|
||||
{PublicModel: "systemone-any-alias", TargetPlatform: service.PlatformTypeSafe, MatchType: service.CompositeRouteMatchExact, Endpoint: service.CompositeRouteEndpointAny, Enabled: true},
|
||||
{PublicModel: "systemone-responses-alias", TargetPlatform: service.PlatformTypeSafe, MatchType: service.CompositeRouteMatchExact, Endpoint: service.CompositeRouteEndpointResponses, Enabled: true},
|
||||
{PublicModel: "systemone-legacy-alias", TargetPlatform: service.PlatformTypeSafe, MatchType: service.CompositeRouteMatchExact, Enabled: true},
|
||||
{PublicModel: "jev-latest", TargetPlatform: service.PlatformTypeSafe, MatchType: service.CompositeRouteMatchExact, Enabled: true},
|
||||
{PublicModel: "responses-alias", TargetPlatform: service.PlatformOpenAI, MatchType: service.CompositeRouteMatchExact, Endpoint: service.CompositeRouteEndpointResponses, Enabled: true},
|
||||
{PublicModel: "image-alias", TargetPlatform: service.PlatformOpenAI, MatchType: service.CompositeRouteMatchExact, Endpoint: service.CompositeRouteEndpointImages, Enabled: true},
|
||||
}}
|
||||
h := newGatewayModelsHandlerForTest(&gatewayModelsAccountRepoStub{}, routes)
|
||||
wantModels := make([]string, 0, len(routes.routes))
|
||||
for _, route := range routes.routes {
|
||||
wantModels = append(wantModels, route.PublicModel)
|
||||
}
|
||||
for _, allowlisted := range []bool{false, true} {
|
||||
name := "unrestricted"
|
||||
if allowlisted {
|
||||
name = "allowlisted"
|
||||
}
|
||||
t.Run(name, func(t *testing.T) {
|
||||
group := &service.Group{ID: groupID, Platform: service.PlatformComposite}
|
||||
if allowlisted {
|
||||
group.ModelAllowlist = service.GroupModelAllowlist{Enabled: true, Models: []string{
|
||||
"systemone-any-alias", "systemone-responses-alias", "systemone-legacy-alias", "jev-latest", "responses-alias", "image-alias",
|
||||
}}
|
||||
}
|
||||
for _, codex := range []bool{false, true} {
|
||||
rec := httptest.NewRecorder()
|
||||
ctx, _ := gin.CreateTestContext(rec)
|
||||
ctx.Request = httptest.NewRequest(http.MethodGet, "/v1/models", nil)
|
||||
ctx.Set(string(middleware2.ContextKeyAPIKey), &service.APIKey{Group: group})
|
||||
if codex {
|
||||
h.CodexModels(ctx)
|
||||
var manifest codexModelsResponseForTest
|
||||
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &manifest))
|
||||
require.Equal(t, []string{"responses-alias"}, codexModelSlugsForTest(manifest.Models))
|
||||
} else {
|
||||
h.Models(ctx)
|
||||
var models gatewayModelsResponseForTest
|
||||
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &models))
|
||||
require.ElementsMatch(t, wantModels, modelIDsForTest(models.Data))
|
||||
}
|
||||
require.Equal(t, http.StatusOK, rec.Code)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGatewayModels_CompositeCodexFiltersEffectiveResponsesRoutes(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
const groupID int64 = 7793
|
||||
defaultModel := defaultModelIDsForPlatform(service.PlatformOpenAI)[0]
|
||||
for _, scenario := range []struct {
|
||||
name string
|
||||
model string
|
||||
accounts []service.Account
|
||||
routes []service.CompositeModelRoute
|
||||
}{
|
||||
{
|
||||
name: "colliding exact routes",
|
||||
model: "shared-alias",
|
||||
routes: []service.CompositeModelRoute{
|
||||
{PublicModel: "shared-alias", TargetPlatform: service.PlatformOpenAI, MatchType: service.CompositeRouteMatchExact, Endpoint: service.CompositeRouteEndpointAny, Priority: 1, Enabled: true},
|
||||
{PublicModel: "shared-alias", TargetPlatform: service.PlatformTypeSafe, MatchType: service.CompositeRouteMatchExact, Endpoint: service.CompositeRouteEndpointResponses, Priority: 100, Enabled: true},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "account alias exact override",
|
||||
model: "account-alias",
|
||||
accounts: []service.Account{{ID: 1, Platform: service.PlatformOpenAI, Credentials: map[string]any{
|
||||
"model_mapping": map[string]any{"account-alias": "gpt-5"},
|
||||
}}},
|
||||
routes: []service.CompositeModelRoute{
|
||||
{PublicModel: "account-alias", TargetPlatform: service.PlatformTypeSafe, MatchType: service.CompositeRouteMatchExact, Endpoint: service.CompositeRouteEndpointResponses, Enabled: true},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "account alias prefix override",
|
||||
model: "account-alias",
|
||||
accounts: []service.Account{{ID: 1, Platform: service.PlatformOpenAI, Credentials: map[string]any{
|
||||
"model_mapping": map[string]any{"account-alias": "gpt-5"},
|
||||
}}},
|
||||
routes: []service.CompositeModelRoute{
|
||||
{PublicModel: "account-", TargetPlatform: service.PlatformTypeSafe, MatchType: service.CompositeRouteMatchPrefix, Endpoint: service.CompositeRouteEndpointResponses, Enabled: true},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "account default override",
|
||||
model: defaultModel,
|
||||
accounts: []service.Account{{ID: 1, Platform: service.PlatformOpenAI}},
|
||||
routes: []service.CompositeModelRoute{
|
||||
{PublicModel: defaultModel, TargetPlatform: service.PlatformTypeSafe, MatchType: service.CompositeRouteMatchExact, Endpoint: service.CompositeRouteEndpointResponses, Enabled: true},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "fallback default override",
|
||||
model: defaultModel,
|
||||
routes: []service.CompositeModelRoute{
|
||||
{PublicModel: defaultModel, TargetPlatform: service.PlatformTypeSafe, MatchType: service.CompositeRouteMatchPrefix, Endpoint: service.CompositeRouteEndpointResponses, Enabled: true},
|
||||
},
|
||||
},
|
||||
} {
|
||||
t.Run(scenario.name, func(t *testing.T) {
|
||||
for _, allowlisted := range []bool{false, true} {
|
||||
name := "unrestricted"
|
||||
if allowlisted {
|
||||
name = "allowlisted"
|
||||
}
|
||||
t.Run(name, func(t *testing.T) {
|
||||
routes := append([]service.CompositeModelRoute(nil), scenario.routes...)
|
||||
if scenario.name != "fallback default override" {
|
||||
routes = append(routes, service.CompositeModelRoute{
|
||||
PublicModel: "responses-control", TargetPlatform: service.PlatformOpenAI, MatchType: service.CompositeRouteMatchExact, Endpoint: service.CompositeRouteEndpointResponses, Enabled: true,
|
||||
})
|
||||
}
|
||||
h := newGatewayModelsHandlerForTest(&gatewayModelsAccountRepoStub{
|
||||
byGroup: map[int64][]service.Account{groupID: scenario.accounts},
|
||||
}, &gatewayModelsRouteRepoStub{routes: routes})
|
||||
group := &service.Group{ID: groupID, Platform: service.PlatformComposite}
|
||||
if allowlisted {
|
||||
group.ModelAllowlist = service.GroupModelAllowlist{Enabled: true, Models: []string{scenario.model, "responses-control"}}
|
||||
}
|
||||
for _, codex := range []bool{false, true} {
|
||||
recorder := httptest.NewRecorder()
|
||||
ctx, _ := gin.CreateTestContext(recorder)
|
||||
ctx.Request = httptest.NewRequest(http.MethodGet, "/v1/models", nil)
|
||||
ctx.Set(string(middleware2.ContextKeyAPIKey), &service.APIKey{Group: group})
|
||||
if codex {
|
||||
h.CodexModels(ctx)
|
||||
var manifest codexModelsResponseForTest
|
||||
require.NoError(t, json.Unmarshal(recorder.Body.Bytes(), &manifest))
|
||||
slugs := codexModelSlugsForTest(manifest.Models)
|
||||
require.NotContains(t, slugs, scenario.model)
|
||||
if scenario.name != "fallback default override" {
|
||||
require.Contains(t, slugs, "responses-control")
|
||||
} else if allowlisted {
|
||||
require.Empty(t, slugs)
|
||||
}
|
||||
} else {
|
||||
h.Models(ctx)
|
||||
var models gatewayModelsResponseForTest
|
||||
require.NoError(t, json.Unmarshal(recorder.Body.Bytes(), &models))
|
||||
require.Contains(t, modelIDsForTest(models.Data), scenario.model)
|
||||
}
|
||||
require.Equal(t, http.StatusOK, recorder.Code)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGatewayModels_CompositeCodexRouteLookupErrorKeepsFallback(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
const groupID int64 = 7793
|
||||
model := defaultCodexModelIDsForPlatform(service.PlatformComposite)[0]
|
||||
for _, allowlisted := range []bool{false, true} {
|
||||
name := "unrestricted"
|
||||
if allowlisted {
|
||||
name = "allowlisted"
|
||||
}
|
||||
t.Run(name, func(t *testing.T) {
|
||||
h := newGatewayModelsHandlerForTest(&gatewayModelsAccountRepoStub{}, &gatewayModelsRouteRepoStub{err: errors.New("unavailable")})
|
||||
group := &service.Group{ID: groupID, Platform: service.PlatformComposite}
|
||||
if allowlisted {
|
||||
group.ModelAllowlist = service.GroupModelAllowlist{Enabled: true, Models: []string{model}}
|
||||
}
|
||||
recorder := httptest.NewRecorder()
|
||||
ctx, _ := gin.CreateTestContext(recorder)
|
||||
ctx.Request = httptest.NewRequest(http.MethodGet, "/v1/models", nil)
|
||||
ctx.Set(string(middleware2.ContextKeyAPIKey), &service.APIKey{Group: group})
|
||||
h.CodexModels(ctx)
|
||||
require.Equal(t, http.StatusOK, recorder.Code)
|
||||
var manifest codexModelsResponseForTest
|
||||
require.NoError(t, json.Unmarshal(recorder.Body.Bytes(), &manifest))
|
||||
require.Contains(t, codexModelSlugsForTest(manifest.Models), model)
|
||||
if allowlisted {
|
||||
require.Len(t, manifest.Models, 1)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,52 @@ func (r *CompositeRouteResolver) SetModelOwnershipResolver(resolver CompositeMod
|
||||
}
|
||||
}
|
||||
|
||||
// ListExactPublicModels returns enabled, concrete route IDs suitable for a model catalog.
|
||||
func (r *CompositeRouteResolver) ListExactPublicModels(ctx context.Context, groupID int64, endpoint string, includeSystemOne bool) ([]string, error) {
|
||||
if r == nil || r.repo == nil || groupID <= 0 {
|
||||
return nil, nil
|
||||
}
|
||||
routes, err := r.repo.ListByGroup(ctx, groupID, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
models := make([]string, 0, len(routes))
|
||||
for _, route := range routes {
|
||||
if route.Enabled && route.MatchType == CompositeRouteMatchExact &&
|
||||
(endpoint == "" || normalizeCompositeRouteEndpoint(route.Endpoint) == CompositeRouteEndpointAny || normalizeCompositeRouteEndpoint(route.Endpoint) == endpoint) {
|
||||
if model := strings.TrimSpace(route.PublicModel); model != "" {
|
||||
models = append(models, model)
|
||||
}
|
||||
}
|
||||
}
|
||||
if !includeSystemOne {
|
||||
models = filterSystemOneRouteModels(routes, models, endpoint)
|
||||
}
|
||||
return models, nil
|
||||
}
|
||||
|
||||
func (r *CompositeRouteResolver) FilterCodexModels(ctx context.Context, groupID int64, models []string) ([]string, error) {
|
||||
if r == nil || r.repo == nil || groupID <= 0 {
|
||||
return models, nil
|
||||
}
|
||||
routes, err := r.repo.ListByGroup(ctx, groupID, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return filterSystemOneRouteModels(routes, models, CompositeRouteEndpointResponses), nil
|
||||
}
|
||||
|
||||
func filterSystemOneRouteModels(routes []CompositeModelRoute, models []string, endpoint string) []string {
|
||||
filtered := make([]string, 0, len(models))
|
||||
for _, model := range models {
|
||||
route, matched := matchCompositeRoute(routes, model, normalizeCompositeRouteEndpoint(endpoint))
|
||||
if !matched || route.TargetPlatform != PlatformTypeSafe {
|
||||
filtered = append(filtered, model)
|
||||
}
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
func (r *CompositeRouteResolver) Resolve(ctx context.Context, groupID int64, model, endpoint string) (CompositeRouteDecision, error) {
|
||||
model = strings.TrimSpace(model)
|
||||
endpoint = normalizeCompositeRouteEndpoint(endpoint)
|
||||
|
||||
@@ -12,6 +12,85 @@ type compositeRouteRepoStub struct {
|
||||
routes []CompositeModelRoute
|
||||
}
|
||||
|
||||
func TestCompositeRouteResolverCodexFilteringMatchesProductionPriority(t *testing.T) {
|
||||
for _, scenario := range []struct {
|
||||
name string
|
||||
routes []CompositeModelRoute
|
||||
want []string
|
||||
}{
|
||||
{
|
||||
name: "endpoint beats priority",
|
||||
routes: []CompositeModelRoute{
|
||||
{ID: 1, PublicModel: "alias", MatchType: CompositeRouteMatchExact, Endpoint: CompositeRouteEndpointAny, TargetPlatform: PlatformOpenAI, Priority: 1},
|
||||
{ID: 2, PublicModel: "alias", MatchType: CompositeRouteMatchExact, Endpoint: CompositeRouteEndpointResponses, TargetPlatform: PlatformTypeSafe, Priority: 100},
|
||||
},
|
||||
want: []string{"unrouted"},
|
||||
},
|
||||
{
|
||||
name: "Responses OpenAI overrides any TypeSafe",
|
||||
routes: []CompositeModelRoute{
|
||||
{ID: 1, PublicModel: "alias", MatchType: CompositeRouteMatchExact, Endpoint: CompositeRouteEndpointAny, TargetPlatform: PlatformTypeSafe, Priority: 1},
|
||||
{ID: 2, PublicModel: "alias", MatchType: CompositeRouteMatchExact, Endpoint: CompositeRouteEndpointResponses, TargetPlatform: PlatformOpenAI, Priority: 100},
|
||||
},
|
||||
want: []string{"alias", "unrouted"},
|
||||
},
|
||||
{
|
||||
name: "longer prefix beats priority",
|
||||
routes: []CompositeModelRoute{
|
||||
{ID: 1, PublicModel: "a", MatchType: CompositeRouteMatchPrefix, TargetPlatform: PlatformOpenAI, Priority: 1},
|
||||
{ID: 2, PublicModel: "ali", MatchType: CompositeRouteMatchPrefix, TargetPlatform: PlatformTypeSafe, Priority: 100},
|
||||
},
|
||||
want: []string{"unrouted"},
|
||||
},
|
||||
{
|
||||
name: "exact beats endpoint prefix",
|
||||
routes: []CompositeModelRoute{
|
||||
{ID: 1, PublicModel: "alias", MatchType: CompositeRouteMatchPrefix, Endpoint: CompositeRouteEndpointResponses, TargetPlatform: PlatformTypeSafe},
|
||||
{ID: 2, PublicModel: "alias", MatchType: CompositeRouteMatchExact, Endpoint: CompositeRouteEndpointAny, TargetPlatform: PlatformOpenAI},
|
||||
},
|
||||
want: []string{"alias", "unrouted"},
|
||||
},
|
||||
{
|
||||
name: "lower priority wins",
|
||||
routes: []CompositeModelRoute{
|
||||
{ID: 1, PublicModel: "alias", MatchType: CompositeRouteMatchExact, TargetPlatform: PlatformTypeSafe, Priority: 100},
|
||||
{ID: 2, PublicModel: "alias", MatchType: CompositeRouteMatchExact, TargetPlatform: PlatformOpenAI, Priority: 1},
|
||||
},
|
||||
want: []string{"alias", "unrouted"},
|
||||
},
|
||||
{
|
||||
name: "lower ID breaks tie",
|
||||
routes: []CompositeModelRoute{
|
||||
{ID: 2, PublicModel: "alias", MatchType: CompositeRouteMatchExact, TargetPlatform: PlatformOpenAI},
|
||||
{ID: 1, PublicModel: "alias", MatchType: CompositeRouteMatchExact, TargetPlatform: PlatformTypeSafe},
|
||||
},
|
||||
want: []string{"unrouted"},
|
||||
},
|
||||
} {
|
||||
t.Run(scenario.name, func(t *testing.T) {
|
||||
for index := range scenario.routes {
|
||||
scenario.routes[index].Enabled = true
|
||||
scenario.routes[index].GroupID = 7
|
||||
}
|
||||
resolver := NewCompositeRouteResolver(compositeRouteRepoStub{routes: scenario.routes})
|
||||
models, err := resolver.FilterCodexModels(context.Background(), 7, []string{"alias", "unrouted"})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, scenario.want, models)
|
||||
decision, err := resolver.Resolve(context.Background(), 7, "alias", CompositeRouteEndpointResponses)
|
||||
require.NoError(t, err)
|
||||
require.True(t, decision.Matched)
|
||||
require.Equal(t, decision.TargetPlatform != PlatformTypeSafe, len(models) == 2)
|
||||
exactModels, err := resolver.ListExactPublicModels(context.Background(), 7, CompositeRouteEndpointResponses, false)
|
||||
require.NoError(t, err)
|
||||
if decision.TargetPlatform == PlatformTypeSafe {
|
||||
require.Empty(t, exactModels)
|
||||
} else {
|
||||
require.Contains(t, exactModels, "alias")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (s compositeRouteRepoStub) ListByGroup(ctx context.Context, groupID int64, includeDisabled bool) ([]CompositeModelRoute, error) {
|
||||
routes := make([]CompositeModelRoute, 0, len(s.routes))
|
||||
for _, route := range s.routes {
|
||||
|
||||
@@ -1535,6 +1535,21 @@ func explicitModelMappingClaims(account Account, model string) bool {
|
||||
return ok && strings.TrimSpace(mapped) != ""
|
||||
}
|
||||
|
||||
// GetCompositeRouteModels returns public IDs from enabled exact composite routes.
|
||||
func (s *GatewayService) GetCompositeRouteModels(ctx context.Context, groupID *int64, endpoint string, includeSystemOne bool) ([]string, error) {
|
||||
if s == nil || s.compositeResolver == nil || groupID == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return s.compositeResolver.ListExactPublicModels(ctx, *groupID, endpoint, includeSystemOne)
|
||||
}
|
||||
|
||||
func (s *GatewayService) FilterCompositeCodexModels(ctx context.Context, groupID int64, models []string) ([]string, error) {
|
||||
if s == nil || s.compositeResolver == nil {
|
||||
return models, nil
|
||||
}
|
||||
return s.compositeResolver.FilterCodexModels(ctx, groupID, models)
|
||||
}
|
||||
|
||||
// GetSchedulablePlatforms returns the concrete platforms that currently have
|
||||
// schedulable accounts in the target group.
|
||||
func (s *GatewayService) GetSchedulablePlatforms(ctx context.Context, groupID *int64) map[string]struct{} {
|
||||
|
||||
Reference in New Issue
Block a user