mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-08 02:17:46 +08:00
fix(usage): align fast-tier pricing and model metadata
This commit is contained in:
@@ -702,6 +702,82 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn openai_fast_usage_without_overlay_inherits_global_model_pricing() {
|
||||
let lookup = TestLookup {
|
||||
name_context: Some(
|
||||
StoredBillingModelContext::new(
|
||||
"provider-1".to_string(),
|
||||
Some("pay_as_you_go".to_string()),
|
||||
Some("key-1".to_string()),
|
||||
None,
|
||||
None,
|
||||
"global-model-1".to_string(),
|
||||
"gpt-5.6-sol".to_string(),
|
||||
None,
|
||||
None,
|
||||
Some(json!({
|
||||
"tiers": [{
|
||||
"up_to": null,
|
||||
"input_price_per_1m": 3.0,
|
||||
"output_price_per_1m": 15.0
|
||||
}]
|
||||
})),
|
||||
Some("model-1".to_string()),
|
||||
Some("gpt-5.6-sol".to_string()),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.expect("billing context should build"),
|
||||
),
|
||||
model_id_context: None,
|
||||
};
|
||||
let mut event = UsageEvent::new(
|
||||
UsageEventType::Completed,
|
||||
"req-fast-global-fallback",
|
||||
UsageEventData {
|
||||
provider_name: "OpenAI".to_string(),
|
||||
model: "gpt-5.6-sol".to_string(),
|
||||
target_model: Some("gpt-5.6-sol".to_string()),
|
||||
provider_id: Some("provider-1".to_string()),
|
||||
provider_api_key_id: Some("key-1".to_string()),
|
||||
request_type: Some("chat".to_string()),
|
||||
api_format: Some("openai:responses".to_string()),
|
||||
endpoint_api_format: Some("openai:responses".to_string()),
|
||||
// OpenAI calls the Fast request tier `priority` on the wire.
|
||||
provider_request_body: Some(json!({
|
||||
"model": "gpt-5.6-sol",
|
||||
"service_tier": "priority"
|
||||
})),
|
||||
input_tokens: Some(1_000_000),
|
||||
status_code: Some(200),
|
||||
..UsageEventData::default()
|
||||
},
|
||||
);
|
||||
|
||||
enrich_usage_event_with_billing(&lookup, &mut event)
|
||||
.await
|
||||
.expect("billing should succeed");
|
||||
|
||||
assert_eq!(event.data.total_cost_usd, Some(3.0));
|
||||
let metadata = event.data.request_metadata.as_ref().expect("metadata");
|
||||
assert_eq!(
|
||||
metadata.pointer("/billing_snapshot/status"),
|
||||
Some(&json!("complete"))
|
||||
);
|
||||
let pricing = metadata
|
||||
.pointer("/settlement_snapshot/pricing_snapshot")
|
||||
.expect("settlement pricing snapshot");
|
||||
assert_eq!(pricing["billing_processing_tier"], "priority");
|
||||
assert_eq!(pricing["pricing_source"], "global_default");
|
||||
assert_eq!(pricing["tiered_pricing_source"], "global_default");
|
||||
assert_eq!(
|
||||
pricing["tiered_pricing"]["tiers"][0]["input_price_per_1m"],
|
||||
3.0
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn settlement_uses_requested_processing_tier_catalog_and_ignores_response_tier() {
|
||||
let lookup = TestLookup {
|
||||
|
||||
@@ -192,6 +192,7 @@ impl BillingModelPricingSnapshot {
|
||||
)?;
|
||||
if !processing_tier_is_standard(&requested_billing_tier)
|
||||
&& requested_resolution.tiered_pricing.is_none()
|
||||
&& requested_resolution.price_per_request.is_none()
|
||||
{
|
||||
return Ok(None);
|
||||
}
|
||||
@@ -295,7 +296,9 @@ impl BillingModelPricingSnapshot {
|
||||
processing_tier,
|
||||
reason,
|
||||
)),
|
||||
ProcessingTierOverlay::Missing => Ok(None),
|
||||
ProcessingTierOverlay::Missing => Ok(self
|
||||
.resolve_standard_tiered_pricing_checked()?
|
||||
.map(|(pricing, source)| (pricing.clone(), source, None))),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -799,6 +802,54 @@ mod tests {
|
||||
assert_eq!(resolution.processing_tier_price_multiplier, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_processing_overlay_inherits_effective_standard_catalog() {
|
||||
let provider_pricing = json!({"tiers":[{"up_to":null,"input_price_per_1m":2.0}]});
|
||||
let default_pricing = json!({"tiers":[{"up_to":null,"input_price_per_1m":3.0}]});
|
||||
|
||||
let provider = snapshot(
|
||||
Some(provider_pricing.clone()),
|
||||
Some(default_pricing.clone()),
|
||||
)
|
||||
.resolve_pricing(Some("fast"), None);
|
||||
assert_eq!(provider.billing_processing_tier.as_deref(), Some("fast"));
|
||||
assert_eq!(provider.tiered_pricing, Some(provider_pricing));
|
||||
assert_eq!(
|
||||
provider.tiered_pricing_source,
|
||||
Some(BillingPricingSource::ProviderOverride)
|
||||
);
|
||||
assert_eq!(provider.processing_tier_price_multiplier, None);
|
||||
|
||||
let global =
|
||||
snapshot(None, Some(default_pricing.clone())).resolve_pricing(Some("priority"), None);
|
||||
assert_eq!(global.billing_processing_tier.as_deref(), Some("priority"));
|
||||
assert_eq!(global.tiered_pricing, Some(default_pricing));
|
||||
assert_eq!(
|
||||
global.tiered_pricing_source,
|
||||
Some(BillingPricingSource::GlobalDefault)
|
||||
);
|
||||
assert_eq!(global.processing_tier_price_multiplier, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nonstandard_tier_keeps_fixed_request_price_without_token_catalog() {
|
||||
let mut pricing = snapshot(None, None);
|
||||
pricing.default_price_per_request = Some(0.02);
|
||||
|
||||
let resolution = pricing.resolve_pricing(Some("fast"), None);
|
||||
assert_eq!(resolution.billing_processing_tier.as_deref(), Some("fast"));
|
||||
assert_eq!(resolution.tiered_pricing, None);
|
||||
assert_eq!(resolution.price_per_request, Some(0.02));
|
||||
assert_eq!(
|
||||
resolution.price_per_request_source,
|
||||
Some(BillingPricingSource::GlobalDefault)
|
||||
);
|
||||
assert!(pricing
|
||||
.resolve_authorization_pricing_candidates(Some("fast"))
|
||||
.expect("fixed request pricing should be valid")
|
||||
.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn explicit_nonstandard_request_selects_requested_catalog_without_actual_tier() {
|
||||
let pricing = snapshot(
|
||||
|
||||
@@ -174,6 +174,7 @@ impl BillingService {
|
||||
}
|
||||
if !pricing_resolution.bills_standard_processing_tier()
|
||||
&& pricing_resolution.tiered_pricing.is_none()
|
||||
&& pricing_resolution.price_per_request.is_none()
|
||||
{
|
||||
return Ok(no_rule_computation(
|
||||
pricing,
|
||||
@@ -1123,6 +1124,64 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn openai_fast_request_without_overlay_uses_global_standard_catalog() {
|
||||
let pricing = BillingModelPricingSnapshot {
|
||||
default_price_per_request: None,
|
||||
default_tiered_pricing: Some(json!({
|
||||
"tiers": [{
|
||||
"up_to": null,
|
||||
"input_price_per_1m": 3.0,
|
||||
"output_price_per_1m": 15.0
|
||||
}]
|
||||
})),
|
||||
model_tiered_pricing: None,
|
||||
..pricing()
|
||||
};
|
||||
|
||||
let result = BillingService::new()
|
||||
.calculate(
|
||||
&pricing,
|
||||
&BillingUsageInput {
|
||||
api_format: Some("openai:responses".to_string()),
|
||||
requested_processing_tier: Some("priority".to_string()),
|
||||
input_tokens: 1_000_000,
|
||||
..BillingUsageInput::new("chat")
|
||||
},
|
||||
)
|
||||
.expect("global Standard pricing should calculate OpenAI Fast usage");
|
||||
|
||||
assert_eq!(result.cost_result.status, BillingSnapshotStatus::Complete);
|
||||
assert_eq!(result.cost_result.cost, 3.0);
|
||||
assert_eq!(
|
||||
result.pricing_resolution.tiered_pricing_source,
|
||||
Some(crate::BillingPricingSource::GlobalDefault)
|
||||
);
|
||||
assert_eq!(
|
||||
result.cost_result.snapshot.resolved_variables["input_price_per_1m"],
|
||||
json!(3.0)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nonstandard_request_with_only_fixed_price_is_still_billable() {
|
||||
let pricing = BillingModelPricingSnapshot {
|
||||
default_price_per_request: Some(0.02),
|
||||
default_tiered_pricing: None,
|
||||
model_tiered_pricing: None,
|
||||
..pricing()
|
||||
};
|
||||
|
||||
let result = BillingService::new()
|
||||
.calculate(&pricing, &processing_usage(Some("fast"), None, 1_000))
|
||||
.expect("fixed request pricing should calculate Fast usage");
|
||||
|
||||
assert_eq!(result.cost_result.status, BillingSnapshotStatus::Complete);
|
||||
assert_eq!(result.cost_result.cost, 0.02);
|
||||
assert_eq!(result.pricing_resolution.tiered_pricing, None);
|
||||
assert_eq!(result.pricing_resolution.price_per_request, Some(0.02));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn response_actual_tier_does_not_override_requested_catalog() {
|
||||
let cases = ["default", "flex", "priority"];
|
||||
@@ -1592,8 +1651,8 @@ mod tests {
|
||||
assert_eq!(
|
||||
service
|
||||
.estimate_authorization_cost_upper_bound(&processing_pricing(), &estimate)
|
||||
.expect("unknown tier estimate should resolve"),
|
||||
None
|
||||
.expect("unknown tier should inherit the effective Standard catalog"),
|
||||
Some(0.000925)
|
||||
);
|
||||
|
||||
estimate.requested_processing_tier = Some("priority".to_string());
|
||||
|
||||
Reference in New Issue
Block a user