mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-14 03:52:26 +08:00
feat(gemini): 显式缓存存储价内置官方价并按 Pro / Flash 系列兜底
- 新增 Gemini 显式缓存存储单价表(Standard 档,USD / token / 小时): Pro 全系 $4.50、Flash 与 Flash-Lite 全系 $1.00;3.6 / 3.7 / 3.8 Flash 为优惠价 $0.50(有效期至 2026-12-31,2027-01-01 起恢复 $1.00), AI Studio 与 Vertex 统一按优惠价计 - 型号表未命中的 Gemini 模型按系列兜底:Pro 取 $4.50,Flash / Flash-Lite 取标准价 $1.00;Flash-Lite 变体不落到同版本 Flash 的优惠价 - 取值优先级:价格表(override_file 等)配置值 > 内置型号价 > 内置系列价; 两者都没有的模型仍拒绝创建缓存
This commit is contained in:
@@ -1325,22 +1325,21 @@ func (s *BillingService) GetModelPricing(model string) (*ModelPricing, error) {
|
||||
return s.getModelPricingAt(model, timezone.Now())
|
||||
}
|
||||
|
||||
// CacheStoragePricePerTokenHour 返回模型的显式上下文缓存存储单价(每 token 每小时,USD)。
|
||||
// 优先级:价格表(override_file 等)按确定性识别配置的值 > 内置型号价 > 内置 Pro/Flash 系列兜底价;
|
||||
// 均未命中返回 0。
|
||||
func (s *BillingService) CacheStoragePricePerTokenHour(model string) float64 {
|
||||
if s != nil && s.pricingService != nil {
|
||||
if pricing := s.pricingService.GetIdentifiedModelPricing(model); pricing != nil && pricing.CacheStorageCostPerTokenPerHour > 0 {
|
||||
return pricing.CacheStorageCostPerTokenPerHour
|
||||
}
|
||||
}
|
||||
return builtinGeminiCacheStoragePrice(model)
|
||||
}
|
||||
|
||||
// getModelPricingAt 是 GetModelPricing 的带计费时点内部变体:pricingAt 显式
|
||||
// 驱动 DeepSeek pro→Flash 切换判定(切换点前 Pro 价、之后 Flash 价),使
|
||||
// 展示/估算路径可与历史补账同刻复算,测试也能用固定时点钉住断言。
|
||||
// CacheStoragePricePerTokenHour 返回模型的显式上下文缓存存储单价(每 token 每小时,USD)。
|
||||
// 只做确定性识别,不按系列兜底;价格表未配置时返回 0。
|
||||
func (s *BillingService) CacheStoragePricePerTokenHour(model string) float64 {
|
||||
if s == nil || s.pricingService == nil {
|
||||
return 0
|
||||
}
|
||||
pricing := s.pricingService.GetIdentifiedModelPricing(model)
|
||||
if pricing == nil || pricing.CacheStorageCostPerTokenPerHour <= 0 {
|
||||
return 0
|
||||
}
|
||||
return pricing.CacheStorageCostPerTokenPerHour
|
||||
}
|
||||
|
||||
func (s *BillingService) getModelPricingAt(model string, pricingAt time.Time) (*ModelPricing, error) {
|
||||
// 标准化模型名称(转小写)
|
||||
model = strings.ToLower(model)
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Gemini 显式上下文缓存存储的内置官方单价(USD / token / 小时,Standard 档)。
|
||||
// 存储价与上下文长度无关(≤200K 与 >200K 同价);Priority 档的更高存储价不在此列。
|
||||
// 来源:https://ai.google.dev/gemini-api/docs/pricing
|
||||
// 与 https://cloud.google.com/gemini-enterprise-agent-platform/generative-ai/pricing
|
||||
const (
|
||||
geminiProCacheStoragePrice = 4.5e-6 // $4.50 / 1M tokens / 小时
|
||||
geminiFlashCacheStoragePrice = 1e-6 // $1.00 / 1M tokens / 小时,Flash-Lite 同价
|
||||
|
||||
// 3.6/3.7/3.8 Flash 的优惠价 $0.50 / 1M tokens / 小时,有效期至 2026-12-31,
|
||||
// 2027-01-01 起恢复为 geminiFlashCacheStoragePrice。AI Studio 价目页直接标注该优惠价;
|
||||
// Vertex 价目页存储价标 $1.00,优惠以「按该模型净消费返还 50% 抵扣金」发放,
|
||||
// 官方未明确存储费是否在返还范围内,两类账号统一按优惠价计。
|
||||
geminiFlashPromoCacheStoragePrice = 0.5e-6
|
||||
)
|
||||
|
||||
// geminiCacheStorageModelPrices 是已发布型号的存储单价。模型名等于前缀,或以「前缀-」开头
|
||||
// (-preview、-customtools、-001 等变体)即命中;更具体的前缀排在前面。
|
||||
var geminiCacheStorageModelPrices = []struct {
|
||||
prefix string
|
||||
price float64
|
||||
}{
|
||||
{"gemini-3.8-flash-cyber", geminiFlashCacheStoragePrice},
|
||||
{"gemini-3.8-flash", geminiFlashPromoCacheStoragePrice},
|
||||
{"gemini-3.7-flash", geminiFlashPromoCacheStoragePrice},
|
||||
{"gemini-3.6-flash", geminiFlashPromoCacheStoragePrice},
|
||||
{"gemini-3.5-flash-lite", geminiFlashCacheStoragePrice},
|
||||
{"gemini-3.5-flash", geminiFlashCacheStoragePrice},
|
||||
{"gemini-3.1-flash-lite", geminiFlashCacheStoragePrice},
|
||||
{"gemini-3.1-pro", geminiProCacheStoragePrice},
|
||||
{"gemini-3-flash", geminiFlashCacheStoragePrice},
|
||||
{"gemini-3-pro", geminiProCacheStoragePrice},
|
||||
{"gemini-2.5-flash-lite", geminiFlashCacheStoragePrice},
|
||||
{"gemini-2.5-flash", geminiFlashCacheStoragePrice},
|
||||
{"gemini-2.5-pro", geminiProCacheStoragePrice},
|
||||
{"gemini-2.0-flash-lite", geminiFlashCacheStoragePrice},
|
||||
{"gemini-2.0-flash", geminiFlashCacheStoragePrice},
|
||||
}
|
||||
|
||||
// builtinGeminiCacheStoragePrice 返回内置的显式缓存存储单价:先按已发布型号匹配,未命中时按系列兜底,
|
||||
// Pro 系列取 geminiProCacheStoragePrice,Flash 与 Flash-Lite 系列取 geminiFlashCacheStoragePrice(标准价,
|
||||
// 不套用优惠价)。其他 Gemini 模型与非 Gemini 模型返回 0。
|
||||
func builtinGeminiCacheStoragePrice(model string) float64 {
|
||||
name := lastSegment(strings.ToLower(strings.TrimSpace(model)))
|
||||
if !strings.HasPrefix(name, "gemini-") {
|
||||
return 0
|
||||
}
|
||||
parts := strings.Split(name, "-")
|
||||
lite := slices.Contains(parts, "lite")
|
||||
for _, entry := range geminiCacheStorageModelPrices {
|
||||
// Flash-Lite 变体只匹配 Flash-Lite 条目:gemini-3.8-flash-lite 不能落到 3.8 Flash 的优惠价。
|
||||
if lite != strings.HasSuffix(entry.prefix, "-lite") {
|
||||
continue
|
||||
}
|
||||
if name == entry.prefix || strings.HasPrefix(name, entry.prefix+"-") {
|
||||
return entry.price
|
||||
}
|
||||
}
|
||||
switch {
|
||||
case slices.Contains(parts, "pro"):
|
||||
return geminiProCacheStoragePrice
|
||||
case slices.Contains(parts, "flash"):
|
||||
return geminiFlashCacheStoragePrice
|
||||
}
|
||||
return 0
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
//go:build unit
|
||||
|
||||
package service
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/Wei-Shaw/sub2api/internal/config"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestBuiltinGeminiCacheStoragePrice(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
model string
|
||||
want float64
|
||||
}{
|
||||
// 已发布型号
|
||||
{"gemini-3.8-flash", 0.5e-6},
|
||||
{"gemini-3.7-flash", 0.5e-6},
|
||||
{"gemini-3.6-flash", 0.5e-6},
|
||||
{"gemini-3.8-flash-cyber", 1e-6},
|
||||
{"gemini-3.5-flash", 1e-6},
|
||||
{"gemini-3.5-flash-lite", 1e-6},
|
||||
{"gemini-3.1-flash-lite", 1e-6},
|
||||
{"gemini-3.1-pro-preview", 4.5e-6},
|
||||
{"gemini-3.1-pro-preview-customtools", 4.5e-6},
|
||||
{"gemini-3-flash-preview", 1e-6},
|
||||
{"gemini-3-pro-preview", 4.5e-6},
|
||||
{"gemini-2.5-pro", 4.5e-6},
|
||||
{"gemini-2.5-flash", 1e-6},
|
||||
{"gemini-2.5-flash-lite", 1e-6},
|
||||
{"gemini-2.0-flash-001", 1e-6},
|
||||
{"gemini-2.0-flash-lite-001", 1e-6},
|
||||
// 资源名与大小写
|
||||
{"models/gemini-3.8-flash", 0.5e-6},
|
||||
{"projects/p/locations/global/publishers/google/models/gemini-2.5-pro", 4.5e-6},
|
||||
{" Gemini-3.8-Flash ", 0.5e-6},
|
||||
// 系列兜底:未发布型号与别名按标准价
|
||||
{"gemini-3.9-flash", 1e-6},
|
||||
{"gemini-3.9-flash-lite", 1e-6},
|
||||
{"gemini-3.8-flash-lite", 1e-6},
|
||||
{"gemini-3.9-pro-preview", 4.5e-6},
|
||||
{"gemini-flash-latest", 1e-6},
|
||||
{"gemini-flash-lite-latest", 1e-6},
|
||||
{"gemini-pro-latest", 4.5e-6},
|
||||
// 无存储价
|
||||
{"gemini-embedding-001", 0},
|
||||
{"gemini-unknown-model", 0},
|
||||
{"claude-sonnet-5-5", 0},
|
||||
{"", 0},
|
||||
} {
|
||||
require.InDelta(t, tc.want, builtinGeminiCacheStoragePrice(tc.model), 1e-15, tc.model)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCacheStoragePricePerTokenHourPriority(t *testing.T) {
|
||||
pricing := &PricingService{}
|
||||
data, err := pricing.parsePricingData([]byte(`{
|
||||
"gemini-3.8-flash":{"litellm_provider":"gemini","input_cost_per_token":0.00000075,"output_cost_per_token":0.00000375,
|
||||
"cache_storage_cost_per_token_per_hour":0.000002},
|
||||
"gemini-3.9-flash":{"litellm_provider":"gemini","input_cost_per_token":0.0000015,"output_cost_per_token":0.0000075,
|
||||
"cache_storage_cost_per_token_per_hour":0.0000007},
|
||||
"gemini-2.5-pro":{"litellm_provider":"gemini","input_cost_per_token":0.00000125,"output_cost_per_token":0.00001}}`))
|
||||
require.NoError(t, err)
|
||||
pricing.pricingData = data
|
||||
billing := NewBillingService(&config.Config{}, pricing)
|
||||
|
||||
require.InDelta(t, 2e-6, billing.CacheStoragePricePerTokenHour("gemini-3.8-flash"), 1e-15, "价格表配置值优先于内置型号价")
|
||||
require.InDelta(t, 0.7e-6, billing.CacheStoragePricePerTokenHour("gemini-3.9-flash"), 1e-15, "价格表配置值优先于内置系列兜底价")
|
||||
require.InDelta(t, 4.5e-6, billing.CacheStoragePricePerTokenHour("gemini-2.5-pro"), 1e-15, "价格表条目缺存储价时取内置型号价")
|
||||
require.InDelta(t, 0.5e-6, billing.CacheStoragePricePerTokenHour("gemini-3.7-flash"), 1e-15, "价格表无条目时取内置型号价")
|
||||
require.InDelta(t, 4.5e-6, billing.CacheStoragePricePerTokenHour("gemini-3.9-pro"), 1e-15, "内置型号未命中时取系列兜底价")
|
||||
require.Zero(t, billing.CacheStoragePricePerTokenHour("customer-alias"))
|
||||
|
||||
noCatalog := NewBillingService(&config.Config{}, nil)
|
||||
require.InDelta(t, 0.5e-6, noCatalog.CacheStoragePricePerTokenHour("gemini-3.8-flash"), 1e-15, "无价格服务时取内置价")
|
||||
}
|
||||
@@ -223,7 +223,8 @@ type LiteLLMModelPricing struct {
|
||||
OutputCostPerImageToken float64 `json:"output_cost_per_image_token"` // 图片输出 token 价格
|
||||
InputCostPerImageToken float64 `json:"input_cost_per_image_token"` // 图片输入 token 价格(如 gpt-image-2 图片编辑)
|
||||
CacheReadInputImageTokenCost float64 `json:"cache_read_input_image_token_cost"`
|
||||
// CacheStorageCostPerTokenPerHour 显式上下文缓存存储单价(每 token 每小时,USD),仅由定价覆盖文件提供。
|
||||
// CacheStorageCostPerTokenPerHour 显式上下文缓存存储单价(每 token 每小时,USD),一般由定价覆盖文件提供,
|
||||
// 配置后优先于内置价(见 BillingService.CacheStoragePricePerTokenHour)。
|
||||
CacheStorageCostPerTokenPerHour float64 `json:"cache_storage_cost_per_token_per_hour"`
|
||||
|
||||
// TokenPricingAbsent 表示源数据中 input/output token 价格均缺失(仅有图片价)。
|
||||
|
||||
@@ -1105,11 +1105,13 @@ pricing:
|
||||
# 覆盖补丁文件(可选,优先级最高):条目按字段浅合并覆盖目录/回退数据,
|
||||
# 字段值为 null 表示删除该字段。模型名须与目录键完全一致。
|
||||
# 修改后在下一次哈希检查时按内容哈希自动重载,无需重启。
|
||||
# Gemini explicit context caching requires a storage price per model, e.g.
|
||||
# Gemini explicit context caching storage price (USD per token per hour) comes
|
||||
# from the field cache_storage_cost_per_token_per_hour here, e.g.
|
||||
# {"gemini-3.8-flash": {"cache_storage_cost_per_token_per_hour": 0.0000005}};
|
||||
# models without it reject POST /v1beta/cachedContents.
|
||||
# Gemini 显式缓存需按模型配置存储单价(每 token 每小时,USD)字段
|
||||
# cache_storage_cost_per_token_per_hour;未配置的模型拒绝创建缓存。
|
||||
# without it, built-in official prices apply (released models first, then the
|
||||
# Pro / Flash series rate). Models with no price at all reject POST /v1beta/cachedContents.
|
||||
# Gemini 显式缓存存储单价(每 token 每小时,USD)优先取这里的 cache_storage_cost_per_token_per_hour 字段,
|
||||
# 未配置时使用内置官方价(先按已发布型号,再按 Pro / Flash 系列兜底);两者都没有的模型拒绝创建缓存。
|
||||
# 目录中不存在的模型条目还须同时提供 input_cost_per_token / output_cost_per_token。
|
||||
# override_file: "./data/model_pricing_overrides.json"
|
||||
# Update interval in hours
|
||||
|
||||
Reference in New Issue
Block a user