mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 01:10:23 +08:00
feat: 扩展 cache creation token 细分统计与 effective_input_tokens 计费逻辑
- 新增 cache_creation_ephemeral_5m/1h_input_tokens 字段,区分不同 TTL 的缓存写入 token - 引入 effective_input_tokens(扣除 cache read 后的有效输入 token),暴露给 usage 接口 - billing 规则生成器支持 5m/1h ephemeral cache 独立定价与分级计费 - usage_mapper 增加 Claude/Anthropic 格式映射,修复 OpenAI responses 格式字段兼容性 - 迁移逻辑增强:支持 checksum 容错、applied/pending 数量日志、逐步执行信息输出 - executor 抽离 LocalExecutionRequestOutcome 类型,统一 sync/stream 路径返回语义 - provider-transport auth 层新增 complete passthrough headers 构建逻辑 - 前端 usage 类型全面补充 effective_input_tokens、cache_creation_tokens、total_input_context 字段
This commit is contained in:
@@ -50,6 +50,14 @@ impl DefaultBillingRuleGenerator {
|
||||
"cache_creation_price_per_1m".to_string(),
|
||||
json!(base_cache_creation_price),
|
||||
);
|
||||
variables.insert(
|
||||
"cache_creation_ephemeral_5m_price_per_1m".to_string(),
|
||||
json!(base_cache_creation_price),
|
||||
);
|
||||
variables.insert(
|
||||
"cache_creation_ephemeral_1h_price_per_1m".to_string(),
|
||||
json!(base_cache_creation_price),
|
||||
);
|
||||
variables.insert(
|
||||
"cache_read_price_per_1m".to_string(),
|
||||
json!(base_cache_read_price),
|
||||
@@ -61,6 +69,21 @@ impl DefaultBillingRuleGenerator {
|
||||
("input_tokens", "input_tokens", json!(0)),
|
||||
("output_tokens", "output_tokens", json!(0)),
|
||||
("cache_creation_tokens", "cache_creation_tokens", json!(0)),
|
||||
(
|
||||
"cache_creation_ephemeral_5m_tokens",
|
||||
"cache_creation_ephemeral_5m_tokens",
|
||||
json!(0),
|
||||
),
|
||||
(
|
||||
"cache_creation_ephemeral_1h_tokens",
|
||||
"cache_creation_ephemeral_1h_tokens",
|
||||
json!(0),
|
||||
),
|
||||
(
|
||||
"cache_creation_uncategorized_tokens",
|
||||
"cache_creation_uncategorized_tokens",
|
||||
json!(0),
|
||||
),
|
||||
("cache_read_tokens", "cache_read_tokens", json!(0)),
|
||||
("request_count", "request_count", json!(1)),
|
||||
] {
|
||||
@@ -83,8 +106,16 @@ impl DefaultBillingRuleGenerator {
|
||||
"output_tokens * output_price_per_1m / 1000000",
|
||||
),
|
||||
(
|
||||
"cache_creation_cost",
|
||||
"cache_creation_tokens * cache_creation_price_per_1m / 1000000",
|
||||
"cache_creation_uncategorized_cost",
|
||||
"cache_creation_uncategorized_tokens * cache_creation_price_per_1m / 1000000",
|
||||
),
|
||||
(
|
||||
"cache_creation_ephemeral_5m_cost",
|
||||
"cache_creation_ephemeral_5m_tokens * cache_creation_ephemeral_5m_price_per_1m / 1000000",
|
||||
),
|
||||
(
|
||||
"cache_creation_ephemeral_1h_cost",
|
||||
"cache_creation_ephemeral_1h_tokens * cache_creation_ephemeral_1h_price_per_1m / 1000000",
|
||||
),
|
||||
(
|
||||
"cache_read_cost",
|
||||
@@ -136,6 +167,30 @@ impl DefaultBillingRuleGenerator {
|
||||
"default": base_cache_creation_price,
|
||||
}),
|
||||
);
|
||||
dimension_mappings.insert(
|
||||
"cache_creation_ephemeral_5m_price_per_1m".to_string(),
|
||||
json!({
|
||||
"source": "tiered",
|
||||
"tier_key": "total_input_context",
|
||||
"allow_zero": true,
|
||||
"ttl_key": "cache_creation_ephemeral_5m_ttl_minutes",
|
||||
"ttl_value_key": "cache_creation_price_per_1m",
|
||||
"tiers": build_tier_entries(&tiers, "cache_creation_price_per_1m", Some(1.25), true),
|
||||
"default": base_cache_creation_price,
|
||||
}),
|
||||
);
|
||||
dimension_mappings.insert(
|
||||
"cache_creation_ephemeral_1h_price_per_1m".to_string(),
|
||||
json!({
|
||||
"source": "tiered",
|
||||
"tier_key": "total_input_context",
|
||||
"allow_zero": true,
|
||||
"ttl_key": "cache_creation_ephemeral_1h_ttl_minutes",
|
||||
"ttl_value_key": "cache_creation_price_per_1m",
|
||||
"tiers": build_tier_entries(&tiers, "cache_creation_price_per_1m", Some(1.25), true),
|
||||
"default": base_cache_creation_price,
|
||||
}),
|
||||
);
|
||||
dimension_mappings.insert(
|
||||
"cache_read_price_per_1m".to_string(),
|
||||
json!({
|
||||
@@ -154,9 +209,7 @@ impl DefaultBillingRuleGenerator {
|
||||
id: "__default__".to_string(),
|
||||
name: format!("Default rule for {}", pricing.global_model_name),
|
||||
task_type: normalize_task_type(task_type).to_string(),
|
||||
expression:
|
||||
"input_cost + output_cost + cache_creation_cost + cache_read_cost + request_cost"
|
||||
.to_string(),
|
||||
expression: "input_cost + output_cost + cache_creation_uncategorized_cost + cache_creation_ephemeral_5m_cost + cache_creation_ephemeral_1h_cost + cache_read_cost + request_cost".to_string(),
|
||||
variables,
|
||||
dimension_mappings,
|
||||
scope: "default".to_string(),
|
||||
|
||||
@@ -73,6 +73,14 @@ pub async fn enrich_usage_event_with_billing(
|
||||
input_tokens: event.data.input_tokens.unwrap_or_default() as i64,
|
||||
output_tokens: event.data.output_tokens.unwrap_or_default() as i64,
|
||||
cache_creation_tokens: event.data.cache_creation_input_tokens.unwrap_or_default() as i64,
|
||||
cache_creation_ephemeral_5m_tokens: event
|
||||
.data
|
||||
.cache_creation_ephemeral_5m_input_tokens
|
||||
.unwrap_or_default() as i64,
|
||||
cache_creation_ephemeral_1h_tokens: event
|
||||
.data
|
||||
.cache_creation_ephemeral_1h_input_tokens
|
||||
.unwrap_or_default() as i64,
|
||||
cache_read_tokens: event.data.cache_read_input_tokens.unwrap_or_default() as i64,
|
||||
cache_ttl_minutes: pricing.provider_api_key_cache_ttl_minutes,
|
||||
};
|
||||
|
||||
@@ -66,6 +66,8 @@ pub struct BillingUsageInput {
|
||||
pub input_tokens: i64,
|
||||
pub output_tokens: i64,
|
||||
pub cache_creation_tokens: i64,
|
||||
pub cache_creation_ephemeral_5m_tokens: i64,
|
||||
pub cache_creation_ephemeral_1h_tokens: i64,
|
||||
pub cache_read_tokens: i64,
|
||||
pub cache_ttl_minutes: Option<i64>,
|
||||
}
|
||||
@@ -79,6 +81,8 @@ impl BillingUsageInput {
|
||||
input_tokens: 0,
|
||||
output_tokens: 0,
|
||||
cache_creation_tokens: 0,
|
||||
cache_creation_ephemeral_5m_tokens: 0,
|
||||
cache_creation_ephemeral_1h_tokens: 0,
|
||||
cache_read_tokens: 0,
|
||||
cache_ttl_minutes: None,
|
||||
}
|
||||
|
||||
@@ -129,6 +129,13 @@ fn build_dimensions(input: &BillingUsageInput) -> BTreeMap<String, Value> {
|
||||
input.input_tokens,
|
||||
input.cache_read_tokens,
|
||||
);
|
||||
let classified_cache_creation_tokens = input
|
||||
.cache_creation_ephemeral_5m_tokens
|
||||
.saturating_add(input.cache_creation_ephemeral_1h_tokens);
|
||||
let cache_creation_uncategorized_tokens = input
|
||||
.cache_creation_tokens
|
||||
.saturating_sub(classified_cache_creation_tokens)
|
||||
.max(0);
|
||||
let total_input_context = input
|
||||
.input_tokens
|
||||
.saturating_add(input.cache_creation_tokens)
|
||||
@@ -141,6 +148,18 @@ fn build_dimensions(input: &BillingUsageInput) -> BTreeMap<String, Value> {
|
||||
"cache_creation_tokens".to_string(),
|
||||
json!(input.cache_creation_tokens),
|
||||
),
|
||||
(
|
||||
"cache_creation_ephemeral_5m_tokens".to_string(),
|
||||
json!(input.cache_creation_ephemeral_5m_tokens),
|
||||
),
|
||||
(
|
||||
"cache_creation_ephemeral_1h_tokens".to_string(),
|
||||
json!(input.cache_creation_ephemeral_1h_tokens),
|
||||
),
|
||||
(
|
||||
"cache_creation_uncategorized_tokens".to_string(),
|
||||
json!(cache_creation_uncategorized_tokens),
|
||||
),
|
||||
(
|
||||
"cache_read_tokens".to_string(),
|
||||
json!(input.cache_read_tokens),
|
||||
@@ -159,6 +178,15 @@ fn build_dimensions(input: &BillingUsageInput) -> BTreeMap<String, Value> {
|
||||
),
|
||||
]);
|
||||
|
||||
out.insert(
|
||||
"cache_creation_ephemeral_5m_ttl_minutes".to_string(),
|
||||
json!(5),
|
||||
);
|
||||
out.insert(
|
||||
"cache_creation_ephemeral_1h_ttl_minutes".to_string(),
|
||||
json!(60),
|
||||
);
|
||||
|
||||
if let Some(cache_ttl_minutes) = input.cache_ttl_minutes {
|
||||
out.insert(
|
||||
"cache_ttl_minutes".to_string(),
|
||||
@@ -223,6 +251,8 @@ mod tests {
|
||||
input_tokens: 1_000,
|
||||
output_tokens: 500,
|
||||
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),
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user