mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-14 12:02:27 +08:00
fix(pricing): 目录 cache 侧字段契约哨兵与 Gemini pro 缓存写入价修正
阶梯计价数据驱动后,目录条目的字段缺失或版本拼接会直接变成计费偏差, 解析层补两道数据契约哨兵,并修正出厂快照中 Gemini pro 系的缓存写入价: - orphanCacheTierFields / warnOrphanCacheTierFields:条目带 cache 侧 above 档(含 _priority/_flex 变体与 above_1hr 形态)却没有基础价时打 WARN——cache 侧 above 档不参与计费取值,按"基础价 × 输入倍率"计,基础价 缺失即该分项按 0 计;变体缺自身基础价时沿 变体→1h→标准 回落链任一存在 即不算孤儿,与计费回落一致 - isLopsidedLongContextLadder / warnLopsidedLongContextLadders:above 档 折算出只有一侧带附加费的阶梯时打 WARN(基础价与 above 档来自不同价格 版本的形态),显式 long_context_* 字段不告警 - deriveLongContextFromAboveTierFields 注释改为陈述契约:cache above 档 须恰为基础价 × 输入倍率 - 出厂快照 6 个 Gemini pro 条目补 cache_creation_input_token_cost(= 标准 输入价,Google 对缓存写入不另收费)、above_200k 改为输入 above 价、带 priority 输入价的 4 条补 priority 孪生,与 model-price-repo 数据修正同值 测试: - 解析层:孤儿字段 WARN(含 priority/1h 孤儿正例、1h 回落 5m 反例)、单侧 阶梯 WARN、出厂快照契约(全条目无孤儿字段、无单侧阶梯、Gemini pro 缓存 写入价 = 输入价) - 用量契约:extractGeminiUsage 与 Antigravity 非流式/流式转换均不产生 cache_creation token(Gemini usageMetadata 无该类别) - 计费:gemini-2.5-pro 阶梯对 cache_read / cache_creation 分项生效,阈值 计入缓存写入 token;阶梯表场景与夹具同步为现行目录值
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
//go:build unit
|
||||
|
||||
package antigravity
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// Gemini usageMetadata 只有 cachedContentTokenCount(缓存命中),没有缓存写入的 token 类别:
|
||||
// 两条转换路径产出的 Claude usage 中 cache_creation_input_tokens 恒为 0,缓存命中计入
|
||||
// cache_read_input_tokens 并从 input_tokens 中扣除。
|
||||
func TestGeminiUsageMapping_NoCacheCreationTokens(t *testing.T) {
|
||||
const geminiBody = `{"candidates":[{"content":{"parts":[{"text":"hi"}],"role":"model"},"finishReason":"STOP"}],` +
|
||||
`"usageMetadata":{"promptTokenCount":100,"candidatesTokenCount":20,"cachedContentTokenCount":30,"thoughtsTokenCount":5}}`
|
||||
|
||||
t.Run("non-stream", func(t *testing.T) {
|
||||
_, usage, err := TransformGeminiToClaude([]byte(geminiBody), "gemini-3.1-pro-preview")
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, usage)
|
||||
require.Equal(t, 70, usage.InputTokens)
|
||||
require.Equal(t, 25, usage.OutputTokens)
|
||||
require.Equal(t, 30, usage.CacheReadInputTokens)
|
||||
require.Zero(t, usage.CacheCreationInputTokens)
|
||||
})
|
||||
|
||||
t.Run("stream", func(t *testing.T) {
|
||||
p := NewStreamingProcessor("gemini-3.1-pro-preview")
|
||||
out := p.ProcessLine(`data: {"response":` + geminiBody + `}`)
|
||||
require.True(t, strings.Contains(string(out), `"message_start"`))
|
||||
require.NotContains(t, string(out), `"cache_creation_input_tokens"`, "message_start 的 usage 不应携带缓存写入分项")
|
||||
_, usage := p.Finish()
|
||||
require.NotNil(t, usage)
|
||||
require.Equal(t, 70, usage.InputTokens)
|
||||
require.Equal(t, 30, usage.CacheReadInputTokens)
|
||||
require.Zero(t, usage.CacheCreationInputTokens)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user