Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions controller/channel-test.go
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,10 @@ func testChannel(channel *model.Channel, testModel string, endpointType string,
}
}
info.SetEstimatePromptTokens(usage.PromptTokens)
if updatedPriceData, applied := ratio_setting.ApplyModelTierPricing(info.OriginModelName, priceData, usage.PromptTokens); applied {
priceData = updatedPriceData
info.PriceData = updatedPriceData
}

quota := 0
if !priceData.UsePrice {
Expand Down
10 changes: 10 additions & 0 deletions controller/option.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
var completionRatioMetaOptionKeys = []string{
"ModelPrice",
"ModelRatio",
"ModelTierPricing",
"CompletionRatio",
"CacheRatio",
"CreateCacheRatio",
Expand Down Expand Up @@ -233,6 +234,15 @@ func UpdateOption(c *gin.Context) {
})
return
}
case "ModelTierPricing":
err = ratio_setting.UpdateModelTierPricingByJSONString(option.Value.(string))
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "模型阶梯定价设置失败: " + err.Error(),
})
return
}
case "ModelRequestRateLimitGroup":
err = setting.CheckModelRequestRateLimitGroup(option.Value.(string))
if err != nil {
Expand Down
36 changes: 36 additions & 0 deletions controller/option_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package controller

import (
"testing"

"github.com/QuantumNous/new-api/common"
"github.com/QuantumNous/new-api/setting/ratio_setting"

"github.com/stretchr/testify/require"
)

func TestBuildCompletionRatioMetaValueIncludesTierOnlyModels(t *testing.T) {
metaJSON := buildCompletionRatioMetaValue(map[string]string{
"ModelTierPricing": `{
"gpt-5": {
"enabled": true,
"basis": "prompt_tokens",
"tiers": [
{
"min_tokens": 0,
"input_price": 2,
"completion_price": 16
}
]
}
}`,
})

meta := make(map[string]ratio_setting.CompletionRatioInfo)
require.NoError(t, common.UnmarshalJsonStr(metaJSON, &meta))

info, ok := meta["gpt-5"]
require.True(t, ok)
require.True(t, info.Locked)
require.Equal(t, 8.0, info.Ratio)
}
3 changes: 3 additions & 0 deletions model/option.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ func InitOptionMap() {
common.OptionMap["ModelRequestRateLimitGroup"] = setting.ModelRequestRateLimitGroup2JSONString()
common.OptionMap["ModelRatio"] = ratio_setting.ModelRatio2JSONString()
common.OptionMap["ModelPrice"] = ratio_setting.ModelPrice2JSONString()
common.OptionMap["ModelTierPricing"] = ratio_setting.ModelTierPricing2JSONString()
common.OptionMap["CacheRatio"] = ratio_setting.CacheRatio2JSONString()
common.OptionMap["CreateCacheRatio"] = ratio_setting.CreateCacheRatio2JSONString()
common.OptionMap["GroupRatio"] = ratio_setting.GroupRatio2JSONString()
Expand Down Expand Up @@ -472,6 +473,8 @@ func updateOptionMap(key string, value string) (err error) {
err = ratio_setting.UpdateCompletionRatioByJSONString(value)
case "ModelPrice":
err = ratio_setting.UpdateModelPriceByJSONString(value)
case "ModelTierPricing":
err = ratio_setting.UpdateModelTierPricingByJSONString(value)
case "CacheRatio":
err = ratio_setting.UpdateCacheRatioByJSONString(value)
case "CreateCacheRatio":
Expand Down
63 changes: 36 additions & 27 deletions relay/helper/price.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ func ModelPriceHelper(c *gin.Context, info *relaycommon.RelayInfo, promptTokens
var audioRatio float64
var audioCompletionRatio float64
var freeModel bool
priceData := types.PriceData{
GroupRatioInfo: groupRatioInfo,
UsePrice: usePrice,
}
if !usePrice {
preConsumedTokens := common.Max(promptTokens, common.PreConsumedQuota)
if meta.MaxTokens != 0 {
Expand All @@ -69,7 +73,7 @@ func ModelPriceHelper(c *gin.Context, info *relaycommon.RelayInfo, promptTokens
var success bool
var matchName string
modelRatio, success, matchName = ratio_setting.GetModelRatio(info.OriginModelName)
if !success {
if !success && !ratio_setting.HasEnabledModelTierPricing(info.OriginModelName) {
acceptUnsetRatio := false
if info.UserSetting.AcceptUnsetRatioModel {
acceptUnsetRatio = true
Expand All @@ -87,13 +91,29 @@ func ModelPriceHelper(c *gin.Context, info *relaycommon.RelayInfo, promptTokens
imageRatio, _ = ratio_setting.GetImageRatio(info.OriginModelName)
audioRatio = ratio_setting.GetAudioRatio(info.OriginModelName)
audioCompletionRatio = ratio_setting.GetAudioCompletionRatio(info.OriginModelName)
ratio := modelRatio * groupRatioInfo.GroupRatio
priceData.ModelRatio = modelRatio
priceData.CompletionRatio = completionRatio
priceData.CacheRatio = cacheRatio
priceData.CacheCreationRatio = cacheCreationRatio
priceData.CacheCreation5mRatio = cacheCreationRatio5m
priceData.CacheCreation1hRatio = cacheCreationRatio1h
priceData.ImageRatio = imageRatio
priceData.AudioRatio = audioRatio
priceData.AudioCompletionRatio = audioCompletionRatio
if updatedPriceData, applied := ratio_setting.ApplyModelTierPricing(info.OriginModelName, priceData, promptTokens); applied {
priceData = updatedPriceData
}
modelRatio = priceData.ModelRatio
completionRatio = priceData.CompletionRatio
cacheRatio = priceData.CacheRatio
ratio := priceData.ModelRatio * groupRatioInfo.GroupRatio
preConsumedQuota = int(float64(preConsumedTokens) * ratio)
} else {
if meta.ImagePriceRatio != 0 {
modelPrice = modelPrice * meta.ImagePriceRatio
}
preConsumedQuota = int(modelPrice * common.QuotaPerUnit * groupRatioInfo.GroupRatio)
priceData.ModelPrice = modelPrice
}

// check if free model pre-consume is disabled
Expand All @@ -115,22 +135,18 @@ func ModelPriceHelper(c *gin.Context, info *relaycommon.RelayInfo, promptTokens
}
}

priceData := types.PriceData{
FreeModel: freeModel,
ModelPrice: modelPrice,
ModelRatio: modelRatio,
CompletionRatio: completionRatio,
GroupRatioInfo: groupRatioInfo,
UsePrice: usePrice,
CacheRatio: cacheRatio,
ImageRatio: imageRatio,
AudioRatio: audioRatio,
AudioCompletionRatio: audioCompletionRatio,
CacheCreationRatio: cacheCreationRatio,
CacheCreation5mRatio: cacheCreationRatio5m,
CacheCreation1hRatio: cacheCreationRatio1h,
QuotaToPreConsume: preConsumedQuota,
}
priceData.FreeModel = freeModel
priceData.ModelPrice = modelPrice
priceData.ModelRatio = modelRatio
priceData.CompletionRatio = completionRatio
priceData.CacheRatio = cacheRatio
priceData.ImageRatio = imageRatio
priceData.AudioRatio = audioRatio
priceData.AudioCompletionRatio = audioCompletionRatio
priceData.CacheCreationRatio = cacheCreationRatio
priceData.CacheCreation5mRatio = cacheCreationRatio5m
priceData.CacheCreation1hRatio = cacheCreationRatio1h
priceData.QuotaToPreConsume = preConsumedQuota

if common.DebugEnabled {
println(fmt.Sprintf("model_price_helper result: %s", priceData.ToSetting()))
Expand Down Expand Up @@ -201,13 +217,6 @@ func ModelPriceHelperPerCall(c *gin.Context, info *relaycommon.RelayInfo) (types
}

func ContainPriceOrRatio(modelName string) bool {
_, ok := ratio_setting.GetModelPrice(modelName, false)
if ok {
return true
}
_, ok, _ = ratio_setting.GetModelRatio(modelName)
if ok {
return true
}
return false
_, _, exist := ratio_setting.GetModelRatioOrPrice(modelName)
return exist
}
60 changes: 60 additions & 0 deletions relay/helper/price_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package helper

import (
"net/http/httptest"
"testing"

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"
"github.com/stretchr/testify/require"
)

func TestModelPriceHelperAllowsTierOnlyPricing(t *testing.T) {
gin.SetMode(gin.TestMode)

require.NoError(t, ratio_setting.UpdateModelTierPricingByJSONString(`{
"tier-only-gemini-3.1-pro-preview": {
"enabled": true,
"basis": "prompt_tokens",
"tiers": [
{
"min_tokens": 0,
"max_tokens": 200000,
"input_price": 2,
"completion_price": 12,
"cache_read_price": 0.2
},
{
"min_tokens": 200000,
"input_price": 4,
"completion_price": 18,
"cache_read_price": 0.4
}
]
}
}`))
t.Cleanup(func() {
require.NoError(t, ratio_setting.UpdateModelTierPricingByJSONString("{}"))
})

recorder := httptest.NewRecorder()
ctx, _ := gin.CreateTestContext(recorder)
info := &relaycommon.RelayInfo{
OriginModelName: "tier-only-gemini-3.1-pro-preview",
UsingGroup: "default",
UserGroup: "default",
}

priceData, err := ModelPriceHelper(ctx, info, 250000, &types.TokenCountMeta{})
require.NoError(t, err)
require.False(t, priceData.UsePrice)
require.Equal(t, 2.0, priceData.ModelRatio)
require.Equal(t, 4.5, priceData.CompletionRatio)
require.Equal(t, 0.1, priceData.CacheRatio)
require.NotNil(t, priceData.TierPricing)
require.Equal(t, 1, priceData.TierPricing.TierIndex)
require.Equal(t, 250000, priceData.TierPricing.BasisValue)
}
22 changes: 22 additions & 0 deletions service/log_info_generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ func GenerateTextOtherInfo(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, m
appendBillingInfo(relayInfo, other)
appendParamOverrideInfo(relayInfo, other)
appendStreamStatus(relayInfo, other)
appendTierPricingInfo(relayInfo, other)
return other
}

Expand Down Expand Up @@ -114,10 +115,30 @@ func appendStreamStatus(relayInfo *relaycommon.RelayInfo, other map[string]inter
other["stream_status"] = streamInfo
}

func appendTierPricingInfo(relayInfo *relaycommon.RelayInfo, other map[string]interface{}) {
if relayInfo == nil || other == nil || relayInfo.PriceData.TierPricing == nil {
return
}
tierInfo := relayInfo.PriceData.TierPricing
other["tier_pricing_enabled"] = tierInfo.Enabled
other["tier_basis"] = tierInfo.Basis
other["tier_index"] = tierInfo.TierIndex
other["tier_min_tokens"] = tierInfo.MinTokens
if tierInfo.MaxTokens != nil {
other["tier_max_tokens"] = *tierInfo.MaxTokens
}
other["tier_basis_value"] = tierInfo.BasisValue
}

func appendBillingInfo(relayInfo *relaycommon.RelayInfo, other map[string]interface{}) {
if relayInfo == nil || other == nil {
return
}
if relayInfo.PriceData.UsePrice {
other["billing_quota_type"] = 1
} else {
other["billing_quota_type"] = 0
}
// billing_source: "wallet" or "subscription"
if relayInfo.BillingSource != "" {
other["billing_source"] = relayInfo.BillingSource
Expand Down Expand Up @@ -256,6 +277,7 @@ func GenerateMjOtherInfo(relayInfo *relaycommon.RelayInfo, priceData types.Price
other := make(map[string]interface{})
other["model_price"] = priceData.ModelPrice
other["group_ratio"] = priceData.GroupRatioInfo.GroupRatio
other["billing_quota_type"] = 1
if priceData.GroupRatioInfo.HasSpecialRatio {
other["user_group_ratio"] = priceData.GroupRatioInfo.GroupSpecialRatio
}
Expand Down
63 changes: 63 additions & 0 deletions service/log_info_generate_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package service

import (
"net/http/httptest"
"testing"
"time"

relaycommon "github.com/QuantumNous/new-api/relay/common"
"github.com/QuantumNous/new-api/types"

"github.com/gin-gonic/gin"
"github.com/stretchr/testify/require"
)

func TestGenerateTextOtherInfoIncludesBillingQuotaTypeForTierPricing(t *testing.T) {
gin.SetMode(gin.TestMode)
recorder := httptest.NewRecorder()
ctx, _ := gin.CreateTestContext(recorder)
now := time.Now()

relayInfo := &relaycommon.RelayInfo{
StartTime: now,
FirstResponseTime: now,
ChannelMeta: &relaycommon.ChannelMeta{},
PriceData: types.PriceData{
UsePrice: false,
TierPricing: &types.TierPricingMeta{
Enabled: true,
Basis: "prompt_tokens",
TierIndex: 1,
MinTokens: 200000,
BasisValue: 200321,
},
},
}

other := GenerateTextOtherInfo(ctx, relayInfo, 2, 1, 4.5, 0, 0.1, 0, -1)

require.Equal(t, 0, other["billing_quota_type"])
require.Equal(t, true, other["tier_pricing_enabled"])
require.Equal(t, 1, other["tier_index"])
require.Equal(t, 200321, other["tier_basis_value"])
}

func TestGenerateTextOtherInfoIncludesBillingQuotaTypeForPerCallPricing(t *testing.T) {
gin.SetMode(gin.TestMode)
recorder := httptest.NewRecorder()
ctx, _ := gin.CreateTestContext(recorder)
now := time.Now()

relayInfo := &relaycommon.RelayInfo{
StartTime: now,
FirstResponseTime: now,
ChannelMeta: &relaycommon.ChannelMeta{},
PriceData: types.PriceData{
UsePrice: true,
},
}

other := GenerateTextOtherInfo(ctx, relayInfo, 0, 1, 0, 0, 0, 0.02, -1)

require.Equal(t, 1, other["billing_quota_type"])
}
Loading