mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-14 03:52:26 +08:00
fix(gateway): avoid ambiguous late-cache input subtraction
This commit is contained in:
@@ -29,10 +29,11 @@ func TestAnthropicChatStreamAuthoritativeUsage(t *testing.T) {
|
||||
{"cache", `,"usage":{"input_tokens":308,"cache_read_input_tokens":241,"cache_creation_input_tokens":17}`, `,"usage":{"output_tokens":49}`, "", true, 566, 308, 566, 49, 241, 17},
|
||||
{"deepseek_partial_cache", "", `,"usage":{"input_tokens":1200,"output_tokens":30,"prompt_cache_hit_tokens":800,"prompt_cache_miss_tokens":400}`, "", true, 1200, 400, 1200, 30, 800, 0},
|
||||
{"openai_partial_cache", "", `,"usage":{"prompt_tokens":1200,"output_tokens":30,"prompt_tokens_details":{"cached_tokens":800}}`, "", true, 1200, 400, 1200, 30, 800, 0},
|
||||
{"repeated_cache_bucket", `,"usage":{"input_tokens":1200}`, `,"usage":{"output_tokens":30,"cache_read_input_tokens":800}`, `,"usage":{"output_tokens":30,"cache_read_input_tokens":800}`, true, 1200, 400, 1200, 30, 800, 0},
|
||||
{"kimi_full_cache", `,"usage":{"input_tokens":173306}`, `,"usage":{"output_tokens":49,"cache_read_input_tokens":173306}`, "", true, 173306, 0, 173306, 49, 173306, 0},
|
||||
{"kimi_full_cache_cached_tokens", `,"usage":{"input_tokens":173306}`, `,"usage":{"output_tokens":49,"cached_tokens":173306}`, "", true, 173306, 0, 173306, 49, 173306, 0},
|
||||
{"kimi_full_cache_prompt_details", `,"usage":{"input_tokens":173306}`, `,"usage":{"output_tokens":49,"prompt_tokens_details":{"cached_tokens":173306}}`, "", true, 173306, 0, 173306, 49, 173306, 0},
|
||||
{"repeated_authoritative_cache_bucket", `,"usage":{"input_tokens":1200,"prompt_tokens":1200}`, `,"usage":{"output_tokens":30,"prompt_tokens":1200,"cache_read_input_tokens":800}`, `,"usage":{"output_tokens":30,"prompt_tokens":1200,"cache_read_input_tokens":800}`, true, 1200, 400, 1200, 30, 800, 0},
|
||||
{"independent_input_with_late_cache", `,"usage":{"input_tokens":308,"cache_read_input_tokens":241,"cache_creation_input_tokens":17}`, `,"usage":{"input_tokens":0,"output_tokens":49,"cache_read_input_tokens":300,"cache_creation_input_tokens":17}`, "", true, 625, 308, 625, 49, 300, 17},
|
||||
{"kimi_full_cache", `,"usage":{"input_tokens":173306,"prompt_tokens":173306}`, `,"usage":{"input_tokens":0,"output_tokens":49,"prompt_tokens":173306,"cache_read_input_tokens":173306}`, "", true, 173306, 0, 173306, 49, 173306, 0},
|
||||
{"kimi_full_cache_cached_tokens", `,"usage":{"input_tokens":173306,"prompt_tokens":173306}`, `,"usage":{"input_tokens":0,"output_tokens":49,"prompt_tokens":173306,"cached_tokens":173306}`, "", true, 173306, 0, 173306, 49, 173306, 0},
|
||||
{"kimi_full_cache_prompt_details", `,"usage":{"input_tokens":173306,"prompt_tokens":173306}`, `,"usage":{"input_tokens":0,"output_tokens":49,"prompt_tokens":173306,"prompt_tokens_details":{"cached_tokens":173306}}`, "", true, 173306, 0, 173306, 49, 173306, 0},
|
||||
{"zero_start", `,"usage":{"input_tokens":0,"output_tokens":0}`, "", "", true, 0, 0, 0, 0, 0, 0},
|
||||
{"zero_delta", "", `,"usage":{"input_tokens":0,"output_tokens":0}`, "", true, 0, 0, 0, 0, 0, 0},
|
||||
{"absent", "", "", "", false, 0, 0, 0, 0, 0, 0},
|
||||
|
||||
@@ -303,19 +303,14 @@ func mergeAnthropicUsage(dst *ClaudeUsage, src apicompat.AnthropicUsage) {
|
||||
dst.CacheReadInputTokens = cacheReadTokens
|
||||
dst.CacheCreationInputTokens = src.CacheCreationInputTokens
|
||||
} else {
|
||||
previousCacheReadTokens := dst.CacheReadInputTokens
|
||||
previousCacheCreationTokens := dst.CacheCreationInputTokens
|
||||
// Without an authoritative prompt total or miss bucket, input_tokens is
|
||||
// provider-specific: it may already be the uncached bucket, or it may be
|
||||
// a total from an earlier event. Do not infer a subtraction merely because
|
||||
// a later event contains cache buckets; that would corrupt providers whose
|
||||
// stream uses independent input and cache fields.
|
||||
if src.InputTokens > 0 {
|
||||
dst.InputTokens = src.InputTokens
|
||||
}
|
||||
if src.InputTokens == 0 && dst.InputTokens > 0 && (cacheReadTokens > 0 || src.CacheCreationInputTokens > 0) {
|
||||
// Some compatible streams put the total prompt count in message_start,
|
||||
// then provide only cumulative cache buckets in message_delta. Subtract
|
||||
// only newly observed buckets so repeated deltas are idempotent.
|
||||
newCacheReadTokens := max(cacheReadTokens-previousCacheReadTokens, 0)
|
||||
newCacheCreationTokens := max(src.CacheCreationInputTokens-previousCacheCreationTokens, 0)
|
||||
dst.InputTokens = max(dst.InputTokens-newCacheReadTokens-newCacheCreationTokens, 0)
|
||||
}
|
||||
if cacheReadTokens > 0 {
|
||||
dst.CacheReadInputTokens = cacheReadTokens
|
||||
}
|
||||
|
||||
@@ -321,22 +321,22 @@ func TestHandleResponsesStreamingResponse_NormalizesTerminalUsage(t *testing.T)
|
||||
}{
|
||||
{
|
||||
name: "cache_read_input_tokens full cache",
|
||||
startUsage: `"input_tokens":173306`,
|
||||
deltaUsage: `"output_tokens":8,"cache_read_input_tokens":173306`,
|
||||
startUsage: `"input_tokens":173306,"prompt_tokens":173306`,
|
||||
deltaUsage: `"input_tokens":0,"output_tokens":8,"prompt_tokens":173306,"cache_read_input_tokens":173306`,
|
||||
wantOutput: 8,
|
||||
wantCached: 173306,
|
||||
},
|
||||
{
|
||||
name: "cached_tokens full cache",
|
||||
startUsage: `"input_tokens":173306`,
|
||||
deltaUsage: `"output_tokens":8,"cached_tokens":173306`,
|
||||
startUsage: `"input_tokens":173306,"prompt_tokens":173306`,
|
||||
deltaUsage: `"input_tokens":0,"output_tokens":8,"prompt_tokens":173306,"cached_tokens":173306`,
|
||||
wantOutput: 8,
|
||||
wantCached: 173306,
|
||||
},
|
||||
{
|
||||
name: "prompt_tokens_details full cache",
|
||||
startUsage: `"input_tokens":173306`,
|
||||
deltaUsage: `"output_tokens":8,"prompt_tokens_details":{"cached_tokens":173306}`,
|
||||
startUsage: `"input_tokens":173306,"prompt_tokens":173306`,
|
||||
deltaUsage: `"input_tokens":0,"output_tokens":8,"prompt_tokens":173306,"prompt_tokens_details":{"cached_tokens":173306}`,
|
||||
wantOutput: 8,
|
||||
wantCached: 173306,
|
||||
},
|
||||
@@ -357,10 +357,10 @@ func TestHandleResponsesStreamingResponse_NormalizesTerminalUsage(t *testing.T)
|
||||
wantCached: 800,
|
||||
},
|
||||
{
|
||||
name: "cache creation only without prompt total or miss bucket",
|
||||
name: "cache creation only without prompt total or miss bucket keeps independent input",
|
||||
startUsage: `"input_tokens":1200`,
|
||||
deltaUsage: `"output_tokens":30,"cache_creation_input_tokens":800`,
|
||||
wantInput: 400,
|
||||
deltaUsage: `"input_tokens":0,"output_tokens":30,"cache_creation_input_tokens":800`,
|
||||
wantInput: 1200,
|
||||
wantOutput: 30,
|
||||
wantCacheCreation: 800,
|
||||
},
|
||||
|
||||
@@ -125,10 +125,10 @@ func TestResponsesStreamingFromNativeAnthropic_NormalizesTerminalUsage(t *testin
|
||||
wantCached int
|
||||
wantCacheCreation int
|
||||
}{
|
||||
{name: "full cache", startUsage: `"input_tokens":1200`, deltaUsage: `"output_tokens":30,"cache_read_input_tokens":1200`, wantOutput: 30, wantCached: 1200},
|
||||
{name: "partial cache", startUsage: `"input_tokens":1200`, deltaUsage: `"output_tokens":30,"cache_read_input_tokens":800`, wantInput: 400, wantOutput: 30, wantCached: 800},
|
||||
{name: "cache creation", startUsage: `"input_tokens":1200`, deltaUsage: `"output_tokens":30,"cache_creation_input_tokens":800`, wantInput: 400, wantOutput: 30, wantCacheCreation: 800},
|
||||
{name: "repeated cumulative cache bucket", startUsage: `"input_tokens":1200`, deltaUsage: `"output_tokens":30,"cache_read_input_tokens":800`, repeatDelta: true, wantInput: 400, wantOutput: 30, wantCached: 800},
|
||||
{name: "full cache", startUsage: `"input_tokens":1200,"prompt_tokens":1200`, deltaUsage: `"input_tokens":0,"output_tokens":30,"prompt_tokens":1200,"cache_read_input_tokens":1200`, wantOutput: 30, wantCached: 1200},
|
||||
{name: "partial cache", startUsage: `"input_tokens":1200,"prompt_tokens":1200`, deltaUsage: `"input_tokens":0,"output_tokens":30,"prompt_tokens":1200,"cache_read_input_tokens":800`, wantInput: 400, wantOutput: 30, wantCached: 800},
|
||||
{name: "cache creation", startUsage: `"input_tokens":1200,"prompt_tokens":1200`, deltaUsage: `"input_tokens":0,"output_tokens":30,"prompt_tokens":1200,"cache_creation_input_tokens":800`, wantInput: 400, wantOutput: 30, wantCacheCreation: 800},
|
||||
{name: "repeated cumulative cache bucket", startUsage: `"input_tokens":1200,"prompt_tokens":1200`, deltaUsage: `"input_tokens":0,"output_tokens":30,"prompt_tokens":1200,"cache_read_input_tokens":800`, repeatDelta: true, wantInput: 400, wantOutput: 30, wantCached: 800},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
||||
Reference in New Issue
Block a user