Merge pull request #5267 from wucm667/fix/issue-5264-model-plaza-composite

fix(model-plaza): show Composite group models
This commit is contained in:
Wesley Liddick
2026-08-07 16:17:52 +08:00
committed by GitHub
4 changed files with 117 additions and 10 deletions
+23 -8
View File
@@ -28,7 +28,8 @@ type PlazaModel struct {
// PlazaGroup 模型广场中以分组为顶层的条目。
//
// 与 AvailableGroupRef 相比多了 Description 与 Models;Models 来自该分组关联渠道的
// 支持模型(按分组平台隔离,防跨平台泄漏),与「可用渠道」页口径一致。
// 支持模型(普通分组按分组平台隔离,Composite 分组展开关联渠道已配置的
// 具体平台),与「可用渠道」页口径一致。
type PlazaGroup struct {
ID int64
Name string
@@ -99,8 +100,12 @@ func (s *ChannelService) ListPlazaGroups(ctx context.Context) ([]PlazaGroup, err
order = append(order, g.ID)
}
// modelIdx[groupID][modelName] = index into byGroup[groupID].Models
modelIdx := make(map[int64]map[string]int, len(groups))
type modelKey struct {
platform string
name string
}
// modelIdx[groupID][platform+modelName] = index into byGroup[groupID].Models
modelIdx := make(map[int64]map[modelKey]int, len(groups))
for i := range channels {
ch := &channels[i]
if ch.Status != StatusActive {
@@ -117,23 +122,28 @@ func (s *ChannelService) ListPlazaGroups(ctx context.Context) ([]PlazaGroup, err
}
idx := modelIdx[gid]
if idx == nil {
idx = make(map[string]int, len(supported))
idx = make(map[modelKey]int, len(supported))
modelIdx[gid] = idx
}
for j := range supported {
m := supported[j]
if m.Platform != pg.Platform {
if pg.Platform == PlatformComposite {
if !isConcreteRequestPlatform(m.Platform) {
continue
}
} else if m.Platform != pg.Platform {
continue
}
pricing := plazaImageDisplayPricing(m.Pricing, groupEnt[gid])
if at, seen := idx[m.Name]; seen {
key := modelKey{platform: m.Platform, name: m.Name}
if at, seen := idx[key]; seen {
// 先见者胜;仅当已存条目无定价而新条目有定价时升级。
if pg.Models[at].Pricing == nil && pricing != nil {
pg.Models[at].Pricing = pricing
}
continue
}
idx[m.Name] = len(pg.Models)
idx[key] = len(pg.Models)
pg.Models = append(pg.Models, PlazaModel{
Name: m.Name,
Platform: m.Platform,
@@ -150,7 +160,12 @@ func (s *ChannelService) ListPlazaGroups(ctx context.Context) ([]PlazaGroup, err
if len(pg.Models) == 0 {
continue
}
sort.SliceStable(pg.Models, func(i, j int) bool { return pg.Models[i].Name < pg.Models[j].Name })
sort.SliceStable(pg.Models, func(i, j int) bool {
if pg.Models[i].Name != pg.Models[j].Name {
return pg.Models[i].Name < pg.Models[j].Name
}
return pg.Models[i].Platform < pg.Models[j].Platform
})
for j := range pg.Models {
pg.Models[j].OfficialPricing = s.lookupOfficialPricing(pg.Models[j].Name, officialMemo)
}
@@ -107,6 +107,68 @@ func TestListPlazaGroups_PlatformIsolation(t *testing.T) {
require.Equal(t, "gpt-5", byName["g-gpt"][0].Name)
}
func TestListPlazaGroups_CompositeIncludesConfiguredConcretePlatforms(t *testing.T) {
anthropicPrice := 3e-6
openAIPrice := 2e-6
ch := Channel{
ID: 1, Name: "multi", Status: StatusActive, GroupIDs: []int64{10},
ModelPricing: []ChannelModelPricing{
{Platform: PlatformAnthropic, Models: []string{"shared-model"}, InputPrice: &anthropicPrice},
{Platform: PlatformOpenAI, Models: []string{"shared-model"}, InputPrice: &openAIPrice},
{Platform: "", Models: []string{"empty-platform"}},
{Platform: PlatformComposite, Models: []string{"nested-composite"}},
{Platform: "unknown-platform", Models: []string{"unknown-platform"}},
},
}
groups := []Group{{ID: 10, Name: "composite", Platform: PlatformComposite, RateMultiplier: 1}}
out, err := newPlazaChannelService([]Channel{ch}, groups, nil).ListPlazaGroups(context.Background())
require.NoError(t, err)
require.Len(t, out, 1)
require.Len(t, out[0].Models, 2, "only concrete platforms are included and same-named models remain distinct")
require.Equal(t, PlatformAnthropic, out[0].Models[0].Platform)
require.Equal(t, PlatformOpenAI, out[0].Models[1].Platform)
require.InDelta(t, anthropicPrice, *out[0].Models[0].Pricing.InputPrice, 1e-12)
require.InDelta(t, openAIPrice, *out[0].Models[1].Pricing.InputPrice, 1e-12)
}
func TestListPlazaGroups_CompositeAndOrdinaryGroupsDoNotLeakPlatforms(t *testing.T) {
ch := Channel{
ID: 1, Name: "multi", Status: StatusActive, GroupIDs: []int64{10, 20},
ModelPricing: []ChannelModelPricing{
{Platform: PlatformAnthropic, Models: []string{"claude-sonnet"}, InputPrice: testPtrFloat64(3e-6)},
{Platform: PlatformOpenAI, Models: []string{"gpt-5"}, InputPrice: testPtrFloat64(2e-6)},
},
}
groups := []Group{
{ID: 10, Name: "anthropic-only", Platform: PlatformAnthropic, RateMultiplier: 1},
{ID: 20, Name: "composite", Platform: PlatformComposite, RateMultiplier: 1},
}
out, err := newPlazaChannelService([]Channel{ch}, groups, nil).ListPlazaGroups(context.Background())
require.NoError(t, err)
require.Len(t, out, 2)
byName := map[string]PlazaGroup{}
for _, group := range out {
byName[group.Name] = group
}
require.Len(t, byName["anthropic-only"].Models, 1)
require.Equal(t, []PlazaModel{{
Name: "claude-sonnet", Platform: PlatformAnthropic, Pricing: byName["anthropic-only"].Models[0].Pricing,
}}, byName["anthropic-only"].Models)
require.Len(t, byName["composite"].Models, 2)
require.Equal(t, []string{"claude-sonnet", "gpt-5"}, []string{
byName["composite"].Models[0].Name,
byName["composite"].Models[1].Name,
})
require.Equal(t, []string{PlatformAnthropic, PlatformOpenAI}, []string{
byName["composite"].Models[0].Platform,
byName["composite"].Models[1].Platform,
})
}
func TestListPlazaGroups_InactiveChannelSkipped(t *testing.T) {
inactive := plazaPricedChannel(1, "off", []int64{10}, "anthropic", "claude-sonnet")
inactive.Status = "inactive"