- 提取 PriceData.ApplyChannelPricingRatios 消除 ModelPriceHelper 和 UpdatePriceDataForChannelPricing 中重复的 ~30 行比率回退逻辑 - 提取 ChannelPricing.ApplyFields 消除 controller 中 4 处相同的字段赋值 - 提取 setCache/removeCache 辅助函数统一写穿透缓存操作 - 将 claudeCacheCreation1hMultiplier 常量移至 types 包避免循环依赖 - 修复 BatchUpsertChannelPricing 成功后未刷新内存缓存的 bug Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>master
| @@ -66,6 +66,12 @@ type CreateChannelPricingRequest struct { | |||||
| AudioCompletionRatio float64 `json:"audio_completion_ratio"` | AudioCompletionRatio float64 `json:"audio_completion_ratio"` | ||||
| } | } | ||||
| // applyRequest 将请求字段应用到 ChannelPricing | |||||
| func applyRequestFields(cp *model.ChannelPricing, req *CreateChannelPricingRequest) { | |||||
| cp.ApplyFields(req.QuotaType, req.ModelRatio, req.CompletionRatio, req.ModelPrice, req.TagIds, | |||||
| req.CacheRatio, req.CacheCreationRatio, req.ImageRatio, req.AudioRatio, req.AudioCompletionRatio) | |||||
| } | |||||
| // CreateChannelPricing 创建或更新渠道定价 | // CreateChannelPricing 创建或更新渠道定价 | ||||
| func CreateChannelPricing(c *gin.Context) { | func CreateChannelPricing(c *gin.Context) { | ||||
| var req CreateChannelPricingRequest | var req CreateChannelPricingRequest | ||||
| @@ -82,17 +88,7 @@ func CreateChannelPricing(c *gin.Context) { | |||||
| // 检查是否已存在 | // 检查是否已存在 | ||||
| existing, _ := model.GetChannelPricing(req.ModelName, req.ChannelId) | existing, _ := model.GetChannelPricing(req.ModelName, req.ChannelId) | ||||
| if existing != nil { | if existing != nil { | ||||
| // 更新 | |||||
| existing.QuotaType = req.QuotaType | |||||
| existing.ModelRatio = req.ModelRatio | |||||
| existing.CompletionRatio = req.CompletionRatio | |||||
| existing.ModelPrice = req.ModelPrice | |||||
| existing.TagIds = req.TagIds | |||||
| existing.CacheRatio = req.CacheRatio | |||||
| existing.CacheCreationRatio = req.CacheCreationRatio | |||||
| existing.ImageRatio = req.ImageRatio | |||||
| existing.AudioRatio = req.AudioRatio | |||||
| existing.AudioCompletionRatio = req.AudioCompletionRatio | |||||
| applyRequestFields(existing, &req) | |||||
| if err := existing.Update(); err != nil { | if err := existing.Update(); err != nil { | ||||
| common.ApiError(c, err) | common.ApiError(c, err) | ||||
| return | return | ||||
| @@ -104,19 +100,10 @@ func CreateChannelPricing(c *gin.Context) { | |||||
| // 创建 | // 创建 | ||||
| cp := &model.ChannelPricing{ | cp := &model.ChannelPricing{ | ||||
| ModelName: req.ModelName, | |||||
| ChannelId: req.ChannelId, | |||||
| QuotaType: req.QuotaType, | |||||
| ModelRatio: req.ModelRatio, | |||||
| CompletionRatio: req.CompletionRatio, | |||||
| ModelPrice: req.ModelPrice, | |||||
| TagIds: req.TagIds, | |||||
| CacheRatio: req.CacheRatio, | |||||
| CacheCreationRatio: req.CacheCreationRatio, | |||||
| ImageRatio: req.ImageRatio, | |||||
| AudioRatio: req.AudioRatio, | |||||
| AudioCompletionRatio: req.AudioCompletionRatio, | |||||
| ModelName: req.ModelName, | |||||
| ChannelId: req.ChannelId, | |||||
| } | } | ||||
| applyRequestFields(cp, &req) | |||||
| if err := cp.Insert(); err != nil { | if err := cp.Insert(); err != nil { | ||||
| common.ApiError(c, err) | common.ApiError(c, err) | ||||
| return | return | ||||
| @@ -143,36 +130,20 @@ func BatchCreateChannelPricing(c *gin.Context) { | |||||
| pricings := make([]*model.ChannelPricing, 0, len(req.Items)) | pricings := make([]*model.ChannelPricing, 0, len(req.Items)) | ||||
| for _, item := range req.Items { | for _, item := range req.Items { | ||||
| pricings = append(pricings, &model.ChannelPricing{ | |||||
| ModelName: item.ModelName, | |||||
| ChannelId: item.ChannelId, | |||||
| QuotaType: item.QuotaType, | |||||
| ModelRatio: item.ModelRatio, | |||||
| CompletionRatio: item.CompletionRatio, | |||||
| ModelPrice: item.ModelPrice, | |||||
| TagIds: item.TagIds, | |||||
| CacheRatio: item.CacheRatio, | |||||
| CacheCreationRatio: item.CacheCreationRatio, | |||||
| ImageRatio: item.ImageRatio, | |||||
| AudioRatio: item.AudioRatio, | |||||
| AudioCompletionRatio: item.AudioCompletionRatio, | |||||
| }) | |||||
| cp := &model.ChannelPricing{ | |||||
| ModelName: item.ModelName, | |||||
| ChannelId: item.ChannelId, | |||||
| } | |||||
| applyRequestFields(cp, item) | |||||
| pricings = append(pricings, cp) | |||||
| } | } | ||||
| // 使用事务逐个处理(GORM 的批量 upsert 在不同数据库表现不一致) | // 使用事务逐个处理(GORM 的批量 upsert 在不同数据库表现不一致) | ||||
| for _, cp := range pricings { | for _, cp := range pricings { | ||||
| existing, _ := model.GetChannelPricing(cp.ModelName, cp.ChannelId) | existing, _ := model.GetChannelPricing(cp.ModelName, cp.ChannelId) | ||||
| if existing != nil { | if existing != nil { | ||||
| existing.QuotaType = cp.QuotaType | |||||
| existing.ModelRatio = cp.ModelRatio | |||||
| existing.CompletionRatio = cp.CompletionRatio | |||||
| existing.ModelPrice = cp.ModelPrice | |||||
| existing.TagIds = cp.TagIds | |||||
| existing.CacheRatio = cp.CacheRatio | |||||
| existing.CacheCreationRatio = cp.CacheCreationRatio | |||||
| existing.ImageRatio = cp.ImageRatio | |||||
| existing.AudioRatio = cp.AudioRatio | |||||
| existing.AudioCompletionRatio = cp.AudioCompletionRatio | |||||
| existing.ApplyFields(cp.QuotaType, cp.ModelRatio, cp.CompletionRatio, cp.ModelPrice, cp.TagIds, | |||||
| cp.CacheRatio, cp.CacheCreationRatio, cp.ImageRatio, cp.AudioRatio, cp.AudioCompletionRatio) | |||||
| if err := existing.Update(); err != nil { | if err := existing.Update(); err != nil { | ||||
| common.ApiError(c, err) | common.ApiError(c, err) | ||||
| return | return | ||||
| @@ -241,18 +212,9 @@ func CopyGlobalPricing(c *gin.Context) { | |||||
| } | } | ||||
| // 获取全局扩展比率 | // 获取全局扩展比率 | ||||
| globalCacheRatio, hasCacheRatio := ratio_setting.GetCacheRatio(ability.Model) | |||||
| if !hasCacheRatio { | |||||
| globalCacheRatio = 0 | |||||
| } | |||||
| globalCacheCreationRatio, hasCacheCreationRatio := ratio_setting.GetCreateCacheRatio(ability.Model) | |||||
| if !hasCacheCreationRatio { | |||||
| globalCacheCreationRatio = 0 | |||||
| } | |||||
| globalImageRatio, hasImageRatio := ratio_setting.GetImageRatio(ability.Model) | |||||
| if !hasImageRatio { | |||||
| globalImageRatio = 0 | |||||
| } | |||||
| globalCacheRatio, _ := ratio_setting.GetCacheRatio(ability.Model) | |||||
| globalCacheCreationRatio, _ := ratio_setting.GetCreateCacheRatio(ability.Model) | |||||
| globalImageRatio, _ := ratio_setting.GetImageRatio(ability.Model) | |||||
| globalAudioRatio := ratio_setting.GetAudioRatio(ability.Model) | globalAudioRatio := ratio_setting.GetAudioRatio(ability.Model) | ||||
| globalAudioCompletionRatio := ratio_setting.GetAudioCompletionRatio(ability.Model) | globalAudioCompletionRatio := ratio_setting.GetAudioCompletionRatio(ability.Model) | ||||
| @@ -276,32 +238,18 @@ func CopyGlobalPricing(c *gin.Context) { | |||||
| } | } | ||||
| if existing != nil { | if existing != nil { | ||||
| existing.QuotaType = quotaType | |||||
| existing.ModelRatio = ratio | |||||
| existing.CompletionRatio = completionRatio | |||||
| existing.ModelPrice = price | |||||
| existing.CacheRatio = globalCacheRatio | |||||
| existing.CacheCreationRatio = globalCacheCreationRatio | |||||
| existing.ImageRatio = globalImageRatio | |||||
| existing.AudioRatio = globalAudioRatio | |||||
| existing.AudioCompletionRatio = globalAudioCompletionRatio | |||||
| existing.ApplyFields(quotaType, ratio, completionRatio, price, "", | |||||
| globalCacheRatio, globalCacheCreationRatio, globalImageRatio, globalAudioRatio, globalAudioCompletionRatio) | |||||
| if err := existing.Update(); err == nil { | if err := existing.Update(); err == nil { | ||||
| imported++ | imported++ | ||||
| } | } | ||||
| } else { | } else { | ||||
| cp := &model.ChannelPricing{ | cp := &model.ChannelPricing{ | ||||
| ModelName: ability.Model, | |||||
| ChannelId: channelId, | |||||
| QuotaType: quotaType, | |||||
| ModelRatio: ratio, | |||||
| CompletionRatio: completionRatio, | |||||
| ModelPrice: price, | |||||
| CacheRatio: globalCacheRatio, | |||||
| CacheCreationRatio: globalCacheCreationRatio, | |||||
| ImageRatio: globalImageRatio, | |||||
| AudioRatio: globalAudioRatio, | |||||
| AudioCompletionRatio: globalAudioCompletionRatio, | |||||
| ModelName: ability.Model, | |||||
| ChannelId: channelId, | |||||
| } | } | ||||
| cp.ApplyFields(quotaType, ratio, completionRatio, price, "", | |||||
| globalCacheRatio, globalCacheCreationRatio, globalImageRatio, globalAudioRatio, globalAudioCompletionRatio) | |||||
| if err := cp.Insert(); err == nil { | if err := cp.Insert(); err == nil { | ||||
| imported++ | imported++ | ||||
| } | } | ||||
| @@ -46,16 +46,40 @@ type ChannelPricing struct { | |||||
| AudioCompletionRatio float64 `json:"audio_completion_ratio" gorm:"default:0"` | AudioCompletionRatio float64 `json:"audio_completion_ratio" gorm:"default:0"` | ||||
| } | } | ||||
| // setCache 写穿透缓存 | |||||
| func setCache(key string, cp *ChannelPricing) { | |||||
| channelPricingCacheLock.Lock() | |||||
| channelPricingCache[key] = cp | |||||
| channelPricingCacheLock.Unlock() | |||||
| } | |||||
| func removeCache(key string) { | |||||
| channelPricingCacheLock.Lock() | |||||
| delete(channelPricingCache, key) | |||||
| channelPricingCacheLock.Unlock() | |||||
| } | |||||
| // ApplyFields 批量设置定价字段(消除 controller 层的重复赋值) | |||||
| func (cp *ChannelPricing) ApplyFields(quotaType int, modelRatio, completionRatio, modelPrice float64, tagIds string, cacheRatio, cacheCreationRatio, imageRatio, audioRatio, audioCompletionRatio float64) { | |||||
| cp.QuotaType = quotaType | |||||
| cp.ModelRatio = modelRatio | |||||
| cp.CompletionRatio = completionRatio | |||||
| cp.ModelPrice = modelPrice | |||||
| cp.TagIds = tagIds | |||||
| cp.CacheRatio = cacheRatio | |||||
| cp.CacheCreationRatio = cacheCreationRatio | |||||
| cp.ImageRatio = imageRatio | |||||
| cp.AudioRatio = audioRatio | |||||
| cp.AudioCompletionRatio = audioCompletionRatio | |||||
| } | |||||
| func (cp *ChannelPricing) Insert() error { | func (cp *ChannelPricing) Insert() error { | ||||
| now := common.GetTimestamp() | now := common.GetTimestamp() | ||||
| cp.CreatedTime = now | cp.CreatedTime = now | ||||
| cp.UpdatedTime = now | cp.UpdatedTime = now | ||||
| err := DB.Create(cp).Error | err := DB.Create(cp).Error | ||||
| if err == nil { | if err == nil { | ||||
| key := getChannelPricingCacheKey(cp.ModelName, cp.ChannelId) | |||||
| channelPricingCacheLock.Lock() | |||||
| channelPricingCache[key] = cp | |||||
| channelPricingCacheLock.Unlock() | |||||
| setCache(getChannelPricingCacheKey(cp.ModelName, cp.ChannelId), cp) | |||||
| } | } | ||||
| return err | return err | ||||
| } | } | ||||
| @@ -68,10 +92,7 @@ func (cp *ChannelPricing) Update() error { | |||||
| "audio_ratio", "audio_completion_ratio", "updated_time"). | "audio_ratio", "audio_completion_ratio", "updated_time"). | ||||
| Updates(cp).Error | Updates(cp).Error | ||||
| if err == nil { | if err == nil { | ||||
| key := getChannelPricingCacheKey(cp.ModelName, cp.ChannelId) | |||||
| channelPricingCacheLock.Lock() | |||||
| channelPricingCache[key] = cp | |||||
| channelPricingCacheLock.Unlock() | |||||
| setCache(getChannelPricingCacheKey(cp.ModelName, cp.ChannelId), cp) | |||||
| } | } | ||||
| return err | return err | ||||
| } | } | ||||
| @@ -83,10 +104,7 @@ func (cp *ChannelPricing) Delete() error { | |||||
| } | } | ||||
| err := DB.Delete(cp).Error | err := DB.Delete(cp).Error | ||||
| if err == nil { | if err == nil { | ||||
| key := getChannelPricingCacheKey(existing.ModelName, existing.ChannelId) | |||||
| channelPricingCacheLock.Lock() | |||||
| delete(channelPricingCache, key) | |||||
| channelPricingCacheLock.Unlock() | |||||
| removeCache(getChannelPricingCacheKey(existing.ModelName, existing.ChannelId)) | |||||
| } | } | ||||
| return err | return err | ||||
| } | } | ||||
| @@ -134,7 +152,7 @@ func BatchUpsertChannelPricing(pricings []*ChannelPricing) error { | |||||
| } | } | ||||
| // 使用 GORM 的 OnConflict 实现 upsert | // 使用 GORM 的 OnConflict 实现 upsert | ||||
| // 唯一索引为 idx_model_channel (model_name, channel_id) | // 唯一索引为 idx_model_channel (model_name, channel_id) | ||||
| return DB.Clauses(clause.OnConflict{ | |||||
| err := DB.Clauses(clause.OnConflict{ | |||||
| Columns: []clause.Column{ | Columns: []clause.Column{ | ||||
| {Name: "model_name"}, | {Name: "model_name"}, | ||||
| {Name: "channel_id"}, | {Name: "channel_id"}, | ||||
| @@ -153,6 +171,12 @@ func BatchUpsertChannelPricing(pricings []*ChannelPricing) error { | |||||
| "updated_time", | "updated_time", | ||||
| }), | }), | ||||
| }).Create(&pricings).Error | }).Create(&pricings).Error | ||||
| if err == nil { | |||||
| for _, cp := range pricings { | |||||
| setCache(getChannelPricingCacheKey(cp.ModelName, cp.ChannelId), cp) | |||||
| } | |||||
| } | |||||
| return err | |||||
| } | } | ||||
| // getChannelPricingCacheKey 生成缓存键 | // getChannelPricingCacheKey 生成缓存键 | ||||
| @@ -14,9 +14,6 @@ import ( | |||||
| "github.com/gin-gonic/gin" | "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 | // 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 { | func HandleGroupRatio(ctx *gin.Context, relayInfo *relaycommon.RelayInfo) types.GroupRatioInfo { | ||||
| groupRatioInfo := types.GroupRatioInfo{ | groupRatioInfo := types.GroupRatioInfo{ | ||||
| @@ -52,13 +49,6 @@ func ModelPriceHelper(c *gin.Context, info *relaycommon.RelayInfo, promptTokens | |||||
| var modelRatio float64 | var modelRatio float64 | ||||
| var completionRatio float64 | var completionRatio float64 | ||||
| var channelPricingFound bool | var channelPricingFound bool | ||||
| var cacheRatio float64 | |||||
| var imageRatio float64 | |||||
| var cacheCreationRatio float64 | |||||
| var cacheCreationRatio5m float64 | |||||
| var cacheCreationRatio1h float64 | |||||
| var audioRatio float64 | |||||
| var audioCompletionRatio float64 | |||||
| // 尝试获取渠道定价(优先于全局定价) | // 尝试获取渠道定价(优先于全局定价) | ||||
| channelMetaAvailable := info != nil && info.ChannelMeta != nil && info.ChannelId > 0 | channelMetaAvailable := info != nil && info.ChannelMeta != nil && info.ChannelId > 0 | ||||
| @@ -70,23 +60,6 @@ func ModelPriceHelper(c *gin.Context, info *relaycommon.RelayInfo, promptTokens | |||||
| modelPrice = cp.ModelPrice | modelPrice = cp.ModelPrice | ||||
| usePrice = cp.QuotaType == model.QuotaTypeByCall | usePrice = cp.QuotaType == model.QuotaTypeByCall | ||||
| channelPricingFound = true | channelPricingFound = true | ||||
| if cp.CacheRatio != 0 { | |||||
| cacheRatio = cp.CacheRatio | |||||
| } | |||||
| if cp.CacheCreationRatio != 0 { | |||||
| cacheCreationRatio = cp.CacheCreationRatio | |||||
| cacheCreationRatio5m = cp.CacheCreationRatio | |||||
| cacheCreationRatio1h = cp.CacheCreationRatio * claudeCacheCreation1hMultiplier | |||||
| } | |||||
| if cp.ImageRatio != 0 { | |||||
| imageRatio = cp.ImageRatio | |||||
| } | |||||
| if cp.AudioRatio != 0 { | |||||
| audioRatio = cp.AudioRatio | |||||
| } | |||||
| if cp.AudioCompletionRatio != 0 { | |||||
| audioCompletionRatio = cp.AudioCompletionRatio | |||||
| } | |||||
| if common.DebugEnabled { | if common.DebugEnabled { | ||||
| println(fmt.Sprintf("[ChannelPricing] hit: model=%s channel=%d source=cache", info.OriginModelName, info.ChannelId)) | println(fmt.Sprintf("[ChannelPricing] hit: model=%s channel=%d source=cache", info.OriginModelName, info.ChannelId)) | ||||
| } | } | ||||
| @@ -123,24 +96,6 @@ func ModelPriceHelper(c *gin.Context, info *relaycommon.RelayInfo, promptTokens | |||||
| } | } | ||||
| completionRatio = ratio_setting.GetCompletionRatio(info.OriginModelName) | completionRatio = ratio_setting.GetCompletionRatio(info.OriginModelName) | ||||
| } | } | ||||
| if cacheRatio == 0 { | |||||
| cacheRatio, _ = ratio_setting.GetCacheRatio(info.OriginModelName) | |||||
| } | |||||
| if cacheCreationRatio == 0 { | |||||
| cacheCreationRatio, _ = ratio_setting.GetCreateCacheRatio(info.OriginModelName) | |||||
| cacheCreationRatio5m = cacheCreationRatio | |||||
| // 固定1h和5min缓存写入价格的比例 | |||||
| cacheCreationRatio1h = cacheCreationRatio * claudeCacheCreation1hMultiplier | |||||
| } | |||||
| if imageRatio == 0 { | |||||
| imageRatio, _ = ratio_setting.GetImageRatio(info.OriginModelName) | |||||
| } | |||||
| if audioRatio == 0 { | |||||
| audioRatio = ratio_setting.GetAudioRatio(info.OriginModelName) | |||||
| } | |||||
| if audioCompletionRatio == 0 { | |||||
| audioCompletionRatio = ratio_setting.GetAudioCompletionRatio(info.OriginModelName) | |||||
| } | |||||
| ratio := modelRatio * groupRatioInfo.GroupRatio | ratio := modelRatio * groupRatioInfo.GroupRatio | ||||
| preConsumedQuota = int(float64(preConsumedTokens) * ratio) | preConsumedQuota = int(float64(preConsumedTokens) * ratio) | ||||
| } else { | } else { | ||||
| @@ -170,20 +125,38 @@ func ModelPriceHelper(c *gin.Context, info *relaycommon.RelayInfo, promptTokens | |||||
| } | } | ||||
| priceData := types.PriceData{ | 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, | |||||
| FreeModel: freeModel, | |||||
| ModelPrice: modelPrice, | |||||
| ModelRatio: modelRatio, | |||||
| CompletionRatio: completionRatio, | |||||
| GroupRatioInfo: groupRatioInfo, | |||||
| UsePrice: usePrice, | |||||
| QuotaToPreConsume: preConsumedQuota, | |||||
| } | |||||
| // 应用渠道定价的扩展比率(非零值覆盖),然后回退全局比率 | |||||
| if channelMetaAvailable { | |||||
| if cp, found := model.GetEffectivePricing(info.OriginModelName, info.ChannelId); found { | |||||
| priceData.ApplyChannelPricingRatios(cp.CacheRatio, cp.CacheCreationRatio, cp.ImageRatio, cp.AudioRatio, cp.AudioCompletionRatio) | |||||
| } | |||||
| } | |||||
| // 全局比率作为回退(仅当对应字段仍为 0 时生效) | |||||
| if priceData.CacheRatio == 0 { | |||||
| priceData.CacheRatio, _ = ratio_setting.GetCacheRatio(info.OriginModelName) | |||||
| } | |||||
| if priceData.CacheCreationRatio == 0 { | |||||
| priceData.CacheCreationRatio, _ = ratio_setting.GetCreateCacheRatio(info.OriginModelName) | |||||
| priceData.CacheCreation5mRatio = priceData.CacheCreationRatio | |||||
| priceData.CacheCreation1hRatio = priceData.CacheCreationRatio * types.ClaudeCacheCreation1hMultiplier | |||||
| } | |||||
| if priceData.ImageRatio == 0 { | |||||
| priceData.ImageRatio, _ = ratio_setting.GetImageRatio(info.OriginModelName) | |||||
| } | |||||
| if priceData.AudioRatio == 0 { | |||||
| priceData.AudioRatio = ratio_setting.GetAudioRatio(info.OriginModelName) | |||||
| } | |||||
| if priceData.AudioCompletionRatio == 0 { | |||||
| priceData.AudioCompletionRatio = ratio_setting.GetAudioCompletionRatio(info.OriginModelName) | |||||
| } | } | ||||
| if common.DebugEnabled { | if common.DebugEnabled { | ||||
| @@ -261,23 +234,7 @@ func UpdatePriceDataForChannelPricing(c *gin.Context, info *relaycommon.RelayInf | |||||
| info.PriceData.ModelPrice = -1 | info.PriceData.ModelPrice = -1 | ||||
| } | } | ||||
| if cp.CacheRatio != 0 { | |||||
| info.PriceData.CacheRatio = cp.CacheRatio | |||||
| } | |||||
| if cp.CacheCreationRatio != 0 { | |||||
| info.PriceData.CacheCreationRatio = cp.CacheCreationRatio | |||||
| info.PriceData.CacheCreation5mRatio = cp.CacheCreationRatio | |||||
| info.PriceData.CacheCreation1hRatio = cp.CacheCreationRatio * claudeCacheCreation1hMultiplier | |||||
| } | |||||
| if cp.ImageRatio != 0 { | |||||
| info.PriceData.ImageRatio = cp.ImageRatio | |||||
| } | |||||
| if cp.AudioRatio != 0 { | |||||
| info.PriceData.AudioRatio = cp.AudioRatio | |||||
| } | |||||
| if cp.AudioCompletionRatio != 0 { | |||||
| info.PriceData.AudioCompletionRatio = cp.AudioCompletionRatio | |||||
| } | |||||
| info.PriceData.ApplyChannelPricingRatios(cp.CacheRatio, cp.CacheCreationRatio, cp.ImageRatio, cp.AudioRatio, cp.AudioCompletionRatio) | |||||
| if info.PriceData.UsePrice { | if info.PriceData.UsePrice { | ||||
| info.PriceData.QuotaToPreConsume = int( | info.PriceData.QuotaToPreConsume = int( | ||||
| @@ -2,6 +2,10 @@ package types | |||||
| import "fmt" | import "fmt" | ||||
| // ClaudeCacheCreation1hMultiplier 1小时缓存写入价格相对于5分钟的比例 | |||||
| // https://docs.claude.com/en/docs/build-with-claude/prompt-caching#1-hour-cache-duration | |||||
| const ClaudeCacheCreation1hMultiplier = 6 / 3.75 | |||||
| type GroupRatioInfo struct { | type GroupRatioInfo struct { | ||||
| GroupRatio float64 | GroupRatio float64 | ||||
| GroupSpecialRatio float64 | GroupSpecialRatio float64 | ||||
| @@ -27,6 +31,28 @@ type PriceData struct { | |||||
| GroupRatioInfo GroupRatioInfo | GroupRatioInfo GroupRatioInfo | ||||
| } | } | ||||
| // ApplyChannelPricingRatios 将渠道定价的扩展比率应用到 PriceData(非零值覆盖) | |||||
| // cacheRatio, cacheCreationRatio, imageRatio, audioRatio, audioCompletionRatio | |||||
| func (p *PriceData) ApplyChannelPricingRatios(cacheRatio, cacheCreationRatio, imageRatio, audioRatio, audioCompletionRatio float64) { | |||||
| if cacheRatio != 0 { | |||||
| p.CacheRatio = cacheRatio | |||||
| } | |||||
| if cacheCreationRatio != 0 { | |||||
| p.CacheCreationRatio = cacheCreationRatio | |||||
| p.CacheCreation5mRatio = cacheCreationRatio | |||||
| p.CacheCreation1hRatio = cacheCreationRatio * ClaudeCacheCreation1hMultiplier | |||||
| } | |||||
| if imageRatio != 0 { | |||||
| p.ImageRatio = imageRatio | |||||
| } | |||||
| if audioRatio != 0 { | |||||
| p.AudioRatio = audioRatio | |||||
| } | |||||
| if audioCompletionRatio != 0 { | |||||
| p.AudioCompletionRatio = audioCompletionRatio | |||||
| } | |||||
| } | |||||
| func (p *PriceData) AddOtherRatio(key string, ratio float64) { | func (p *PriceData) AddOtherRatio(key string, ratio float64) { | ||||
| if p.OtherRatios == nil { | if p.OtherRatios == nil { | ||||
| p.OtherRatios = make(map[string]float64) | p.OtherRatios = make(map[string]float64) | ||||