use time::OffsetDateTime;
use super::super::api::PromptCacheTtl;
use crate::llm_config::RateCard;
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct PricingDetail {
pub input_per_1k: f64,
pub output_per_1k: f64,
pub cache_read_per_1k: Option<f64>,
pub cache_write_per_1k: Option<f64>,
pub cache_write_1h_per_1k: Option<f64>,
pub source: PricingSource,
pub rate_card: RateCard,
pub input_band_minimum: Option<u64>,
pub hosted_tool_fees: std::collections::BTreeMap<String, crate::llm_config::HostedToolFee>,
pub modality_rates: Option<crate::llm_config::ModalityRates>,
pub platform_fee_percent: f64,
}
impl PricingDetail {
fn for_provider(mut self, provider: &str) -> Self {
self.platform_fee_percent = crate::llm_config::provider_config(provider)
.and_then(|provider| provider.platform_fee_percent)
.unwrap_or(0.0);
self
}
fn from_pricing(
pricing: &crate::llm_config::ModelPricing,
rate_card: RateCard,
input_band_minimum: Option<u64>,
source: PricingSource,
) -> Self {
Self {
input_per_1k: pricing.input_per_mtok / 1000.0,
output_per_1k: pricing.output_per_mtok / 1000.0,
cache_read_per_1k: pricing.cache_read_per_mtok.map(|rate| rate / 1000.0),
cache_write_per_1k: pricing.cache_write_per_mtok.map(|rate| rate / 1000.0),
cache_write_1h_per_1k: pricing.cache_write_1h_per_mtok.map(|rate| rate / 1000.0),
source,
rate_card,
input_band_minimum,
hosted_tool_fees: pricing.hosted_tool_fees.clone(),
modality_rates: pricing.modality_rates.clone(),
platform_fee_percent: 0.0,
}
}
pub(crate) fn cache_write_priced(&self, ttl: Option<PromptCacheTtl>) -> bool {
self.cache_write_rate(ttl).1
}
pub(super) fn cache_write_rate(&self, ttl: Option<PromptCacheTtl>) -> (f64, bool) {
let short = self.cache_write_per_1k.unwrap_or(self.input_per_1k);
match ttl {
Some(PromptCacheTtl::OneHour) => match self.cache_write_1h_per_1k {
Some(rate) => (rate, true),
None => (short, false),
},
_ => (short, true),
}
}
}
pub(crate) fn settlement_now() -> OffsetDateTime {
crate::llm_config::pricing_clock_now()
}
pub(crate) fn instant_from_wall_ms(value_ms: i64) -> OffsetDateTime {
OffsetDateTime::from_unix_timestamp_nanos(i128::from(value_ms) * 1_000_000)
.unwrap_or(OffsetDateTime::UNIX_EPOCH)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum PricingSource {
CatalogModel,
CatalogServingTier,
ProviderEconomics,
}
impl PricingSource {
pub(crate) fn as_str(self) -> &'static str {
match self {
PricingSource::CatalogModel => "catalog_model",
PricingSource::CatalogServingTier => "catalog_serving_tier",
PricingSource::ProviderEconomics => "provider_economics",
}
}
}
fn model_pricing_for_observed_route(
provider: &str,
model: &str,
at: OffsetDateTime,
) -> Option<crate::llm_config::ResolvedPricing> {
crate::llm_config::model_pricing_per_mtok_for_route(provider, model, at).or_else(|| {
(provider == "mock")
.then(|| crate::llm_config::model_pricing_per_mtok(model, at))
.flatten()
})
}
pub(crate) fn pricing_detail_for(
provider: &str,
model: &str,
at: OffsetDateTime,
) -> Option<PricingDetail> {
if let Some(resolved) = model_pricing_for_observed_route(provider, model, at) {
return Some(
PricingDetail::from_pricing(
&resolved.pricing,
resolved.rate_card,
None,
PricingSource::CatalogModel,
)
.for_provider(provider),
);
}
let (input, output, _) = crate::llm_config::provider_economics(provider);
match (input, output) {
(Some(input_per_1k), Some(output_per_1k)) => Some(
PricingDetail {
input_per_1k,
output_per_1k,
cache_read_per_1k: None,
cache_write_per_1k: None,
cache_write_1h_per_1k: None,
source: PricingSource::ProviderEconomics,
rate_card: RateCard::Base,
input_band_minimum: None,
hosted_tool_fees: Default::default(),
modality_rates: None,
platform_fee_percent: 0.0,
}
.for_provider(provider),
),
_ => None,
}
}
pub(super) fn pricing_detail_for_usage(
provider: &str,
model: &str,
input_tokens: i64,
at: OffsetDateTime,
) -> Option<PricingDetail> {
if let Some(resolved) = model_pricing_for_observed_route(provider, model, at) {
let (band, pricing) = match resolved.pricing.band_for_input_tokens(input_tokens) {
Some((minimum, banded)) => (Some(minimum), banded),
None => (None, resolved.pricing),
};
return Some(
PricingDetail::from_pricing(
&pricing,
resolved.rate_card,
band,
PricingSource::CatalogModel,
)
.for_provider(provider),
);
}
pricing_detail_for(provider, model, at)
}
pub(crate) fn pricing_per_1k_for(provider: &str, model: &str) -> Option<(f64, f64)> {
pricing_detail_for(provider, model, settlement_now()).map(|p| (p.input_per_1k, p.output_per_1k))
}
pub(crate) fn pricing_detail_for_tier(
provider: &str,
model: &str,
served_fast: bool,
input_tokens: i64,
at: OffsetDateTime,
) -> Option<PricingDetail> {
if served_fast {
if let Some(mut resolved) = crate::llm_config::model_serving_tier_pricing_per_mtok_for_route(
provider,
model,
crate::llm::serving_tiers::FAST_TIER_ID,
at,
) {
if let Some(model_pricing) =
crate::llm_config::model_pricing_per_mtok_for_route(provider, model, at)
{
resolved.pricing.input_token_bands = model_pricing.pricing.input_token_bands;
}
let (band, pricing) = match resolved.pricing.band_for_input_tokens(input_tokens) {
Some((minimum, banded)) => (Some(minimum), banded),
None => (None, resolved.pricing),
};
return Some(
PricingDetail::from_pricing(
&pricing,
resolved.rate_card,
band,
PricingSource::CatalogServingTier,
)
.for_provider(provider),
);
}
}
pricing_detail_for_usage(provider, model, input_tokens, at)
}