refactor(price): improve handling of other ratios in PriceData

This commit is contained in:
CaIon
2026-07-07 21:22:19 +08:00
parent 394b023dbf
commit fc1259f583
8 changed files with 256 additions and 56 deletions
+18 -10
View File
@@ -11,6 +11,7 @@ import (
"github.com/QuantumNous/new-api/model"
relaycommon "github.com/QuantumNous/new-api/relay/common"
"github.com/QuantumNous/new-api/setting/ratio_setting"
"github.com/QuantumNous/new-api/types"
"github.com/gin-gonic/gin"
)
@@ -23,9 +24,9 @@ func LogTaskConsumption(c *gin.Context, info *relaycommon.RelayInfo) {
if common.StringsContains(constant.TaskPricePatches, info.OriginModelName) {
logContent = fmt.Sprintf("%s,按次计费", logContent)
} else {
if len(info.PriceData.OtherRatios) > 0 {
if otherRatios := info.PriceData.OtherRatios(); len(otherRatios) > 0 {
var contents []string
for key, ra := range info.PriceData.OtherRatios {
for key, ra := range otherRatios {
if 1.0 != ra {
contents = append(contents, fmt.Sprintf("%s: %.2f", key, ra))
}
@@ -126,8 +127,8 @@ func taskBillingOther(task *model.Task) map[string]interface{} {
other["model_ratio"] = bc.ModelRatio
}
other["group_ratio"] = bc.GroupRatio
if len(bc.OtherRatios) > 0 {
for k, v := range bc.OtherRatios {
if priceData := taskBillingContextPriceData(bc); priceData != nil {
for k, v := range priceData.OtherRatios() {
other[k] = v
}
}
@@ -140,6 +141,17 @@ func taskBillingOther(task *model.Task) map[string]interface{} {
return other
}
func taskBillingContextPriceData(bc *model.TaskBillingContext) *types.PriceData {
if bc == nil || len(bc.OtherRatios) == 0 {
return nil
}
priceData := &types.PriceData{}
if !priceData.ReplaceOtherRatios(bc.OtherRatios) {
return nil
}
return priceData
}
// taskModelName 从 BillingContext 或 Properties 中获取模型名称。
func taskModelName(task *model.Task) string {
if bc := task.PrivateData.BillingContext; bc != nil && bc.OriginModelName != "" {
@@ -294,12 +306,8 @@ func RecalculateTaskQuotaByTokens(ctx context.Context, task *model.Task, totalTo
// 计算 OtherRatios 乘积(视频折扣、时长等)
otherMultiplier := 1.0
if bc := task.PrivateData.BillingContext; bc != nil {
for _, r := range bc.OtherRatios {
if r != 1.0 && r > 0 {
otherMultiplier *= r
}
}
if priceData := taskBillingContextPriceData(task.PrivateData.BillingContext); priceData != nil {
otherMultiplier = priceData.OtherRatioMultiplier()
}
// 计算实际应扣费额度: totalTokens * modelRatio * groupRatio * otherMultiplier(饱和转换,防止溢出成负数)
+99
View File
@@ -3,6 +3,7 @@ package service
import (
"context"
"encoding/json"
"math"
"net/http"
"os"
"testing"
@@ -11,7 +12,9 @@ import (
"github.com/QuantumNous/new-api/common"
"github.com/QuantumNous/new-api/model"
relaycommon "github.com/QuantumNous/new-api/relay/common"
"github.com/QuantumNous/new-api/types"
"github.com/glebarez/sqlite"
"github.com/shopspring/decimal"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gorm.io/gorm"
@@ -139,6 +142,102 @@ func makeTask(userId, channelId, quota, tokenId int, billingSource string, subsc
}
}
func TestPriceDataOtherRatiosFilterAndSnapshot(t *testing.T) {
priceData := types.PriceData{}
priceData.AddOtherRatio("zero", 0)
priceData.AddOtherRatio("negative", -0.5)
priceData.AddOtherRatio("nan", math.NaN())
priceData.AddOtherRatio("inf", math.Inf(1))
priceData.AddOtherRatio("one", 1)
priceData.AddOtherRatio("positive", 2.5)
ratios := priceData.OtherRatios()
require.Len(t, ratios, 2)
assert.Equal(t, 1.0, ratios["one"])
assert.Equal(t, 2.5, ratios["positive"])
assert.True(t, priceData.HasOtherRatio("one"))
assert.False(t, priceData.HasOtherRatio("zero"))
ratios["positive"] = 99
ratios["new"] = 3
nextSnapshot := priceData.OtherRatios()
assert.Equal(t, 2.5, nextSnapshot["positive"])
assert.NotContains(t, nextSnapshot, "new")
}
func TestPriceDataReplaceAndApplyOtherRatios(t *testing.T) {
priceData := types.PriceData{}
replaced := priceData.ReplaceOtherRatios(map[string]float64{
"zero": 0,
"negative": -3,
"nan": math.NaN(),
"inf": math.Inf(1),
"one": 1,
"duration": 2,
"size": 1.5,
})
require.True(t, replaced)
assert.Equal(t, 3.0, priceData.OtherRatioMultiplier())
assert.Equal(t, 30.0, priceData.ApplyOtherRatiosToFloat(10))
assert.Equal(t, 10.0, priceData.RemoveOtherRatiosFromFloat(30))
assert.True(t, decimal.NewFromInt(30).Equal(priceData.ApplyOtherRatiosToDecimal(decimal.NewFromInt(10))))
replaced = priceData.ReplaceOtherRatios(map[string]float64{
"zero": 0,
"nan": math.NaN(),
})
require.False(t, replaced)
assert.Nil(t, priceData.OtherRatios())
assert.Equal(t, 1.0, priceData.OtherRatioMultiplier())
}
func TestTaskBillingOtherFiltersHistoricalOtherRatios(t *testing.T) {
task := makeTask(1, 1, 100, 0, BillingSourceWallet, 0)
task.PrivateData.BillingContext.OtherRatios = map[string]float64{
"seconds": 2,
"identity": 1,
"zero": 0,
"negative": -1,
"nan": math.NaN(),
"inf": math.Inf(1),
}
other := taskBillingOther(task)
assert.Equal(t, 2.0, other["seconds"])
assert.Equal(t, 1.0, other["identity"])
assert.NotContains(t, other, "zero")
assert.NotContains(t, other, "negative")
assert.NotContains(t, other, "nan")
assert.NotContains(t, other, "inf")
}
func TestTaskBillingContextPriceDataFiltersMultiplier(t *testing.T) {
priceData := taskBillingContextPriceData(&model.TaskBillingContext{
OtherRatios: map[string]float64{
"seconds": 2,
"size": 3,
"identity": 1,
"zero": 0,
"negative": -1,
"nan": math.NaN(),
"inf": math.Inf(1),
},
})
require.NotNil(t, priceData)
assert.Equal(t, 6.0, priceData.OtherRatioMultiplier())
assert.Equal(t, map[string]float64{
"seconds": 2,
"size": 3,
"identity": 1,
}, priceData.OtherRatios())
}
// ---------------------------------------------------------------------------
// Read-back helpers
// ---------------------------------------------------------------------------
+2 -11
View File
@@ -296,12 +296,7 @@ func calculateTextQuotaSummary(ctx *gin.Context, relayInfo *relaycommon.RelayInf
quotaCalculateDecimal := promptQuota.Add(completionQuota).Mul(ratio)
quotaCalculateDecimal = quotaCalculateDecimal.Add(summary.ToolCallSurchargeQuota)
quotaCalculateDecimal = quotaCalculateDecimal.Add(audioInputQuota)
if len(relayInfo.PriceData.OtherRatios) > 0 {
for _, otherRatio := range relayInfo.PriceData.OtherRatios {
quotaCalculateDecimal = quotaCalculateDecimal.Mul(decimal.NewFromFloat(otherRatio))
}
}
quotaCalculateDecimal = relayInfo.PriceData.ApplyOtherRatiosToDecimal(quotaCalculateDecimal)
if !ratio.IsZero() && quotaCalculateDecimal.LessThanOrEqual(decimal.Zero) {
quotaCalculateDecimal = decimal.NewFromInt(1)
@@ -313,11 +308,7 @@ func calculateTextQuotaSummary(ctx *gin.Context, relayInfo *relaycommon.RelayInf
quotaCalculateDecimal := dModelPrice.Mul(dQuotaPerUnit).Mul(dGroupRatio)
quotaCalculateDecimal = quotaCalculateDecimal.Add(summary.ToolCallSurchargeQuota)
quotaCalculateDecimal = quotaCalculateDecimal.Add(audioInputQuota)
if len(relayInfo.PriceData.OtherRatios) > 0 {
for _, otherRatio := range relayInfo.PriceData.OtherRatios {
quotaCalculateDecimal = quotaCalculateDecimal.Mul(decimal.NewFromFloat(otherRatio))
}
}
quotaCalculateDecimal = relayInfo.PriceData.ApplyOtherRatiosToDecimal(quotaCalculateDecimal)
quota, clamp := common.QuotaFromDecimalChecked(quotaCalculateDecimal)
summary.Quota = quota
noteQuotaClamp(relayInfo, clamp)