refactor(price): improve handling of other ratios in PriceData
This commit is contained in:
+18
-10
@@ -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(饱和转换,防止溢出成负数)
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user