From 04d1c437cc23849da9a478716a24237e099ccea9 Mon Sep 17 00:00:00 2001 From: think-back <71199016+think-back@users.noreply.github.com> Date: Tue, 11 Aug 2026 19:51:25 +0800 Subject: [PATCH] Expose per-model group ratios on the public PLG pricing payload MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Billing resolves a model's discount through GetEffectiveGroupRatio, which checks GroupModelRatio (group x model) before falling back to the flat group ratio. The public PLG payload only carried the flat ratio, so any model priced below its group — a domestic model set to 0.6 while plg sits at 0.9, say — was quoted on /models at a price higher than it is charged. buildWebsitePublicGroupPricingPayload now emits group_model_ratio, reusing filterGroupModelRatioByUsableGroupsAndModels so the map is restricted to the plg group and to models the payload actually exposes. Models removed by the hidden-models filter drop out of the ratio map too, since it is built from the already-filtered pricing list. pricing_version moves to website-public-plg-v2. No website change was needed: enrichVendorNames already folds group_model_ratio into each model's group_ratio via buildEffectiveGroupRatio, and getBestGroupRatio reads from there. The override simply never arrived. Tests were added on both sides to pin the precedence — per-model first, group ratio second — so the wiring cannot silently regress. This is display-only. Actual billing already applied the override. --- controller/pricing.go | 16 ++-- controller/pricing_test.go | 48 +++++++++++- .../src/lib/group-model-ratio-pricing.test.ts | 73 +++++++++++++++++++ 3 files changed, 131 insertions(+), 6 deletions(-) create mode 100644 website/src/lib/group-model-ratio-pricing.test.ts diff --git a/controller/pricing.go b/controller/pricing.go index d4e1f9578f8..02ca6bac574 100644 --- a/controller/pricing.go +++ b/controller/pricing.go @@ -303,16 +303,22 @@ func buildWebsitePublicGroupPricingPayload( description = group } usableGroup := map[string]string{group: description} + visiblePricing := filterHiddenPricingModels(filterPricingByUsableGroups(pricing, usableGroup)) return gin.H{ - "success": true, - "data": filterHiddenPricingModels(filterPricingByUsableGroups(pricing, usableGroup)), - "vendors": vendors, - "group_ratio": map[string]float64{group: ratio}, + "success": true, + "data": visiblePricing, + "vendors": vendors, + "group_ratio": map[string]float64{group: ratio}, + // Per-model overrides beat the flat group ratio during billing + // (ratio_setting.GetEffectiveGroupRatio), so the public payload has to + // carry them too — otherwise a model priced below the group ratio is + // quoted higher than it is actually charged. + "group_model_ratio": filterGroupModelRatioByUsableGroupsAndModels(ratio_setting.GetGroupModelRatioCopy(), usableGroup, visiblePricing), "usable_group": usableGroup, "supported_endpoint": supportedEndpoint, "auto_groups": autoGroups, - "pricing_version": "website-public-plg-v1", + "pricing_version": "website-public-plg-v2", } } diff --git a/controller/pricing_test.go b/controller/pricing_test.go index aadde30b405..18e18f45a6c 100644 --- a/controller/pricing_test.go +++ b/controller/pricing_test.go @@ -227,10 +227,11 @@ func TestBuildWebsitePublicGroupPricingPayloadIncludesHiddenPLGOnly(t *testing.T ], "vendors": null, "group_ratio": {"plg": 0.9}, + "group_model_ratio": {}, "usable_group": {"plg": "plg"}, "supported_endpoint": null, "auto_groups": null, - "pricing_version": "website-public-plg-v1" + "pricing_version": "website-public-plg-v2" }`, string(body)) } @@ -278,3 +279,48 @@ func TestFilterHiddenPricingModelsDoesNotMutateInput(t *testing.T) { require.Len(t, pricing, 2) require.Equal(t, "gpt-4o", pricing[0].ModelName) } + +func withGroupModelRatio(t *testing.T, value string) { + t.Helper() + previous := ratio_setting.GroupModelRatio2JSONString() + t.Cleanup(func() { + _ = ratio_setting.UpdateGroupModelRatioByJSONString(previous) + }) + require.NoError(t, ratio_setting.UpdateGroupModelRatioByJSONString(value)) +} + +// The public PLG payload must expose per-model group ratios. Without them the +// website falls back to the flat plg ratio and quotes a price the user does +// not actually pay when a model is configured cheaper for plg. +func TestBuildWebsitePublicGroupPricingPayloadExposesGroupModelRatio(t *testing.T) { + withGroupModelRatio(t, `{"plg":{"glm-5":0.6,"hidden-elsewhere":0.5},"vip":{"glm-5":0.4}}`) + + pricing := []model.Pricing{ + {ModelName: "glm-5", EnableGroup: []string{"plg"}}, + {ModelName: "gpt-4o", EnableGroup: []string{"plg"}}, + } + + payload := buildWebsitePublicGroupPricingPayload(pricing, nil, nil, nil, "plg", 0.9) + + groupModelRatio, ok := payload["group_model_ratio"].(map[string]map[string]float64) + require.True(t, ok, "group_model_ratio must be present") + require.Equal(t, map[string]map[string]float64{ + "plg": {"glm-5": 0.6}, + }, groupModelRatio) +} + +func TestBuildWebsitePublicGroupPricingPayloadOmitsHiddenModelRatios(t *testing.T) { + withGroupModelRatio(t, `{"plg":{"glm-5":0.6,"secret-model":0.3}}`) + withHiddenPricingModels(t, "secret-model") + + pricing := []model.Pricing{ + {ModelName: "glm-5", EnableGroup: []string{"plg"}}, + {ModelName: "secret-model", EnableGroup: []string{"plg"}}, + } + + payload := buildWebsitePublicGroupPricingPayload(pricing, nil, nil, nil, "plg", 0.9) + + require.Equal(t, map[string]map[string]float64{ + "plg": {"glm-5": 0.6}, + }, payload["group_model_ratio"]) +} diff --git a/website/src/lib/group-model-ratio-pricing.test.ts b/website/src/lib/group-model-ratio-pricing.test.ts new file mode 100644 index 00000000000..8146e4f7a8b --- /dev/null +++ b/website/src/lib/group-model-ratio-pricing.test.ts @@ -0,0 +1,73 @@ +import { describe, expect, test } from "bun:test"; +import { buildRowsForModels } from "./home-models"; +import { + buildEffectiveGroupRatio, + formatModelPrice, + getBestGroupRatio, + type PricingData, + type PricingModel, +} from "./pricing"; + +const VENDORS: PricingData["vendors"] = [{ id: 1, name: "Zhipu" }]; + +// A domestic model billed cheaper than the flat plg ratio: GroupModelRatio +// gives plg/glm-5 a 0.6 override while the plg group ratio is 0.9. +const GROUP_RATIO = { plg: 0.9 }; +const GROUP_MODEL_RATIO = { plg: { "glm-5": 0.6 } }; + +const GLM: PricingModel = { + model_name: "glm-5", + vendor_id: 1, + quota_type: 0, + model_ratio: 0.3, // official $0.60 / 1M input + completion_ratio: 1, + enable_groups: ["plg"], +}; + +// No per-model override: falls back to the flat plg group ratio. +const GPT: PricingModel = { + model_name: "gpt-4.1-mini", + vendor_id: 1, + quota_type: 0, + model_ratio: 0.2, // official $0.40 / 1M input + completion_ratio: 1, + enable_groups: ["plg"], +}; + +function enrich(model: PricingModel): PricingModel { + return { + ...model, + group_ratio: buildEffectiveGroupRatio(model, GROUP_RATIO, GROUP_MODEL_RATIO), + }; +} + +describe("per-model group ratio takes precedence over the flat group ratio", () => { + test("buildEffectiveGroupRatio applies the model override", () => { + expect(buildEffectiveGroupRatio(GLM, GROUP_RATIO, GROUP_MODEL_RATIO)).toEqual({ plg: 0.6 }); + expect(buildEffectiveGroupRatio(GPT, GROUP_RATIO, GROUP_MODEL_RATIO)).toEqual({ plg: 0.9 }); + }); + + test("getBestGroupRatio prefers the model override", () => { + expect(getBestGroupRatio(enrich(GLM), GROUP_RATIO)).toBe(0.6); + expect(getBestGroupRatio(enrich(GPT), GROUP_RATIO)).toBe(0.9); + }); + + test("formatModelPrice quotes the overridden ratio, not the group ratio", () => { + // 0.3 x 2 x 0.6 = 0.36. Using the flat 0.9 would over-quote at $0.54. + expect(formatModelPrice(enrich(GLM), "input")).toBe("$0.36"); + expect(formatModelPrice(enrich(GPT), "input")).toBe("$0.36"); + }); + + test("directory rows quote the overridden ratio", () => { + const rows = buildRowsForModels([enrich(GLM), enrich(GPT)], VENDORS, GROUP_RATIO); + + expect(rows[0].official).toBe("$0.6"); + expect(rows[0].discounted).toBe("$0.36"); // 0.6 x 0.6, not 0.6 x 0.9 + expect(rows[1].official).toBe("$0.4"); + expect(rows[1].discounted).toBe("$0.36"); // 0.4 x 0.9 + }); + + test("an unenriched model still falls back to the flat group ratio", () => { + expect(getBestGroupRatio(GLM, GROUP_RATIO)).toBe(0.9); + }); +});