diff --git a/controller/option.go b/controller/option.go index ecb1e25e867..e8630e74e6f 100644 --- a/controller/option.go +++ b/controller/option.go @@ -23,6 +23,7 @@ var completionRatioMetaOptionKeys = []string{ "CacheRatio", "CreateCacheRatio", "ImageRatio", + "ImageCompletionRatio", "AudioRatio", "AudioCompletionRatio", } @@ -224,6 +225,15 @@ func UpdateOption(c *gin.Context) { }) return } + case "ImageCompletionRatio": + err = ratio_setting.UpdateImageCompletionRatioByJSONString(option.Value.(string)) + if err != nil { + c.JSON(http.StatusOK, gin.H{ + "success": false, + "message": "图片补全倍率设置失败: " + err.Error(), + }) + return + } case "CreateCacheRatio": err = ratio_setting.UpdateCreateCacheRatioByJSONString(option.Value.(string)) if err != nil { diff --git a/model/option.go b/model/option.go index f80b3f7cf96..0fafc9d4c3b 100644 --- a/model/option.go +++ b/model/option.go @@ -123,6 +123,7 @@ func InitOptionMap() { common.OptionMap["ImageRatio"] = ratio_setting.ImageRatio2JSONString() common.OptionMap["AudioRatio"] = ratio_setting.AudioRatio2JSONString() common.OptionMap["AudioCompletionRatio"] = ratio_setting.AudioCompletionRatio2JSONString() + common.OptionMap["ImageCompletionRatio"] = ratio_setting.ImageCompletionRatio2JSONString() common.OptionMap["TopUpLink"] = common.TopUpLink //common.OptionMap["ChatLink"] = common.ChatLink //common.OptionMap["ChatLink2"] = common.ChatLink2 diff --git a/model/pricing.go b/model/pricing.go index 0fee7caecac..5148c1fde6b 100644 --- a/model/pricing.go +++ b/model/pricing.go @@ -29,6 +29,7 @@ type Pricing struct { CacheRatio *float64 `json:"cache_ratio,omitempty"` CreateCacheRatio *float64 `json:"create_cache_ratio,omitempty"` ImageRatio *float64 `json:"image_ratio,omitempty"` + ImageCompletionRatio *float64 `json:"image_completion_ratio,omitempty"` AudioRatio *float64 `json:"audio_ratio,omitempty"` AudioCompletionRatio *float64 `json:"audio_completion_ratio,omitempty"` EnableGroup []string `json:"enable_groups"` @@ -322,6 +323,10 @@ func updatePricing() { audioCompletionRatio := ratio_setting.GetAudioCompletionRatio(model) pricing.AudioCompletionRatio = &audioCompletionRatio } + if ratio_setting.ContainsImageCompletionRatio(model) { + imageCompletionRatio := ratio_setting.GetImageCompletionRatio(model) + pricing.ImageCompletionRatio = &imageCompletionRatio + } if billingMode := billing_setting.GetBillingMode(model); billingMode == "tiered_expr" { pricing.BillingMode = billingMode if expr, ok := billing_setting.GetBillingExpr(model); ok { diff --git a/relay/compatible_handler.go b/relay/compatible_handler.go index 3cff05ba14d..b1229a3293d 100644 --- a/relay/compatible_handler.go +++ b/relay/compatible_handler.go @@ -257,6 +257,7 @@ func postConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, usage imageTokens := usage.PromptTokensDetails.ImageTokens audioTokens := usage.PromptTokensDetails.AudioTokens completionTokens := usage.CompletionTokens + completionImageTokens := usage.CompletionTokenDetails.ImageTokens cachedCreationTokens := usage.PromptTokensDetails.CachedCreationTokens modelName := relayInfo.OriginModelName @@ -265,6 +266,7 @@ func postConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, usage completionRatio := relayInfo.PriceData.CompletionRatio cacheRatio := relayInfo.PriceData.CacheRatio imageRatio := relayInfo.PriceData.ImageRatio + imageCompletionRatio := relayInfo.PriceData.ImageCompletionRatio modelRatio := relayInfo.PriceData.ModelRatio groupRatio := relayInfo.PriceData.GroupRatioInfo.GroupRatio modelPrice := relayInfo.PriceData.ModelPrice @@ -276,6 +278,7 @@ func postConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, usage dImageTokens := decimal.NewFromInt(int64(imageTokens)) dAudioTokens := decimal.NewFromInt(int64(audioTokens)) dCompletionTokens := decimal.NewFromInt(int64(completionTokens)) + dCompletionImageTokens := decimal.NewFromInt(int64(completionImageTokens)) dCachedCreationTokens := decimal.NewFromInt(int64(cachedCreationTokens)) dCompletionRatio := decimal.NewFromFloat(completionRatio) dCacheRatio := decimal.NewFromFloat(cacheRatio) @@ -361,6 +364,12 @@ func postConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, usage Add(imageTokensWithRatio). Add(dCachedCreationTokensWithRatio) + if !dCompletionImageTokens.IsZero() { + dImageCompletionRatio := decimal.NewFromFloat(imageCompletionRatio) + dCompletionTokens = dCompletionTokens.Sub(dCompletionImageTokens) + dCompletionTokens = dCompletionTokens.Add(dCompletionImageTokens.Mul(dImageCompletionRatio)) + } + completionQuota := dCompletionTokens.Mul(dCompletionRatio) quotaCalculateDecimal = promptQuota.Add(completionQuota).Mul(ratio) diff --git a/relay/helper/price.go b/relay/helper/price.go index 71a9ac631be..b0c6d55f853 100644 --- a/relay/helper/price.go +++ b/relay/helper/price.go @@ -62,6 +62,7 @@ func ModelPriceHelper(c *gin.Context, info *relaycommon.RelayInfo, promptTokens var completionRatio float64 var cacheRatio float64 var imageRatio float64 + var imageCompletionRatio float64 var cacheCreationRatio float64 var cacheCreationRatio5m float64 var cacheCreationRatio1h float64 @@ -92,6 +93,7 @@ func ModelPriceHelper(c *gin.Context, info *relaycommon.RelayInfo, promptTokens // 固定1h和5min缓存写入价格的比例 cacheCreationRatio1h = cacheCreationRatio * claudeCacheCreation1hMultiplier imageRatio, _ = ratio_setting.GetImageRatio(info.OriginModelName) + imageCompletionRatio = ratio_setting.GetImageCompletionRatio(info.OriginModelName) audioRatio = ratio_setting.GetAudioRatio(info.OriginModelName) audioCompletionRatio = ratio_setting.GetAudioCompletionRatio(info.OriginModelName) ratio := modelRatio * groupRatioInfo.GroupRatio @@ -131,6 +133,7 @@ func ModelPriceHelper(c *gin.Context, info *relaycommon.RelayInfo, promptTokens UsePrice: usePrice, CacheRatio: cacheRatio, ImageRatio: imageRatio, + ImageCompletionRatio: imageCompletionRatio, AudioRatio: audioRatio, AudioCompletionRatio: audioCompletionRatio, CacheCreationRatio: cacheCreationRatio, diff --git a/setting/ratio_setting/model_ratio.go b/setting/ratio_setting/model_ratio.go index c20508713cd..4d03295d73e 100644 --- a/setting/ratio_setting/model_ratio.go +++ b/setting/ratio_setting/model_ratio.go @@ -183,6 +183,8 @@ var defaultModelRatio = map[string]float64{ "gemini-2.5-flash-lite-preview-thinking-*": 0.05, "gemini-2.5-flash-lite-preview-06-17": 0.05, "gemini-2.5-flash": 0.15, + "gemini-3.1-flash-image-preview": 0.25, + "gemini-3-pro-image-preview": 1.0, "gemini-robotics-er-1.5-preview": 0.15, "gemini-embedding-001": 0.075, "text-embedding-004": 0.001, @@ -322,9 +324,15 @@ var defaultAudioCompletionRatio = map[string]float64{ "tts-1-hd-1106": 0, } +var defaultImageCompletionRatio = map[string]float64{ + "gemini-3.1-flash-image-preview": 20, + "gemini-3-pro-image-preview": 10, +} + var modelPriceMap = types.NewRWMap[string, float64]() var modelRatioMap = types.NewRWMap[string, float64]() var completionRatioMap = types.NewRWMap[string, float64]() +var imageCompletionRatioMap = types.NewRWMap[string, float64]() var defaultCompletionRatio = map[string]float64{ "gpt-4-gizmo-*": 2, @@ -338,6 +346,7 @@ func InitRatioSettings() { modelPriceMap.AddAll(defaultModelPrice) modelRatioMap.AddAll(defaultModelRatio) completionRatioMap.AddAll(defaultCompletionRatio) + imageCompletionRatioMap.AddAll(defaultImageCompletionRatio) cacheRatioMap.AddAll(defaultCacheRatio) createCacheRatioMap.AddAll(defaultCreateCacheRatio) imageRatioMap.AddAll(defaultImageRatio) @@ -572,9 +581,8 @@ func getHardcodedCompletionModelRatio(name string) (float64, bool) { } else if strings.HasPrefix(name, "gemini-robotics-er-1.5") { return 2.5 / 0.3, false } else if strings.HasPrefix(name, "gemini-3-pro") { - if strings.HasPrefix(name, "gemini-3-pro-image") { - return 60, false - } + return 6, false + } else if strings.HasPrefix(name, "gemini-3.1-flash") { return 6, false } return 4, false @@ -697,6 +705,28 @@ func GetCompletionRatioCopy() map[string]float64 { return completionRatioMap.ReadAll() } +func ImageCompletionRatio2JSONString() string { + return imageCompletionRatioMap.MarshalJSONString() +} + +func UpdateImageCompletionRatioByJSONString(jsonStr string) error { + return types.LoadFromJsonStringWithCallback(imageCompletionRatioMap, jsonStr, InvalidateExposedDataCache) +} + +func GetImageCompletionRatio(name string) float64 { + name = FormatMatchingModelName(name) + if ratio, ok := imageCompletionRatioMap.Get(name); ok { + return ratio + } + return 1 +} + +func ContainsImageCompletionRatio(name string) bool { + name = FormatMatchingModelName(name) + _, ok := imageCompletionRatioMap.Get(name) + return ok +} + // 转换模型名,减少渠道必须配置各种带参数模型 func FormatMatchingModelName(name string) string { diff --git a/types/price_data.go b/types/price_data.go index 93bc6ae8d16..45c1642a31b 100644 --- a/types/price_data.go +++ b/types/price_data.go @@ -18,6 +18,7 @@ type PriceData struct { CacheCreation5mRatio float64 CacheCreation1hRatio float64 ImageRatio float64 + ImageCompletionRatio float64 AudioRatio float64 AudioCompletionRatio float64 OtherRatios map[string]float64 @@ -38,5 +39,5 @@ func (p *PriceData) AddOtherRatio(key string, ratio float64) { } func (p *PriceData) ToSetting() string { - return fmt.Sprintf("ModelPrice: %f, ModelRatio: %f, CompletionRatio: %f, CacheRatio: %f, GroupRatio: %f, UsePrice: %t, CacheCreationRatio: %f, CacheCreation5mRatio: %f, CacheCreation1hRatio: %f, QuotaToPreConsume: %d, ImageRatio: %f, AudioRatio: %f, AudioCompletionRatio: %f", p.ModelPrice, p.ModelRatio, p.CompletionRatio, p.CacheRatio, p.GroupRatioInfo.GroupRatio, p.UsePrice, p.CacheCreationRatio, p.CacheCreation5mRatio, p.CacheCreation1hRatio, p.QuotaToPreConsume, p.ImageRatio, p.AudioRatio, p.AudioCompletionRatio) + return fmt.Sprintf("ModelPrice: %f, ModelRatio: %f, CompletionRatio: %f, CacheRatio: %f, GroupRatio: %f, UsePrice: %t, CacheCreationRatio: %f, CacheCreation5mRatio: %f, CacheCreation1hRatio: %f, QuotaToPreConsume: %d, ImageRatio: %f, ImageCompletionRatio: %f, AudioRatio: %f, AudioCompletionRatio: %f", p.ModelPrice, p.ModelRatio, p.CompletionRatio, p.CacheRatio, p.GroupRatioInfo.GroupRatio, p.UsePrice, p.CacheCreationRatio, p.CacheCreation5mRatio, p.CacheCreation1hRatio, p.QuotaToPreConsume, p.ImageRatio, p.ImageCompletionRatio, p.AudioRatio, p.AudioCompletionRatio) }