fix(usage): 修复 Gemini total 重复累加 cache_read 并补充测试

- write.rs: Gemini usage 提取 total 时不再叠加 cachedContentTokenCount
- 补充 Claude 大 cache_read 场景下 input_tokens 不被扣减的测试
- 补充前端 getEffectiveInputTokens 对 Claude 格式不减 cache_read 的测试
This commit is contained in:
fawney19
2026-04-24 21:20:45 +08:00
parent e265475c17
commit db1059e73b
4 changed files with 99 additions and 10 deletions

View File

@@ -1340,6 +1340,31 @@ mod tests {
assert_eq!(payload["cache_creation_ephemeral_1h_input_tokens"], 6);
}
#[test]
fn user_usage_payload_keeps_claude_effective_input_when_cache_read_is_large() {
let item = StoredRequestUsageAudit {
provider_name: "Claude".to_string(),
model: "claude-sonnet-4-5".to_string(),
api_format: Some("claude:chat".to_string()),
api_family: Some("claude".to_string()),
endpoint_api_format: Some("claude:chat".to_string()),
provider_api_family: Some("claude".to_string()),
input_tokens: 4941,
output_tokens: 973,
total_tokens: 59474,
cache_creation_input_tokens: 687,
cache_read_input_tokens: 52873,
..sample_usage("completed")
};
let payload = build_users_me_usage_record_payload(&item, false, &BTreeMap::new(), false);
assert_eq!(payload["input_tokens"], 4941);
assert_eq!(payload["effective_input_tokens"], 4941);
assert_eq!(payload["cache_creation_input_tokens"], 687);
assert_eq!(payload["cache_read_input_tokens"], 52873);
}
#[test]
fn user_usage_active_pending_with_failure_signal_is_not_active() {
let item = StoredRequestUsageAudit {

View File

@@ -418,6 +418,54 @@ mod tests {
assert_eq!(usage.cache_read_tokens, 1);
}
#[test]
fn maps_claude_usage_with_large_cache_read_tokens_without_subtracting_input() {
let usage = map_usage(
&serde_json::json!({
"input_tokens": 4941,
"cache_creation_input_tokens": 687,
"cache_read_input_tokens": 52873,
"output_tokens": 973
}),
"claude:chat",
);
assert_eq!(usage.input_tokens, 4941);
assert_eq!(usage.cache_creation_tokens, 687);
assert_eq!(usage.cache_read_tokens, 52873);
assert_eq!(usage.output_tokens, 973);
}
#[test]
fn maps_claude_usage_with_cache_creation_total_and_zero_ttl_breakdown() {
let usage = map_usage(
&serde_json::json!({
"cache_creation": {
"ephemeral_1h_input_tokens": 0,
"ephemeral_5m_input_tokens": 0
},
"cache_creation_input_tokens": 2051,
"cache_read_input_tokens": 2051,
"inference_geo": "inference_geo",
"input_tokens": 2095,
"output_tokens": 503,
"server_tool_use": {
"web_fetch_requests": 2,
"web_search_requests": 0
},
"service_tier": "standard"
}),
"claude:chat",
);
assert_eq!(usage.input_tokens, 2095);
assert_eq!(usage.cache_creation_tokens, 2051);
assert_eq!(usage.cache_creation_ephemeral_5m_tokens, 0);
assert_eq!(usage.cache_creation_ephemeral_1h_tokens, 0);
assert_eq!(usage.cache_read_tokens, 2051);
assert_eq!(usage.output_tokens, 503);
}
#[test]
fn maps_claude_usage_with_ephemeral_cache_breakdown() {
let usage = map_usage(

View File

@@ -2180,16 +2180,7 @@ fn extract_token_counts_from_json(value: &Value) -> Option<(u64, u64, u64)> {
.get("totalTokenCount")
.and_then(Value::as_u64)
.unwrap_or(input + output);
let cache_read = usage
.get("cachedContentTokenCount")
.and_then(Value::as_u64)
.unwrap_or_default();
let total = if cache_read > 0 {
input.saturating_add(output).saturating_add(cache_read)
} else {
raw_total
};
return Some((input, output, total));
return Some((input, output, raw_total));
}
if let Some(chunks) = value.get("chunks").and_then(Value::as_array) {
@@ -2353,6 +2344,21 @@ mod tests {
assert_eq!(tokens, (6, 20, 41883));
}
#[test]
fn extracts_gemini_usage_tokens_without_adding_cached_content_twice() {
let tokens = extract_token_counts_from_json(&json!({
"usageMetadata": {
"promptTokenCount": 14,
"candidatesTokenCount": 6,
"cachedContentTokenCount": 2,
"totalTokenCount": 20
}
}))
.expect("tokens should exist");
assert_eq!(tokens, (14, 6, 20));
}
#[test]
fn extracts_usage_tokens_from_last_matching_chunk() {
let tokens = extract_token_counts_from_json(&json!({

View File

@@ -26,4 +26,14 @@ describe('usage token normalization', () => {
api_format: 'openai:chat',
})).toBe(80)
})
it('does not subtract cache read tokens for Claude usage', () => {
expect(getEffectiveInputTokens({
input_tokens: 4941,
cache_creation_input_tokens: 687,
cache_read_input_tokens: 52873,
output_tokens: 973,
api_format: 'claude:chat',
})).toBe(4941)
})
})