feat(billing): support configurable reasoning effort multipliers

This commit is contained in:
haruka
2026-09-20 16:41:49 +08:00
parent acdf3c54da
commit b8d52fad34
52 changed files with 1709 additions and 354 deletions
@@ -449,15 +449,14 @@ func TestPricingRequestToService_TimePricingNil(t *testing.T) {
require.Nil(t, got[0].TimePricing)
}
// 账号成本统计规则不支持倍率:allowChannelMultipliers=false 时必须丢弃,
// 避免渠道倍率意外污染账号成本口径。
// Fast/Flex 和区间倍率只用于渠道售价;思考等级倍率可在账号统计规则中独立配置。
func TestPricingRequestToService_MultipliersGatedByFlag(t *testing.T) {
req := channelModelPricingRequest{
Models: []string{"gpt-5"},
BillingMode: "token",
FastMultiplier: float64Ptr(2.5),
FlexMultiplier: float64Ptr(0.5),
MaxReasoningEffortMultiplier: float64Ptr(3),
Models: []string{"gpt-5"},
BillingMode: "token",
FastMultiplier: float64Ptr(2.5),
FlexMultiplier: float64Ptr(0.5),
ReasoningEffortMultipliers: map[string]float64{"high": 1.5, "max": 3},
Intervals: []pricingIntervalRequest{{
MinTokens: 272000,
InputMultiplier: float64Ptr(2),
@@ -470,7 +469,7 @@ func TestPricingRequestToService_MultipliersGatedByFlag(t *testing.T) {
allowed := pricingRequestToService([]channelModelPricingRequest{req}, true)
require.Equal(t, float64Ptr(2.5), allowed[0].FastMultiplier)
require.Equal(t, float64Ptr(0.5), allowed[0].FlexMultiplier)
require.Equal(t, float64Ptr(3), allowed[0].MaxReasoningEffortMultiplier)
require.Equal(t, req.ReasoningEffortMultipliers, allowed[0].ReasoningEffortMultipliers)
require.Equal(t, float64Ptr(2), allowed[0].Intervals[0].InputMultiplier)
require.Equal(t, float64Ptr(1.5), allowed[0].Intervals[0].OutputMultiplier)
require.Equal(t, float64Ptr(2), allowed[0].Intervals[0].CacheWriteMultiplier)
@@ -479,7 +478,7 @@ func TestPricingRequestToService_MultipliersGatedByFlag(t *testing.T) {
dropped := pricingRequestToService([]channelModelPricingRequest{req}, false)
require.Nil(t, dropped[0].FastMultiplier)
require.Nil(t, dropped[0].FlexMultiplier)
require.Nil(t, dropped[0].MaxReasoningEffortMultiplier)
require.Equal(t, req.ReasoningEffortMultipliers, dropped[0].ReasoningEffortMultipliers)
require.Nil(t, dropped[0].Intervals[0].InputMultiplier)
require.Nil(t, dropped[0].Intervals[0].OutputMultiplier)
require.Nil(t, dropped[0].Intervals[0].CacheWriteMultiplier)
@@ -511,6 +510,23 @@ func TestPricingToResponse_TimePricingNil(t *testing.T) {
require.Nil(t, got.TimePricing)
}
func TestPricingRequestAndResponse_ReasoningEffortMultipliers(t *testing.T) {
var req channelModelPricingRequest
require.NoError(t, json.Unmarshal([]byte(`{"models":["custom-model"],"reasoning_effort_multipliers":{"none":0.5,"high":1.5,"max":3}}`), &req))
pricing := pricingRequestToService([]channelModelPricingRequest{req}, true)
got := pricingToResponse(&pricing[0])
require.Equal(t, map[string]float64{"none": 0.5, "high": 1.5, "max": 3}, got.ReasoningEffortMultipliers)
data, err := json.Marshal(got)
require.NoError(t, err)
require.Contains(t, string(data), `"reasoning_effort_multipliers":{"high":1.5,"max":3,"none":0.5}`)
require.NotContains(t, string(data), "max_reasoning_effort_multiplier")
req = channelModelPricingRequest{}
require.NoError(t, json.Unmarshal([]byte(`{"models":["custom-model"],"reasoning_effort_multipliers":{}}`), &req))
pricing = pricingRequestToService([]channelModelPricingRequest{req}, true)
require.Empty(t, pricing[0].ReasoningEffortMultipliers)
}
// ---------------------------------------------------------------------------
// 3. SyncPricingModels handler
// ---------------------------------------------------------------------------
@@ -583,10 +599,10 @@ func TestGetModelDefaultPricing_ReturnsFable51CacheTTLs(t *testing.T) {
require.Equal(t, http.StatusOK, w.Code)
var body struct {
Data struct {
Found bool `json:"found"`
CacheWritePrice float64 `json:"cache_write_price"`
CacheWrite1hPrice *float64 `json:"cache_write_1h_price"`
MaxReasoningEffortMultiplier *float64 `json:"max_reasoning_effort_multiplier"`
Found bool `json:"found"`
CacheWritePrice float64 `json:"cache_write_price"`
CacheWrite1hPrice *float64 `json:"cache_write_1h_price"`
ReasoningEffortMultipliers map[string]float64 `json:"reasoning_effort_multipliers"`
} `json:"data"`
}
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &body))
@@ -594,8 +610,7 @@ func TestGetModelDefaultPricing_ReturnsFable51CacheTTLs(t *testing.T) {
require.InDelta(t, 12.5e-6, body.Data.CacheWritePrice, 1e-12)
require.NotNil(t, body.Data.CacheWrite1hPrice)
require.InDelta(t, 20e-6, *body.Data.CacheWrite1hPrice, 1e-12)
require.NotNil(t, body.Data.MaxReasoningEffortMultiplier)
require.Equal(t, 3.0, *body.Data.MaxReasoningEffortMultiplier)
require.Empty(t, body.Data.ReasoningEffortMultipliers)
}
func TestGetModelDefaultPricing_OmitsUnsupportedCache1hPrice(t *testing.T) {