mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 17:30:23 +08:00
fix: 修正时间戳精度、TTL 定价匹配逻辑及账单快照展示
- 将 created_at_unix_ms 从 current_unix_secs 改为 current_unix_ms,修正候选尝试和跳过记录的时间戳精度 - formula_engine: TTL 定价从「<=上限」模糊匹配改为精确匹配,null 值改为回退到基础价格而非透传 - 新增 ttl_pricing_requires_exact_match 和 ttl_pricing_null_value_falls_back_to_base_tier_value 测试用例 - service: 新增 5min/1h cache TTL 的端到端计费验证测试 - RequestDetailDrawer: 优先从 billing_snapshot 读取已解析的价格和费用,正确展示当前 TTL 对应的缓存创建/读取价格,修正输入/输出/缓存费用列的数据来源
This commit is contained in:
@@ -264,4 +264,142 @@ mod tests {
|
||||
assert!(result.actual_total_cost > 0.0);
|
||||
assert_eq!(result.rate_multiplier, 0.5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn five_minute_cache_ttl_uses_base_cache_prices() {
|
||||
let pricing = BillingModelPricingSnapshot {
|
||||
provider_id: "provider-1".to_string(),
|
||||
provider_billing_type: Some("pay_as_you_go".to_string()),
|
||||
provider_api_key_id: Some("key-1".to_string()),
|
||||
provider_api_key_rate_multipliers: None,
|
||||
provider_api_key_cache_ttl_minutes: Some(5),
|
||||
global_model_id: "global-model-1".to_string(),
|
||||
global_model_name: "gpt-5.4".to_string(),
|
||||
global_model_config: None,
|
||||
default_price_per_request: None,
|
||||
default_tiered_pricing: Some(json!({
|
||||
"tiers": [{
|
||||
"up_to": null,
|
||||
"input_price_per_1m": 2.5,
|
||||
"output_price_per_1m": 15.0,
|
||||
"cache_creation_price_per_1m": 3.125,
|
||||
"cache_read_price_per_1m": 0.25,
|
||||
"cache_ttl_pricing": [{
|
||||
"ttl_minutes": 60,
|
||||
"cache_creation_price_per_1m": 5.0,
|
||||
"cache_read_price_per_1m": null
|
||||
}]
|
||||
}]
|
||||
})),
|
||||
model_id: None,
|
||||
model_provider_model_name: None,
|
||||
model_config: None,
|
||||
model_price_per_request: None,
|
||||
model_tiered_pricing: None,
|
||||
};
|
||||
|
||||
let result = BillingService::new()
|
||||
.calculate(
|
||||
&pricing,
|
||||
&BillingUsageInput {
|
||||
task_type: "chat".to_string(),
|
||||
api_format: None,
|
||||
request_count: 1,
|
||||
input_tokens: 1_000,
|
||||
output_tokens: 10,
|
||||
cache_creation_tokens: 0,
|
||||
cache_creation_ephemeral_5m_tokens: 0,
|
||||
cache_creation_ephemeral_1h_tokens: 0,
|
||||
cache_read_tokens: 100,
|
||||
cache_ttl_minutes: Some(5),
|
||||
},
|
||||
)
|
||||
.expect("billing should calculate");
|
||||
|
||||
assert_eq!(
|
||||
result
|
||||
.cost_result
|
||||
.snapshot
|
||||
.resolved_variables
|
||||
.get("cache_creation_price_per_1m"),
|
||||
Some(&json!(3.125))
|
||||
);
|
||||
assert_eq!(
|
||||
result
|
||||
.cost_result
|
||||
.snapshot
|
||||
.resolved_variables
|
||||
.get("cache_read_price_per_1m"),
|
||||
Some(&json!(0.25))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn one_hour_cache_ttl_keeps_base_cache_read_when_ttl_entry_omits_it() {
|
||||
let pricing = BillingModelPricingSnapshot {
|
||||
provider_id: "provider-1".to_string(),
|
||||
provider_billing_type: Some("pay_as_you_go".to_string()),
|
||||
provider_api_key_id: Some("key-1".to_string()),
|
||||
provider_api_key_rate_multipliers: None,
|
||||
provider_api_key_cache_ttl_minutes: Some(60),
|
||||
global_model_id: "global-model-1".to_string(),
|
||||
global_model_name: "gpt-5.4".to_string(),
|
||||
global_model_config: None,
|
||||
default_price_per_request: None,
|
||||
default_tiered_pricing: Some(json!({
|
||||
"tiers": [{
|
||||
"up_to": null,
|
||||
"input_price_per_1m": 2.5,
|
||||
"output_price_per_1m": 15.0,
|
||||
"cache_creation_price_per_1m": 3.125,
|
||||
"cache_read_price_per_1m": 0.25,
|
||||
"cache_ttl_pricing": [{
|
||||
"ttl_minutes": 60,
|
||||
"cache_creation_price_per_1m": 5.0,
|
||||
"cache_read_price_per_1m": null
|
||||
}]
|
||||
}]
|
||||
})),
|
||||
model_id: None,
|
||||
model_provider_model_name: None,
|
||||
model_config: None,
|
||||
model_price_per_request: None,
|
||||
model_tiered_pricing: None,
|
||||
};
|
||||
|
||||
let result = BillingService::new()
|
||||
.calculate(
|
||||
&pricing,
|
||||
&BillingUsageInput {
|
||||
task_type: "chat".to_string(),
|
||||
api_format: None,
|
||||
request_count: 1,
|
||||
input_tokens: 1_000,
|
||||
output_tokens: 10,
|
||||
cache_creation_tokens: 0,
|
||||
cache_creation_ephemeral_5m_tokens: 0,
|
||||
cache_creation_ephemeral_1h_tokens: 0,
|
||||
cache_read_tokens: 100,
|
||||
cache_ttl_minutes: Some(60),
|
||||
},
|
||||
)
|
||||
.expect("billing should calculate");
|
||||
|
||||
assert_eq!(
|
||||
result
|
||||
.cost_result
|
||||
.snapshot
|
||||
.resolved_variables
|
||||
.get("cache_creation_price_per_1m"),
|
||||
Some(&json!(5.0))
|
||||
);
|
||||
assert_eq!(
|
||||
result
|
||||
.cost_result
|
||||
.snapshot
|
||||
.resolved_variables
|
||||
.get("cache_read_price_per_1m"),
|
||||
Some(&json!(0.25))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user