refactor: deprecate int32 (#7025)
* refactor: deprecate int32 * fix(db): reject legacy user quota schemas at startup * fix(quota): enforce wallet bounds and saturating billing conversions * fix(rate-limit): keep count*duration from wrapping int64 * fix: error message
This commit is contained in:
+7
-3
@@ -3,7 +3,6 @@ package service
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -272,11 +271,16 @@ func CalcOpenRouterCacheCreateTokens(usage dto.Usage, priceData types.PriceData)
|
||||
completionTokens := float64(usage.CompletionTokens)
|
||||
promptCacheReadTokens := float64(usage.PromptTokensDetails.CachedTokens)
|
||||
|
||||
return int(math.Round((cost -
|
||||
value := (cost -
|
||||
totalPromptTokens*quotaPrice +
|
||||
promptCacheReadTokens*(quotaPrice-promptCacheReadPrice) -
|
||||
completionTokens*completionPrice) /
|
||||
(promptCacheCreatePrice - quotaPrice)))
|
||||
(promptCacheCreatePrice - quotaPrice)
|
||||
quota, clamp := common.QuotaRoundChecked(value)
|
||||
if clamp != nil {
|
||||
return -1
|
||||
}
|
||||
return quota
|
||||
}
|
||||
|
||||
func PostAudioConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, usage *dto.Usage, extraContent string) {
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"math"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/QuantumNous/new-api/common"
|
||||
relaycommon "github.com/QuantumNous/new-api/relay/common"
|
||||
"github.com/QuantumNous/new-api/relaykit/dto"
|
||||
"github.com/QuantumNous/new-api/relaykit/types"
|
||||
hosttypes "github.com/QuantumNous/new-api/types"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -42,6 +45,28 @@ func TestAttachQuotaSaturationNestsUnderAdminInfo(t *testing.T) {
|
||||
require.Equal(t, common.MaxQuota, sat["clamped"])
|
||||
}
|
||||
|
||||
func TestCalcViolationFeeQuotaSaturates(t *testing.T) {
|
||||
oldQuotaPerUnit := common.QuotaPerUnit
|
||||
common.QuotaPerUnit = 500_000
|
||||
t.Cleanup(func() { common.QuotaPerUnit = oldQuotaPerUnit })
|
||||
|
||||
require.Equal(t, common.MaxQuota, calcViolationFeeQuota(1e20, 1))
|
||||
}
|
||||
|
||||
func TestCalcOpenRouterCacheCreateTokensDoesNotWrap(t *testing.T) {
|
||||
oldQuotaPerUnit := common.QuotaPerUnit
|
||||
common.QuotaPerUnit = 500_000
|
||||
t.Cleanup(func() { common.QuotaPerUnit = oldQuotaPerUnit })
|
||||
|
||||
got := CalcOpenRouterCacheCreateTokens(dto.Usage{Cost: math.Inf(1)}, hosttypes.PriceData{
|
||||
ModelRatio: 1,
|
||||
CacheCreationRatio: 2,
|
||||
CacheRatio: 1,
|
||||
CompletionRatio: 1,
|
||||
})
|
||||
require.Equal(t, -1, got)
|
||||
}
|
||||
|
||||
// TestAttachQuotaSaturationPreservesExistingAdminInfo verifies the marker is
|
||||
// merged into a pre-existing admin_info map without clobbering it.
|
||||
func TestAttachQuotaSaturationPreservesExistingAdminInfo(t *testing.T) {
|
||||
|
||||
@@ -216,8 +216,8 @@ func composeTieredTextQuota(relayInfo *relaycommon.RelayInfo, summary textQuotaS
|
||||
}
|
||||
|
||||
// Saturate the final sum, not just the surcharge: tieredQuota can be near
|
||||
// MaxQuota and adding the surcharge could push the total past the int32
|
||||
// quota policy bound (persisted quota columns are 32-bit).
|
||||
// MaxQuota and adding the surcharge could push the total past the
|
||||
// single-request quota policy bound.
|
||||
total, clamp := common.QuotaFromDecimalChecked(
|
||||
decimal.NewFromInt(int64(tieredQuota)).Add(summary.ToolCallSurchargeQuota),
|
||||
)
|
||||
|
||||
@@ -774,9 +774,9 @@ func TestComposeTieredTextQuotaErrorFallbackUsesPreConsumedQuota(t *testing.T) {
|
||||
// settlement both saturates the quota and records the clamp on RelayInfo, so
|
||||
// every consume path (text, audio, WSS) can surface it under admin_info.
|
||||
func TestTryTieredSettleRecordsClampOnOverflow(t *testing.T) {
|
||||
// exprOutput = p * 1e9; quotaBeforeGroup = p*1e9 / 1e6 * 5e5 far exceeds
|
||||
// MaxInt32 and must saturate.
|
||||
exprStr := `tier("base", p * 1000000000)`
|
||||
// exprOutput = p * 1e12; quotaBeforeGroup = p*1e12 / 1e6 * 5e5 far exceeds
|
||||
// the supported single-request range and must saturate.
|
||||
exprStr := `tier("base", p * 1000000000000)`
|
||||
relayInfo := &relaycommon.RelayInfo{
|
||||
OriginModelName: "overflow-model",
|
||||
TieredBillingSnapshot: &billingexpr.BillingSnapshot{
|
||||
@@ -792,7 +792,7 @@ func TestTryTieredSettleRecordsClampOnOverflow(t *testing.T) {
|
||||
|
||||
require.True(t, ok)
|
||||
require.NotNil(t, result)
|
||||
require.Equal(t, math.MaxInt32, quota, "oversized settlement must clamp, never wrap negative")
|
||||
require.Equal(t, common.MaxQuota, quota, "oversized settlement must clamp, never wrap negative")
|
||||
require.NotNil(t, relayInfo.QuotaClamp, "clamp must be recorded on RelayInfo for admin auditing")
|
||||
require.Equal(t, common.QuotaClampOverflow, relayInfo.QuotaClamp.Kind)
|
||||
}
|
||||
|
||||
@@ -180,7 +180,7 @@ func TryTieredSettle(relayInfo *relaycommon.RelayInfo, params billingexpr.TokenP
|
||||
return true, quota, nil
|
||||
}
|
||||
|
||||
// Surface any int32 saturation from settlement onto RelayInfo so the
|
||||
// Surface any single-request saturation from settlement onto RelayInfo so the
|
||||
// consume log records it under admin_info, regardless of which caller
|
||||
// (text, audio, WSS) consumes the returned quota. First non-nil wins.
|
||||
noteQuotaClamp(relayInfo, tr.Clamp)
|
||||
|
||||
@@ -140,11 +140,11 @@ func getImageToken(c *gin.Context, fileMeta *types.FileMeta, model string, strea
|
||||
if imageTokens > 1536 {
|
||||
imageTokens = 1536
|
||||
}
|
||||
return int(math.Round(float64(imageTokens) * multiplier)), nil
|
||||
return common.QuotaRound(float64(imageTokens) * multiplier), nil
|
||||
}
|
||||
// below cap
|
||||
imageTokens := rawPatches
|
||||
return int(math.Round(float64(imageTokens) * multiplier)), nil
|
||||
return common.QuotaRound(float64(imageTokens) * multiplier), nil
|
||||
}
|
||||
|
||||
// Tile-based calculation for 4o/4.1/4.5/o1/o3/etc.
|
||||
|
||||
@@ -88,15 +88,14 @@ func calcViolationFeeQuota(amount, groupRatio float64) int {
|
||||
if groupRatio <= 0 {
|
||||
return 0
|
||||
}
|
||||
quota := decimal.NewFromFloat(amount).
|
||||
quota := common.QuotaFromDecimal(decimal.NewFromFloat(amount).
|
||||
Mul(decimal.NewFromFloat(common.QuotaPerUnit)).
|
||||
Mul(decimal.NewFromFloat(groupRatio)).
|
||||
Round(0).
|
||||
IntPart()
|
||||
Round(0))
|
||||
if quota <= 0 {
|
||||
return 0
|
||||
}
|
||||
return int(quota)
|
||||
return quota
|
||||
}
|
||||
|
||||
// ChargeViolationFeeIfNeeded charges an additional fee after the normal flow finishes (including refund).
|
||||
|
||||
Reference in New Issue
Block a user