mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-27 00:04:32 +00:00
Merge pull request #4839 from visa2/fix/composite-route-prefix-passthrough
fix(composite): pass the requested model through when a prefix route leaves upstream_model empty
This commit is contained in:
@@ -1588,7 +1588,8 @@ func TestAdminService_CreateCompositeRoute_NormalizesAndPersists(t *testing.T) {
|
||||
require.Equal(t, "router/gpt-", route.PublicModel)
|
||||
require.Equal(t, CompositeRouteMatchPrefix, route.MatchType)
|
||||
require.Equal(t, PlatformOpenAI, route.TargetPlatform)
|
||||
require.Equal(t, "router/gpt-", route.UpstreamModel)
|
||||
// prefix 路由留空 upstream_model 不再回填 public_model:留空表示透传原始请求模型。
|
||||
require.Equal(t, "", route.UpstreamModel)
|
||||
require.Equal(t, CompositeRouteEndpointResponses, route.Endpoint)
|
||||
require.Equal(t, 100, route.Priority)
|
||||
require.True(t, route.Enabled)
|
||||
@@ -1596,6 +1597,29 @@ func TestAdminService_CreateCompositeRoute_NormalizesAndPersists(t *testing.T) {
|
||||
require.Equal(t, route, routeRepo.created)
|
||||
}
|
||||
|
||||
// TestAdminService_CreateCompositeRoute_ExactEmptyUpstreamBackfillsPublicModel 锁定
|
||||
// 保守行为:exact 路由留空 upstream_model 仍回填 public_model(持久化/展示契约不变)。
|
||||
func TestAdminService_CreateCompositeRoute_ExactEmptyUpstreamBackfillsPublicModel(t *testing.T) {
|
||||
groupRepo := &groupRepoStubForAdmin{
|
||||
getByID: &Group{ID: 7, Platform: PlatformComposite},
|
||||
}
|
||||
routeRepo := &compositeRouteRepoStubForAdmin{nextID: 99}
|
||||
svc := &adminServiceImpl{groupRepo: groupRepo, compositeRouteRepo: routeRepo}
|
||||
|
||||
route, err := svc.CreateCompositeRoute(context.Background(), 7, CompositeRouteInput{
|
||||
PublicModel: "openrouter/gpt-5",
|
||||
MatchType: CompositeRouteMatchExact,
|
||||
TargetPlatform: PlatformOpenAI,
|
||||
Endpoint: CompositeRouteEndpointResponses,
|
||||
Enabled: true,
|
||||
})
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, route)
|
||||
require.Equal(t, CompositeRouteMatchExact, route.MatchType)
|
||||
require.Equal(t, "openrouter/gpt-5", route.UpstreamModel)
|
||||
}
|
||||
|
||||
func TestAdminService_UpdateAndDeleteCompositeRouteRequireRouteOwnership(t *testing.T) {
|
||||
groupRepo := &groupRepoStubForAdmin{
|
||||
getByID: &Group{ID: 7, Platform: PlatformComposite},
|
||||
|
||||
@@ -118,7 +118,12 @@ func normalizeCompositeRouteInput(input CompositeRouteInput) CompositeRouteInput
|
||||
input.TargetPlatform = strings.TrimSpace(input.TargetPlatform)
|
||||
input.UpstreamModel = strings.TrimSpace(input.UpstreamModel)
|
||||
input.Endpoint = normalizeCompositeRouteEndpoint(input.Endpoint)
|
||||
if input.UpstreamModel == "" {
|
||||
// 仅对 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)
|
||||
|
||||
@@ -107,6 +107,63 @@ func TestCompositeRouteResolverPrefersEndpointSpecificLongestPrefix(t *testing.T
|
||||
require.Equal(t, int64(2), decision.Route.ID)
|
||||
}
|
||||
|
||||
// TestCompositeRouteResolverPrefixEmptyUpstreamPassesThroughRequestedModel 验证:
|
||||
// 前缀匹配路由留空 upstream_model 时,转发的是具体请求模型(各自原样),而不是
|
||||
// 塌缩成 public_model。这是「留空 = 透传原始模型」语义的核心场景。
|
||||
func TestCompositeRouteResolverPrefixEmptyUpstreamPassesThroughRequestedModel(t *testing.T) {
|
||||
resolver := NewCompositeRouteResolver(compositeRouteRepoStub{
|
||||
routes: []CompositeModelRoute{
|
||||
{
|
||||
ID: 1,
|
||||
GroupID: 7,
|
||||
PublicModel: "deepseek-v4",
|
||||
MatchType: CompositeRouteMatchPrefix,
|
||||
TargetPlatform: PlatformOpenAI,
|
||||
UpstreamModel: "", // 留空 = 透传
|
||||
Endpoint: CompositeRouteEndpointAny,
|
||||
Priority: 100,
|
||||
Enabled: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
for _, model := range []string{"deepseek-v4-flash", "deepseek-v4-pro", "deepseek-v4"} {
|
||||
decision, err := resolver.Resolve(context.Background(), 7, model, CompositeRouteEndpointChatCompletions)
|
||||
require.NoError(t, err)
|
||||
require.True(t, decision.Matched, "model %q should match prefix route", model)
|
||||
require.Equal(t, CompositeRouteSourceExplicit, decision.Source)
|
||||
require.Equal(t, PlatformOpenAI, decision.TargetPlatform)
|
||||
require.Equal(t, model, decision.UpstreamModel, "model %q should pass through verbatim", model)
|
||||
}
|
||||
}
|
||||
|
||||
// TestCompositeRouteResolverPrefixExplicitUpstreamStillFixed 验证:前缀匹配路由显式
|
||||
// 填写 upstream_model 时,所有命中请求仍转发同一个固定上游模型(行为不变)。
|
||||
func TestCompositeRouteResolverPrefixExplicitUpstreamStillFixed(t *testing.T) {
|
||||
resolver := NewCompositeRouteResolver(compositeRouteRepoStub{
|
||||
routes: []CompositeModelRoute{
|
||||
{
|
||||
ID: 1,
|
||||
GroupID: 7,
|
||||
PublicModel: "deepseek-v4",
|
||||
MatchType: CompositeRouteMatchPrefix,
|
||||
TargetPlatform: PlatformOpenAI,
|
||||
UpstreamModel: "deepseek-chat",
|
||||
Endpoint: CompositeRouteEndpointAny,
|
||||
Priority: 100,
|
||||
Enabled: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
for _, model := range []string{"deepseek-v4-flash", "deepseek-v4-pro"} {
|
||||
decision, err := resolver.Resolve(context.Background(), 7, model, CompositeRouteEndpointChatCompletions)
|
||||
require.NoError(t, err)
|
||||
require.True(t, decision.Matched)
|
||||
require.Equal(t, "deepseek-chat", decision.UpstreamModel)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompositeRouteResolverIgnoresDisabledRoutesAndFallsBackToDetector(t *testing.T) {
|
||||
resolver := NewCompositeRouteResolver(compositeRouteRepoStub{
|
||||
routes: []CompositeModelRoute{
|
||||
|
||||
Reference in New Issue
Block a user