mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-09 02:47:45 +08:00
fix(models): correct fast pricing and online sync
This commit is contained in:
@@ -360,13 +360,14 @@ impl BillingModelPricingSnapshot {
|
||||
} else {
|
||||
BillingPricingSource::Mixed
|
||||
};
|
||||
let pricing = multiply_pricing_catalog(standard, multiplier).ok_or_else(|| {
|
||||
invalid_processing_tier_error(
|
||||
multiplier_source,
|
||||
processing_tier,
|
||||
"price_multiplier produced an invalid pricing catalog",
|
||||
)
|
||||
})?;
|
||||
let pricing =
|
||||
multiply_pricing_catalog(standard, multiplier, processing_tier).ok_or_else(|| {
|
||||
invalid_processing_tier_error(
|
||||
multiplier_source,
|
||||
processing_tier,
|
||||
"price_multiplier produced an invalid pricing catalog",
|
||||
)
|
||||
})?;
|
||||
Ok((pricing, source))
|
||||
}
|
||||
|
||||
@@ -587,7 +588,11 @@ fn invalid_processing_tier_error(
|
||||
))
|
||||
}
|
||||
|
||||
fn multiply_pricing_catalog(pricing: &Value, multiplier: f64) -> Option<Value> {
|
||||
fn multiply_pricing_catalog(
|
||||
pricing: &Value,
|
||||
multiplier: f64,
|
||||
processing_tier: &str,
|
||||
) -> Option<Value> {
|
||||
if !multiplier.is_finite() || multiplier < 0.0 {
|
||||
return None;
|
||||
}
|
||||
@@ -597,6 +602,12 @@ fn multiply_pricing_catalog(pricing: &Value, multiplier: f64) -> Option<Value> {
|
||||
// settlement snapshot recursively carry unrelated processing-tier configuration.
|
||||
object.remove("processing_tiers");
|
||||
|
||||
// Fast pricing is flat: its multiplier applies to Standard's first band at every context size.
|
||||
// Fast prices are fixed from the base Standard band; long-context premiums do not stack.
|
||||
if matches!(processing_tier, "priority" | "fast") {
|
||||
collapse_token_tiers_to_first(object)?;
|
||||
}
|
||||
|
||||
if let Some(tiers) = object.get_mut("tiers").and_then(Value::as_array_mut) {
|
||||
for tier in tiers.iter_mut().filter_map(Value::as_object_mut) {
|
||||
multiply_price_fields(
|
||||
@@ -646,6 +657,24 @@ fn multiply_pricing_catalog(pricing: &Value, multiplier: f64) -> Option<Value> {
|
||||
Some(multiplied)
|
||||
}
|
||||
|
||||
fn collapse_token_tiers_to_first(object: &mut serde_json::Map<String, Value>) -> Option<()> {
|
||||
let Some(tiers_value) = object.get_mut("tiers") else {
|
||||
return Some(());
|
||||
};
|
||||
if tiers_value.is_null() {
|
||||
return Some(());
|
||||
}
|
||||
let tiers = tiers_value.as_array_mut()?;
|
||||
let Some(mut first_tier) = tiers.first().cloned() else {
|
||||
return Some(());
|
||||
};
|
||||
first_tier
|
||||
.as_object_mut()?
|
||||
.insert("up_to".to_string(), Value::Null);
|
||||
*tiers = vec![first_tier];
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn multiply_price_fields(
|
||||
object: &mut serde_json::Map<String, Value>,
|
||||
fields: &[&str],
|
||||
@@ -989,7 +1018,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn processing_multiplier_materializes_known_prices_without_touching_bounds_or_extensions() {
|
||||
fn fast_processing_multiplier_materializes_known_prices_and_preserves_extensions() {
|
||||
let mut pricing = snapshot(
|
||||
Some(json!({
|
||||
"tiers": [{
|
||||
@@ -1038,7 +1067,10 @@ mod tests {
|
||||
Some(BillingPricingSource::ProviderOverride)
|
||||
);
|
||||
assert_eq!(resolution.processing_tier_price_multiplier, Some(2.5));
|
||||
assert_eq!(catalog.pointer("/tiers/0/up_to"), Some(&json!(272000)));
|
||||
assert_eq!(
|
||||
catalog.pointer("/tiers/0/up_to"),
|
||||
Some(&serde_json::Value::Null)
|
||||
);
|
||||
assert_eq!(
|
||||
catalog.pointer("/tiers/0/input_price_per_1m"),
|
||||
Some(&json!(5.0))
|
||||
@@ -1109,6 +1141,119 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fast_multipliers_flatten_multiband_standard_while_flex_preserves_all_bands() {
|
||||
let pricing = snapshot(
|
||||
Some(json!({
|
||||
"tiers": [
|
||||
{
|
||||
"up_to": 272000,
|
||||
"input_price_per_1m": 2.0,
|
||||
"output_price_per_1m": 4.0,
|
||||
"cache_creation_price_per_1m": 6.0,
|
||||
"cache_read_price_per_1m": 8.0,
|
||||
"future_tier_option": "first"
|
||||
},
|
||||
{
|
||||
"up_to": null,
|
||||
"input_price_per_1m": 5.0,
|
||||
"output_price_per_1m": 7.0,
|
||||
"cache_creation_price_per_1m": 9.0,
|
||||
"cache_read_price_per_1m": 11.0,
|
||||
"future_tier_option": "second"
|
||||
}
|
||||
],
|
||||
"image_output_price_default": 10.0,
|
||||
"future_catalog_option": 31,
|
||||
"processing_tiers": {
|
||||
"priority": {"price_multiplier": 2.0},
|
||||
"fast": {"price_multiplier": 3.0},
|
||||
"flex": {"price_multiplier": 4.0}
|
||||
}
|
||||
})),
|
||||
None,
|
||||
);
|
||||
|
||||
for (tier, expected_tiers, expected_image_price, expected_multiplier) in [
|
||||
(
|
||||
"priority",
|
||||
json!([{
|
||||
"up_to": null,
|
||||
"input_price_per_1m": 4.0,
|
||||
"output_price_per_1m": 8.0,
|
||||
"cache_creation_price_per_1m": 12.0,
|
||||
"cache_read_price_per_1m": 16.0,
|
||||
"future_tier_option": "first"
|
||||
}]),
|
||||
json!(20.0),
|
||||
2.0,
|
||||
),
|
||||
(
|
||||
"fast",
|
||||
json!([{
|
||||
"up_to": null,
|
||||
"input_price_per_1m": 6.0,
|
||||
"output_price_per_1m": 12.0,
|
||||
"cache_creation_price_per_1m": 18.0,
|
||||
"cache_read_price_per_1m": 24.0,
|
||||
"future_tier_option": "first"
|
||||
}]),
|
||||
json!(30.0),
|
||||
3.0,
|
||||
),
|
||||
] {
|
||||
let resolution = pricing.resolve_pricing(Some(tier), None);
|
||||
let catalog = resolution
|
||||
.tiered_pricing
|
||||
.as_ref()
|
||||
.expect("fast multiplier should materialize a pricing catalog");
|
||||
|
||||
assert_eq!(catalog["tiers"], expected_tiers, "{tier}");
|
||||
assert_eq!(
|
||||
catalog["image_output_price_default"], expected_image_price,
|
||||
"{tier}"
|
||||
);
|
||||
assert_eq!(catalog["future_catalog_option"], json!(31), "{tier}");
|
||||
assert!(catalog.get("processing_tiers").is_none(), "{tier}");
|
||||
assert_eq!(
|
||||
resolution.processing_tier_price_multiplier,
|
||||
Some(expected_multiplier),
|
||||
"{tier}"
|
||||
);
|
||||
}
|
||||
|
||||
let flex = pricing.resolve_pricing(Some("flex"), None);
|
||||
let flex_catalog = flex
|
||||
.tiered_pricing
|
||||
.as_ref()
|
||||
.expect("flex multiplier should materialize a pricing catalog");
|
||||
assert_eq!(
|
||||
flex_catalog["tiers"],
|
||||
json!([
|
||||
{
|
||||
"up_to": 272000,
|
||||
"input_price_per_1m": 8.0,
|
||||
"output_price_per_1m": 16.0,
|
||||
"cache_creation_price_per_1m": 24.0,
|
||||
"cache_read_price_per_1m": 32.0,
|
||||
"future_tier_option": "first"
|
||||
},
|
||||
{
|
||||
"up_to": null,
|
||||
"input_price_per_1m": 20.0,
|
||||
"output_price_per_1m": 28.0,
|
||||
"cache_creation_price_per_1m": 36.0,
|
||||
"cache_read_price_per_1m": 44.0,
|
||||
"future_tier_option": "second"
|
||||
}
|
||||
])
|
||||
);
|
||||
assert_eq!(flex_catalog["image_output_price_default"], json!(40.0));
|
||||
assert_eq!(flex_catalog["future_catalog_option"], json!(31));
|
||||
assert!(flex_catalog.get("processing_tiers").is_none());
|
||||
assert_eq!(flex.processing_tier_price_multiplier, Some(4.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn processing_overlay_precedence_is_provider_explicit_multiplier_then_global() {
|
||||
let provider_explicit = snapshot(
|
||||
|
||||
@@ -1456,6 +1456,55 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fast_multiplier_uses_one_fixed_first_band_above_context_threshold() {
|
||||
let pricing = BillingModelPricingSnapshot {
|
||||
default_tiered_pricing: Some(json!({
|
||||
"tiers": [
|
||||
{
|
||||
"up_to": 271999,
|
||||
"input_price_per_1m": 5.0,
|
||||
"output_price_per_1m": 30.0
|
||||
},
|
||||
{
|
||||
"up_to": null,
|
||||
"input_price_per_1m": 10.0,
|
||||
"output_price_per_1m": 45.0
|
||||
}
|
||||
],
|
||||
"processing_tiers": {
|
||||
"priority": {"price_multiplier": 2.0}
|
||||
}
|
||||
})),
|
||||
model_tiered_pricing: None,
|
||||
..pricing()
|
||||
};
|
||||
|
||||
for input_tokens in [271_999, 272_000, 300_000] {
|
||||
let result = BillingService::new()
|
||||
.calculate(
|
||||
&pricing,
|
||||
&processing_usage(Some("priority"), Some("priority"), input_tokens),
|
||||
)
|
||||
.expect("Fast pricing should settle at every context size");
|
||||
assert_eq!(
|
||||
result.cost_result.status,
|
||||
BillingSnapshotStatus::Complete,
|
||||
"context: {input_tokens}"
|
||||
);
|
||||
assert_eq!(
|
||||
result.cost_result.snapshot.resolved_variables["input_price_per_1m"],
|
||||
json!(10.0),
|
||||
"Fast must use the first Standard band at context {input_tokens}"
|
||||
);
|
||||
assert_eq!(
|
||||
result.cost_result.snapshot.resolved_variables["output_price_per_1m"],
|
||||
json!(60.0),
|
||||
"Fast output must remain fixed at context {input_tokens}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn processing_catalog_boundaries_match_context_and_priority_contracts() {
|
||||
let cases = [
|
||||
|
||||
Reference in New Issue
Block a user