package helper import ( "fmt" "github.com/QuantumNous/new-api/common" "github.com/QuantumNous/new-api/logger" "github.com/QuantumNous/new-api/model" relaycommon "github.com/QuantumNous/new-api/relay/common" "github.com/QuantumNous/new-api/setting/operation_setting" "github.com/QuantumNous/new-api/setting/ratio_setting" "github.com/QuantumNous/new-api/types" "github.com/gin-gonic/gin" ) // https://docs.claude.com/en/docs/build-with-claude/prompt-caching#1-hour-cache-duration const claudeCacheCreation1hMultiplier = 6 / 3.75 // HandleGroupRatio checks for "auto_group" in the context and updates the group ratio and relayInfo.UsingGroup if present func HandleGroupRatio(ctx *gin.Context, relayInfo *relaycommon.RelayInfo) types.GroupRatioInfo { groupRatioInfo := types.GroupRatioInfo{ GroupRatio: 1.0, // default ratio GroupSpecialRatio: -1, } // check auto group autoGroup, exists := ctx.Get("auto_group") if exists { logger.LogDebug(ctx, fmt.Sprintf("final group: %s", autoGroup)) relayInfo.UsingGroup = autoGroup.(string) } // check user group special ratio userGroupRatio, ok := ratio_setting.GetGroupGroupRatio(relayInfo.UserGroup, relayInfo.UsingGroup) if ok { // user group special ratio groupRatioInfo.GroupSpecialRatio = userGroupRatio groupRatioInfo.GroupRatio = userGroupRatio groupRatioInfo.HasSpecialRatio = true } else { // normal group ratio groupRatioInfo.GroupRatio = ratio_setting.GetGroupRatio(relayInfo.UsingGroup) } return groupRatioInfo } func ModelPriceHelper(c *gin.Context, info *relaycommon.RelayInfo, promptTokens int, meta *types.TokenCountMeta) (types.PriceData, error) { var modelPrice float64 var usePrice bool var modelRatio float64 var completionRatio float64 var channelPricingFound bool // 尝试获取渠道定价(优先于全局定价) channelMetaAvailable := info != nil && info.ChannelMeta != nil && info.ChannelId > 0 if channelMetaAvailable { cpRatio, cpCompletionRatio, cpPrice, cpUsePrice, found := model.GetEffectivePricing(info.OriginModelName, info.ChannelId) if found { modelRatio = cpRatio completionRatio = cpCompletionRatio modelPrice = cpPrice usePrice = cpUsePrice channelPricingFound = true } } // 如果没有渠道定价,使用全局定价 if !channelPricingFound { modelPrice, usePrice = ratio_setting.GetModelPrice(info.OriginModelName, false) } groupRatioInfo := HandleGroupRatio(c, info) var preConsumedQuota int var cacheRatio float64 var imageRatio float64 var cacheCreationRatio float64 var cacheCreationRatio5m float64 var cacheCreationRatio1h float64 var audioRatio float64 var audioCompletionRatio float64 var freeModel bool if !usePrice { preConsumedTokens := common.Max(promptTokens, common.PreConsumedQuota) if meta.MaxTokens != 0 { preConsumedTokens += meta.MaxTokens } // 如果没有找到渠道定价,获取全局 modelRatio 和 completionRatio if !channelPricingFound { var success bool var matchName string modelRatio, success, matchName = ratio_setting.GetModelRatio(info.OriginModelName) if !success { acceptUnsetRatio := false if info.UserSetting.AcceptUnsetRatioModel { acceptUnsetRatio = true } if !acceptUnsetRatio { return types.PriceData{}, fmt.Errorf("模型 %s 倍率或价格未配置,请联系管理员设置或开启自用模式;Model %s ratio or price not set, please set or start self-use mode", matchName, matchName) } } completionRatio = ratio_setting.GetCompletionRatio(info.OriginModelName) } cacheRatio, _ = ratio_setting.GetCacheRatio(info.OriginModelName) cacheCreationRatio, _ = ratio_setting.GetCreateCacheRatio(info.OriginModelName) cacheCreationRatio5m = cacheCreationRatio // 固定1h和5min缓存写入价格的比例 cacheCreationRatio1h = cacheCreationRatio * claudeCacheCreation1hMultiplier imageRatio, _ = ratio_setting.GetImageRatio(info.OriginModelName) audioRatio = ratio_setting.GetAudioRatio(info.OriginModelName) audioCompletionRatio = ratio_setting.GetAudioCompletionRatio(info.OriginModelName) ratio := modelRatio * groupRatioInfo.GroupRatio preConsumedQuota = int(float64(preConsumedTokens) * ratio) } else { if meta.ImagePriceRatio != 0 { modelPrice = modelPrice * meta.ImagePriceRatio } preConsumedQuota = int(modelPrice * common.QuotaPerUnit * groupRatioInfo.GroupRatio) } // check if free model pre-consume is disabled if !operation_setting.GetQuotaSetting().EnableFreeModelPreConsume { // if model price or ratio is 0, do not pre-consume quota if groupRatioInfo.GroupRatio == 0 { preConsumedQuota = 0 freeModel = true } else if usePrice { if modelPrice == 0 { preConsumedQuota = 0 freeModel = true } } else { if modelRatio == 0 { preConsumedQuota = 0 freeModel = true } } } 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, } if common.DebugEnabled { println(fmt.Sprintf("model_price_helper result: %s", priceData.ToSetting())) } info.PriceData = priceData return priceData, nil } // ModelPriceHelperPerCall 按次计费的 PriceHelper (MJ、Task) func ModelPriceHelperPerCall(c *gin.Context, info *relaycommon.RelayInfo) types.PriceData { groupRatioInfo := HandleGroupRatio(c, info) modelPrice, success := ratio_setting.GetModelPrice(info.OriginModelName, true) // 如果没有配置价格,则使用默认价格 if !success { defaultPrice, ok := ratio_setting.GetDefaultModelPriceMap()[info.OriginModelName] if !ok { modelPrice = 0.1 } else { modelPrice = defaultPrice } } quota := int(modelPrice * common.QuotaPerUnit * groupRatioInfo.GroupRatio) // 免费模型检测(与 ModelPriceHelper 对齐) freeModel := false if !operation_setting.GetQuotaSetting().EnableFreeModelPreConsume { if groupRatioInfo.GroupRatio == 0 || modelPrice == 0 { quota = 0 freeModel = true } } priceData := types.PriceData{ FreeModel: freeModel, ModelPrice: modelPrice, Quota: quota, GroupRatioInfo: groupRatioInfo, } return priceData } 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 } // UpdatePriceDataForChannelPricing 在渠道选择后更新 PriceData 以使用渠道定价 // ModelPriceHelper 在渠道选择之前被调用,此时 ChannelMeta 为 nil,导致渠道定价无法使用。 // 需要在渠道选择后调用此函数更新价格数据,用于后续的日志记录和计费计算。 func UpdatePriceDataForChannelPricing(c *gin.Context, info *relaycommon.RelayInfo, channelId int) { if info == nil || channelId <= 0 { return } cpRatio, cpCompletionRatio, cpPrice, cpUsePrice, found := model.GetEffectivePricing(info.OriginModelName, channelId) if !found { return } // 更新 PriceData 中的定价相关字段 info.PriceData.ModelRatio = cpRatio info.PriceData.CompletionRatio = cpCompletionRatio info.PriceData.UsePrice = cpUsePrice // 按次计费模式下使用渠道价格,按量计费模式下设置 ModelPrice = -1 让前端识别计费模式 if cpUsePrice { info.PriceData.ModelPrice = cpPrice } else { info.PriceData.ModelPrice = -1 } // 重新计算预扣费额度(用于后续可能的引用) if cpUsePrice { info.PriceData.QuotaToPreConsume = int(cpPrice * common.QuotaPerUnit * info.PriceData.GroupRatioInfo.GroupRatio) } else { estimateTokens := info.GetEstimatePromptTokens() if estimateTokens > 0 { ratio := cpRatio * info.PriceData.GroupRatioInfo.GroupRatio info.PriceData.QuotaToPreConsume = int(float64(estimateTokens) * ratio) } } }