fix(composite): route exact account model aliases

Composite model catalogs expose exact account model_mapping aliases, but request routing only considered explicit routes or model-name detection. Unprefixed aliases were selectable but unroutable.

Resolve unique exact account ownership before the detector, keep explicit routes authoritative, and fail closed when an alias is claimed across platforms.
This commit is contained in:
Long Li
2026-08-22 14:43:40 +09:00
parent e471be7302
commit 3e98a5a1ad
6 changed files with 409 additions and 3 deletions
@@ -8,6 +8,150 @@ import (
"github.com/stretchr/testify/require"
)
type compositeOwnershipAccountRepo struct {
AccountRepository
accounts []Account
}
func (r *compositeOwnershipAccountRepo) ListSchedulableByGroupID(context.Context, int64) ([]Account, error) {
return r.accounts, nil
}
// Scenario: 唯一平台的精确别名可路由
func TestResolveCompositeModelOwnershipKeepsProviderAccountsIsolated(t *testing.T) {
groupID := int64(7)
repo := &compositeOwnershipAccountRepo{
accounts: []Account{
{
ID: 1,
Platform: PlatformOpenAI,
Credentials: map[string]any{
"model_mapping": map[string]any{"gpt-public": "gpt-5"},
},
},
{
ID: 2,
Platform: PlatformDeepseek,
Credentials: map[string]any{
"model_mapping": map[string]any{"reasoning-alias": "deepseek-v4-pro"},
},
},
},
}
svc := &GatewayService{accountRepo: repo}
deepSeekOwnership, err := svc.resolveCompositeModelOwnership(context.Background(), groupID, "reasoning-alias")
require.NoError(t, err)
require.Equal(t, CompositeModelOwnership{TargetPlatform: PlatformDeepseek, Matched: true}, deepSeekOwnership)
openAIOwnership, err := svc.resolveCompositeModelOwnership(context.Background(), groupID, "gpt-public")
require.NoError(t, err)
require.Equal(t, CompositeModelOwnership{TargetPlatform: PlatformOpenAI, Matched: true}, openAIOwnership)
}
// Scenario: 通配符和空映射不声明所有权
func TestResolveCompositeModelOwnershipRequiresNonEmptyExactMappings(t *testing.T) {
groupID := int64(7)
repo := &compositeOwnershipAccountRepo{
accounts: []Account{
{
ID: 1,
Platform: PlatformOpenAI,
Credentials: map[string]any{
"model_mapping": map[string]any{"*": "gpt-5", "gpt-*": "gpt-5", "empty-alias": ""},
},
},
{
ID: 2,
Platform: PlatformGrok,
Credentials: map[string]any{
"model_mapping": map[string]any{"grok-public": "grok-4"},
},
},
},
}
svc := &GatewayService{accountRepo: repo}
for _, model := range []string{"gpt-5", "empty-alias", "unknown-alias"} {
ownership, err := svc.resolveCompositeModelOwnership(context.Background(), groupID, model)
require.NoError(t, err)
require.Equal(t, CompositeModelOwnership{}, ownership, "model=%s", model)
}
ownership, err := svc.resolveCompositeModelOwnership(context.Background(), groupID, "grok-public")
require.NoError(t, err)
require.Equal(t, CompositeModelOwnership{TargetPlatform: PlatformGrok, Matched: true}, ownership)
}
func TestResolveCompositeModelOwnershipAllowsSamePlatformAndRejectsCrossPlatformAliases(t *testing.T) {
groupID := int64(7)
repo := &compositeOwnershipAccountRepo{
accounts: []Account{
{ID: 1, Platform: PlatformOpenAI, Credentials: map[string]any{"model_mapping": map[string]any{"shared-openai": "gpt-5", "ambiguous": "gpt-5"}}},
{ID: 2, Platform: PlatformOpenAI, Credentials: map[string]any{"model_mapping": map[string]any{"shared-openai": "gpt-5.1"}}},
{ID: 3, Platform: PlatformDeepseek, Credentials: map[string]any{"model_mapping": map[string]any{"ambiguous": "deepseek-v4-pro"}}},
},
}
svc := &GatewayService{accountRepo: repo}
samePlatform, err := svc.resolveCompositeModelOwnership(context.Background(), groupID, "shared-openai")
require.NoError(t, err)
require.Equal(t, CompositeModelOwnership{TargetPlatform: PlatformOpenAI, Matched: true}, samePlatform)
ambiguous, err := svc.resolveCompositeModelOwnership(context.Background(), groupID, "ambiguous")
require.NoError(t, err)
require.Equal(t, CompositeModelOwnership{Ambiguous: true}, ambiguous)
}
func TestNewGatewayServiceWiresCompositeModelOwnershipResolver(t *testing.T) {
groupID := int64(7)
repo := &compositeOwnershipAccountRepo{
accounts: []Account{{
ID: 1,
Platform: PlatformDeepseek,
Credentials: map[string]any{"model_mapping": map[string]any{"reasoning-alias": "deepseek-v4-pro"}},
}},
}
resolver := NewCompositeRouteResolver(nil)
svc := 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,
resolver,
nil,
nil,
)
require.Same(t, resolver, svc.compositeResolver)
decision, err := resolver.Resolve(context.Background(), groupID, "reasoning-alias", CompositeRouteEndpointResponses)
require.NoError(t, err)
require.True(t, decision.Matched)
require.Equal(t, CompositeRouteSourceAccount, decision.Source)
require.Equal(t, PlatformDeepseek, decision.TargetPlatform)
}
func TestDetectModelPlatform(t *testing.T) {
tests := []struct {
name string