mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-08-04 11:43:57 +08:00
revert(proxy): drop the 1-hour cache TTL option and TTL-bucketed write accounting
The forced 1-hour cache_control TTL (schema v14) was a mistake. Injected breakpoints return to Anthropic's standard 5-minute TTL, caller-owned markers are preserved verbatim instead of having their TTLs rewritten, and the 5m/1h cache-write buckets are removed from usage parsing and pricing (back to the single aggregate cache-creation rate). The cache TTL selector is removed from the rectifier settings panel along with its i18n keys in all four locales. SCHEMA_VERSION returns to 13: the unreleased cache_creation_1h_tokens column and the v13->v14 migration are removed. The feature never shipped in a release and the introducing commit was never pushed, so no databases were stamped v14 outside this machine (local DB verified at user_version 13).
This commit is contained in:
@@ -94,20 +94,9 @@ impl CostCalculator {
|
||||
Decimal::from(usage.output_tokens) * pricing.output_cost_per_million / million;
|
||||
let cache_read_cost =
|
||||
Decimal::from(usage.cache_read_tokens) * pricing.cache_read_cost_per_million / million;
|
||||
let (cache_creation_unspecified, cache_creation_5m, cache_creation_1h) =
|
||||
usage.normalized_cache_creation_buckets();
|
||||
let cache_creation_5m_price = pricing.cache_creation_cost_per_million;
|
||||
// The stored cache-creation price is the existing 5-minute rate. Anthropic
|
||||
// prices 5m writes at 1.25x input and 1h writes at 2x input, therefore the
|
||||
// corresponding 1h price is 8/5 of the configured 5m price. Unknown providers
|
||||
// without TTL details retain the configured legacy rate.
|
||||
let cache_creation_1h_price =
|
||||
cache_creation_5m_price * Decimal::from(8u32) / Decimal::from(5u32);
|
||||
let cache_creation_cost = (Decimal::from(cache_creation_unspecified)
|
||||
+ Decimal::from(cache_creation_5m))
|
||||
* cache_creation_5m_price
|
||||
/ million
|
||||
+ Decimal::from(cache_creation_1h) * cache_creation_1h_price / million;
|
||||
let cache_creation_cost = Decimal::from(usage.cache_creation_tokens)
|
||||
* pricing.cache_creation_cost_per_million
|
||||
/ million;
|
||||
|
||||
// 总成本 = 各项基础成本之和 × 倍率
|
||||
let base_total = input_cost + output_cost + cache_read_cost + cache_creation_cost;
|
||||
@@ -170,8 +159,6 @@ mod tests {
|
||||
output_tokens: 500,
|
||||
cache_read_tokens: 200,
|
||||
cache_creation_tokens: 100,
|
||||
cache_creation_5m_tokens: 0,
|
||||
cache_creation_1h_tokens: 0,
|
||||
model: None,
|
||||
message_id: None,
|
||||
};
|
||||
@@ -204,8 +191,6 @@ mod tests {
|
||||
output_tokens: 500,
|
||||
cache_read_tokens: 200,
|
||||
cache_creation_tokens: 100,
|
||||
cache_creation_5m_tokens: 0,
|
||||
cache_creation_1h_tokens: 0,
|
||||
model: None,
|
||||
message_id: None,
|
||||
};
|
||||
@@ -226,25 +211,6 @@ mod tests {
|
||||
assert_eq!(cost.total_cost, Decimal::from_str("0.010035").unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_one_hour_cache_creation_uses_premium_rate() {
|
||||
let usage = TokenUsage {
|
||||
cache_creation_tokens: 1_000_000,
|
||||
cache_creation_5m_tokens: 250_000,
|
||||
cache_creation_1h_tokens: 750_000,
|
||||
..TokenUsage::default()
|
||||
};
|
||||
let pricing = ModelPricing::from_strings("3", "15", "0.3", "3.75").unwrap();
|
||||
|
||||
let cost = CostCalculator::calculate(&usage, &pricing, Decimal::ONE);
|
||||
|
||||
// 250k × $3.75/M + 750k × $6/M = $5.4375.
|
||||
assert_eq!(
|
||||
cost.cache_creation_cost,
|
||||
Decimal::from_str("5.4375").unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cost_multiplier() {
|
||||
let usage = TokenUsage {
|
||||
@@ -252,8 +218,6 @@ mod tests {
|
||||
output_tokens: 0,
|
||||
cache_read_tokens: 0,
|
||||
cache_creation_tokens: 0,
|
||||
cache_creation_5m_tokens: 0,
|
||||
cache_creation_1h_tokens: 0,
|
||||
model: None,
|
||||
message_id: None,
|
||||
};
|
||||
@@ -276,8 +240,6 @@ mod tests {
|
||||
output_tokens: 500,
|
||||
cache_read_tokens: 0,
|
||||
cache_creation_tokens: 0,
|
||||
cache_creation_5m_tokens: 0,
|
||||
cache_creation_1h_tokens: 0,
|
||||
model: None,
|
||||
message_id: None,
|
||||
};
|
||||
@@ -295,8 +257,6 @@ mod tests {
|
||||
output_tokens: 1,
|
||||
cache_read_tokens: 1,
|
||||
cache_creation_tokens: 1,
|
||||
cache_creation_5m_tokens: 0,
|
||||
cache_creation_1h_tokens: 0,
|
||||
model: None,
|
||||
message_id: None,
|
||||
};
|
||||
|
||||
@@ -81,12 +81,11 @@ impl<'a> UsageLogger<'a> {
|
||||
"INSERT OR REPLACE INTO proxy_request_logs (
|
||||
request_id, provider_id, app_type, model, request_model, pricing_model,
|
||||
input_tokens, output_tokens, cache_read_tokens, cache_creation_tokens,
|
||||
cache_creation_1h_tokens,
|
||||
input_token_semantics,
|
||||
input_cost_usd, output_cost_usd, cache_read_cost_usd, cache_creation_cost_usd, total_cost_usd,
|
||||
latency_ms, first_token_ms, status_code, error_message, session_id,
|
||||
provider_type, is_streaming, cost_multiplier, created_at
|
||||
) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17, ?18, ?19, ?20, ?21, ?22, ?23, ?24, ?25, ?26)",
|
||||
) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17, ?18, ?19, ?20, ?21, ?22, ?23, ?24, ?25)",
|
||||
rusqlite::params![
|
||||
log.request_id,
|
||||
log.provider_id,
|
||||
@@ -98,7 +97,6 @@ impl<'a> UsageLogger<'a> {
|
||||
log.usage.output_tokens,
|
||||
log.usage.cache_read_tokens,
|
||||
log.usage.cache_creation_tokens,
|
||||
log.usage.cache_creation_1h_tokens,
|
||||
input_token_semantics,
|
||||
input_cost,
|
||||
output_cost,
|
||||
@@ -398,8 +396,6 @@ mod tests {
|
||||
output_tokens: 500,
|
||||
cache_read_tokens: 0,
|
||||
cache_creation_tokens: 0,
|
||||
cache_creation_5m_tokens: 0,
|
||||
cache_creation_1h_tokens: 0,
|
||||
model: None,
|
||||
message_id: None,
|
||||
};
|
||||
|
||||
@@ -27,22 +27,6 @@ fn openai_cache_write_tokens(usage: &Value) -> u32 {
|
||||
.unwrap_or(0) as u32
|
||||
}
|
||||
|
||||
fn cache_creation_ttl_tokens(usage: &Value) -> (u32, u32) {
|
||||
let details = usage
|
||||
.get("cache_creation")
|
||||
.or_else(|| usage.pointer("/input_tokens_details/cache_creation"))
|
||||
.or_else(|| usage.pointer("/prompt_tokens_details/cache_creation"));
|
||||
let five_minutes = details
|
||||
.and_then(|value| value.get("ephemeral_5m_input_tokens"))
|
||||
.and_then(Value::as_u64)
|
||||
.unwrap_or(0) as u32;
|
||||
let one_hour = details
|
||||
.and_then(|value| value.get("ephemeral_1h_input_tokens"))
|
||||
.and_then(Value::as_u64)
|
||||
.unwrap_or(0) as u32;
|
||||
(five_minutes, one_hour)
|
||||
}
|
||||
|
||||
/// Session 日志 request_id 前缀,与 `session_usage.rs` 中的格式保持一致
|
||||
pub const SESSION_REQUEST_ID_PREFIX: &str = "session:";
|
||||
|
||||
@@ -53,13 +37,6 @@ pub struct TokenUsage {
|
||||
pub output_tokens: u32,
|
||||
pub cache_read_tokens: u32,
|
||||
pub cache_creation_tokens: u32,
|
||||
/// Anthropic cache-write TTL detail. The aggregate above remains the stable
|
||||
/// public/storage metric; these buckets make 1-hour writes billable at 2x input
|
||||
/// instead of the 5-minute 1.25x rate.
|
||||
#[serde(default)]
|
||||
pub cache_creation_5m_tokens: u32,
|
||||
#[serde(default)]
|
||||
pub cache_creation_1h_tokens: u32,
|
||||
/// 从响应中提取的实际模型名称(如果可用)
|
||||
pub model: Option<String>,
|
||||
/// 从响应中提取的消息 ID(用于跨源去重)
|
||||
@@ -70,23 +47,6 @@ pub struct TokenUsage {
|
||||
}
|
||||
|
||||
impl TokenUsage {
|
||||
/// Return mutually exclusive cache-write buckets, clamped to the aggregate.
|
||||
/// Providers that do not expose TTL detail remain in `unspecified` and keep the
|
||||
/// legacy cache-creation price.
|
||||
pub fn normalized_cache_creation_buckets(&self) -> (u32, u32, u32) {
|
||||
let one_hour = self
|
||||
.cache_creation_1h_tokens
|
||||
.min(self.cache_creation_tokens);
|
||||
let five_minutes = self
|
||||
.cache_creation_5m_tokens
|
||||
.min(self.cache_creation_tokens.saturating_sub(one_hour));
|
||||
let unspecified = self
|
||||
.cache_creation_tokens
|
||||
.saturating_sub(one_hour)
|
||||
.saturating_sub(five_minutes);
|
||||
(unspecified, five_minutes, one_hour)
|
||||
}
|
||||
|
||||
/// 生成与 session 日志共享的 request_id,用于跨源去重。
|
||||
/// 有 message_id 时返回 `session:{id}`,否则回退到随机 UUID。
|
||||
pub fn dedup_request_id(&self) -> String {
|
||||
@@ -123,7 +83,6 @@ impl TokenUsage {
|
||||
/// 从 Claude API 非流式响应解析
|
||||
pub fn from_claude_response(body: &Value) -> Option<Self> {
|
||||
let usage = body.get("usage")?;
|
||||
let (cache_creation_5m_tokens, cache_creation_1h_tokens) = cache_creation_ttl_tokens(usage);
|
||||
// 提取响应中的模型名称
|
||||
let model = body
|
||||
.get("model")
|
||||
@@ -145,8 +104,6 @@ impl TokenUsage {
|
||||
.get("cache_creation_input_tokens")
|
||||
.and_then(|v| v.as_u64())
|
||||
.unwrap_or(0) as u32,
|
||||
cache_creation_5m_tokens,
|
||||
cache_creation_1h_tokens,
|
||||
model,
|
||||
message_id,
|
||||
})
|
||||
@@ -177,8 +134,6 @@ impl TokenUsage {
|
||||
}
|
||||
}
|
||||
if let Some(msg_usage) = event.get("message").and_then(|m| m.get("usage")) {
|
||||
let (cache_creation_5m, cache_creation_1h) =
|
||||
cache_creation_ttl_tokens(msg_usage);
|
||||
// 从 message_start 获取 input_tokens(原生 Claude API)
|
||||
if let Some(input) =
|
||||
msg_usage.get("input_tokens").and_then(|v| v.as_u64())
|
||||
@@ -195,14 +150,10 @@ impl TokenUsage {
|
||||
.and_then(|v| v.as_u64())
|
||||
.unwrap_or(0)
|
||||
as u32;
|
||||
usage.cache_creation_5m_tokens = cache_creation_5m;
|
||||
usage.cache_creation_1h_tokens = cache_creation_1h;
|
||||
}
|
||||
}
|
||||
"message_delta" => {
|
||||
if let Some(delta_usage) = event.get("usage") {
|
||||
let (delta_cache_creation_5m, delta_cache_creation_1h) =
|
||||
cache_creation_ttl_tokens(delta_usage);
|
||||
// 从 message_delta 获取 output_tokens
|
||||
if let Some(output) =
|
||||
delta_usage.get("output_tokens").and_then(|v| v.as_u64())
|
||||
@@ -241,14 +192,6 @@ impl TokenUsage {
|
||||
}
|
||||
if let Some(cache_creation) = delta_cache_creation {
|
||||
usage.cache_creation_tokens = cache_creation;
|
||||
if delta_cache_creation_5m > 0
|
||||
|| delta_cache_creation_1h > 0
|
||||
{
|
||||
usage.cache_creation_5m_tokens =
|
||||
delta_cache_creation_5m;
|
||||
usage.cache_creation_1h_tokens =
|
||||
delta_cache_creation_1h;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -263,10 +206,6 @@ impl TokenUsage {
|
||||
if usage.cache_creation_tokens == 0 {
|
||||
if let Some(cache_creation) = delta_cache_creation {
|
||||
usage.cache_creation_tokens = cache_creation;
|
||||
if delta_cache_creation_5m > 0 || delta_cache_creation_1h > 0 {
|
||||
usage.cache_creation_5m_tokens = delta_cache_creation_5m;
|
||||
usage.cache_creation_1h_tokens = delta_cache_creation_1h;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -298,8 +237,6 @@ impl TokenUsage {
|
||||
output_tokens: usage.get("completion_tokens")?.as_u64()? as u32,
|
||||
cache_read_tokens: 0,
|
||||
cache_creation_tokens: 0,
|
||||
cache_creation_5m_tokens: 0,
|
||||
cache_creation_1h_tokens: 0,
|
||||
model: None,
|
||||
message_id: None,
|
||||
})
|
||||
@@ -333,15 +270,12 @@ impl TokenUsage {
|
||||
|
||||
let cached_tokens = openai_cache_read_tokens(usage);
|
||||
let cache_write_tokens = openai_cache_write_tokens(usage);
|
||||
let (cache_creation_5m_tokens, cache_creation_1h_tokens) = cache_creation_ttl_tokens(usage);
|
||||
|
||||
Some(Self {
|
||||
input_tokens: input_tokens? as u32,
|
||||
output_tokens: output_tokens? as u32,
|
||||
cache_read_tokens: cached_tokens,
|
||||
cache_creation_tokens: cache_write_tokens,
|
||||
cache_creation_5m_tokens,
|
||||
cache_creation_1h_tokens,
|
||||
model,
|
||||
message_id: None,
|
||||
})
|
||||
@@ -360,7 +294,6 @@ impl TokenUsage {
|
||||
// 获取 cached_tokens (可能在 cache_read_input_tokens 或 input_tokens_details 中)
|
||||
let cached_tokens = openai_cache_read_tokens(usage);
|
||||
let cache_write_tokens = openai_cache_write_tokens(usage);
|
||||
let (cache_creation_5m_tokens, cache_creation_1h_tokens) = cache_creation_ttl_tokens(usage);
|
||||
|
||||
// 调整 input_tokens: OpenAI total input 同时包含 cache read/write 两桶。
|
||||
let adjusted_input = input_tokens
|
||||
@@ -378,8 +311,6 @@ impl TokenUsage {
|
||||
output_tokens,
|
||||
cache_read_tokens: cached_tokens,
|
||||
cache_creation_tokens: cache_write_tokens,
|
||||
cache_creation_5m_tokens,
|
||||
cache_creation_1h_tokens,
|
||||
model,
|
||||
message_id: None,
|
||||
})
|
||||
@@ -460,7 +391,6 @@ impl TokenUsage {
|
||||
// 获取 cached_tokens (可能在 prompt_tokens_details 中)
|
||||
let cached_tokens = openai_cache_read_tokens(usage);
|
||||
let cache_write_tokens = openai_cache_write_tokens(usage);
|
||||
let (cache_creation_5m_tokens, cache_creation_1h_tokens) = cache_creation_ttl_tokens(usage);
|
||||
|
||||
// 提取响应中的模型名称
|
||||
let model = body
|
||||
@@ -473,8 +403,6 @@ impl TokenUsage {
|
||||
output_tokens: completion_tokens as u32,
|
||||
cache_read_tokens: cached_tokens,
|
||||
cache_creation_tokens: cache_write_tokens,
|
||||
cache_creation_5m_tokens,
|
||||
cache_creation_1h_tokens,
|
||||
model,
|
||||
message_id: None,
|
||||
})
|
||||
@@ -520,8 +448,6 @@ impl TokenUsage {
|
||||
.and_then(|v| v.as_u64())
|
||||
.unwrap_or(0) as u32,
|
||||
cache_creation_tokens: 0,
|
||||
cache_creation_5m_tokens: 0,
|
||||
cache_creation_1h_tokens: 0,
|
||||
model,
|
||||
message_id: None,
|
||||
})
|
||||
@@ -573,8 +499,6 @@ impl TokenUsage {
|
||||
output_tokens: total_output,
|
||||
cache_read_tokens: total_cache_read,
|
||||
cache_creation_tokens: 0,
|
||||
cache_creation_5m_tokens: 0,
|
||||
cache_creation_1h_tokens: 0,
|
||||
model,
|
||||
message_id: None,
|
||||
})
|
||||
@@ -597,11 +521,7 @@ mod tests {
|
||||
"input_tokens": 100,
|
||||
"output_tokens": 50,
|
||||
"cache_read_input_tokens": 20,
|
||||
"cache_creation_input_tokens": 10,
|
||||
"cache_creation": {
|
||||
"ephemeral_5m_input_tokens": 4,
|
||||
"ephemeral_1h_input_tokens": 6
|
||||
}
|
||||
"cache_creation_input_tokens": 10
|
||||
}
|
||||
});
|
||||
|
||||
@@ -610,9 +530,6 @@ mod tests {
|
||||
assert_eq!(usage.output_tokens, 50);
|
||||
assert_eq!(usage.cache_read_tokens, 20);
|
||||
assert_eq!(usage.cache_creation_tokens, 10);
|
||||
assert_eq!(usage.cache_creation_5m_tokens, 4);
|
||||
assert_eq!(usage.cache_creation_1h_tokens, 6);
|
||||
assert_eq!(usage.normalized_cache_creation_buckets(), (0, 4, 6));
|
||||
assert_eq!(usage.model, Some("claude-sonnet-4-20250514".to_string()));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user