mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-05 00:47:48 +08:00
fix(billing): preserve effective cache and tier facts
This commit is contained in:
@@ -2,17 +2,18 @@ mod types;
|
||||
|
||||
pub use types::{
|
||||
extract_provider_actual_service_tier_from_response,
|
||||
extract_provider_reasoning_effort_from_body, extract_provider_service_tier_from_body,
|
||||
normalize_provider_service_tier, parse_usage_body_ref, usage_body_ref,
|
||||
usage_request_metadata_client_family, ApiKeyLastUsedDelta, ManagementTokenCounterDelta,
|
||||
PendingUsageCleanupSummary, ProviderApiKeyWindowUsageRequest, ProxyNodeCounterDelta,
|
||||
StoredProviderApiKeyUsageSummary, StoredProviderApiKeyWindowUsageSummary,
|
||||
StoredProviderUsageSummary, StoredProviderUsageWindow, StoredRequestUsageAudit,
|
||||
StoredUsageAuditAggregation, StoredUsageAuditSummary, StoredUsageBreakdownSummaryRow,
|
||||
StoredUsageCacheAffinityHitSummary, StoredUsageCacheAffinityIntervalRow,
|
||||
StoredUsageCacheHitSummary, StoredUsageCostSavingsSummary, StoredUsageDailySummary,
|
||||
StoredUsageDashboardDailyBreakdownRow, StoredUsageDashboardProviderCount,
|
||||
StoredUsageDashboardSummary, StoredUsageErrorDistributionRow, StoredUsageLeaderboardSummary,
|
||||
extract_provider_cache_ttl_minutes_from_metadata, extract_provider_reasoning_effort_from_body,
|
||||
extract_provider_service_tier_from_body, normalize_provider_service_tier, parse_usage_body_ref,
|
||||
resolve_provider_cache_ttl_minutes, usage_body_ref, usage_request_metadata_client_family,
|
||||
ApiKeyLastUsedDelta, ManagementTokenCounterDelta, PendingUsageCleanupSummary,
|
||||
ProviderApiKeyWindowUsageRequest, ProxyNodeCounterDelta, StoredProviderApiKeyUsageSummary,
|
||||
StoredProviderApiKeyWindowUsageSummary, StoredProviderUsageSummary, StoredProviderUsageWindow,
|
||||
StoredRequestUsageAudit, StoredUsageAuditAggregation, StoredUsageAuditSummary,
|
||||
StoredUsageBreakdownSummaryRow, StoredUsageCacheAffinityHitSummary,
|
||||
StoredUsageCacheAffinityIntervalRow, StoredUsageCacheHitSummary, StoredUsageCostSavingsSummary,
|
||||
StoredUsageDailySummary, StoredUsageDashboardDailyBreakdownRow,
|
||||
StoredUsageDashboardProviderCount, StoredUsageDashboardSummary,
|
||||
StoredUsageErrorDistributionRow, StoredUsageLeaderboardSummary,
|
||||
StoredUsagePerformancePercentilesRow, StoredUsageProviderPerformance,
|
||||
StoredUsageProviderPerformanceProviderRow, StoredUsageProviderPerformanceSummary,
|
||||
StoredUsageProviderPerformanceTimelineRow, StoredUsageSettledCostSummary,
|
||||
@@ -30,5 +31,6 @@ pub use types::{
|
||||
UsagePerformancePercentilesQuery, UsageProviderPerformanceQuery, UsageReadRepository,
|
||||
UsageRepository, UsageSettledCostSummaryQuery, UsageTimeSeriesGranularity,
|
||||
UsageTimeSeriesQuery, UsageWriteRepository, PROVIDER_ACTUAL_SERVICE_TIER_METADATA_KEY,
|
||||
PROVIDER_REASONING_EFFORT_METADATA_KEY, PROVIDER_SERVICE_TIER_METADATA_KEY,
|
||||
PROVIDER_CACHE_TTL_MINUTES_METADATA_KEY, PROVIDER_REASONING_EFFORT_METADATA_KEY,
|
||||
PROVIDER_SERVICE_TIER_METADATA_KEY,
|
||||
};
|
||||
|
||||
@@ -5,6 +5,7 @@ use serde_json::Value;
|
||||
pub const PROVIDER_REASONING_EFFORT_METADATA_KEY: &str = "provider_reasoning_effort";
|
||||
pub const PROVIDER_SERVICE_TIER_METADATA_KEY: &str = "provider_service_tier";
|
||||
pub const PROVIDER_ACTUAL_SERVICE_TIER_METADATA_KEY: &str = "provider_actual_service_tier";
|
||||
pub const PROVIDER_CACHE_TTL_MINUTES_METADATA_KEY: &str = "provider_cache_ttl_minutes";
|
||||
|
||||
pub fn extract_provider_reasoning_effort_from_body(value: Option<&Value>) -> Option<String> {
|
||||
let object = value.and_then(Value::as_object)?;
|
||||
@@ -71,6 +72,48 @@ pub fn normalize_provider_service_tier(value: &str) -> Option<String> {
|
||||
Some(normalized)
|
||||
}
|
||||
|
||||
pub fn resolve_provider_cache_ttl_minutes(
|
||||
provider_api_format: Option<&str>,
|
||||
provider_model: Option<&str>,
|
||||
source_model: Option<&str>,
|
||||
provider_request_body: Option<&Value>,
|
||||
) -> Option<i64> {
|
||||
let provider_api_format = provider_api_format?.trim();
|
||||
let provider_request_body = provider_request_body?;
|
||||
let provider_model = provider_request_body
|
||||
.get("model")
|
||||
.and_then(Value::as_str)
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.or_else(|| {
|
||||
provider_model
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
})?;
|
||||
let source_model = source_model
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.unwrap_or(provider_model);
|
||||
aether_ai_formats::resolve_openai_prompt_cache_ttl_minutes(
|
||||
provider_api_format,
|
||||
provider_model,
|
||||
source_model,
|
||||
provider_request_body,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn extract_provider_cache_ttl_minutes_from_metadata(value: Option<&Value>) -> Option<i64> {
|
||||
value
|
||||
.and_then(Value::as_object)
|
||||
.and_then(|metadata| metadata.get(PROVIDER_CACHE_TTL_MINUTES_METADATA_KEY))
|
||||
.and_then(|value| {
|
||||
value
|
||||
.as_i64()
|
||||
.or_else(|| value.as_u64().and_then(|value| i64::try_from(value).ok()))
|
||||
})
|
||||
.filter(|value| *value > 0)
|
||||
}
|
||||
|
||||
/// Joined usage read model assembled from the accounting row plus the newer audit/snapshot
|
||||
/// satellite tables.
|
||||
///
|
||||
@@ -482,6 +525,20 @@ impl StoredRequestUsageAudit {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn provider_cache_ttl_minutes(&self) -> Option<i64> {
|
||||
resolve_provider_cache_ttl_minutes(
|
||||
self.endpoint_api_format
|
||||
.as_deref()
|
||||
.or(self.api_format.as_deref()),
|
||||
self.target_model.as_deref().or(Some(self.model.as_str())),
|
||||
Some(self.model.as_str()),
|
||||
self.provider_request_body.as_ref(),
|
||||
)
|
||||
.or_else(|| {
|
||||
extract_provider_cache_ttl_minutes_from_metadata(self.request_metadata.as_ref())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn body_ref(&self, field: UsageBodyField) -> Option<&str> {
|
||||
match field {
|
||||
UsageBodyField::RequestBody => self.request_body_ref.as_deref(),
|
||||
@@ -2097,8 +2154,9 @@ fn parse_timestamp(value: i64, field_name: &str) -> Result<u64, crate::DataLayer
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{
|
||||
extract_provider_actual_service_tier_from_response, StoredRequestUsageAudit,
|
||||
UpsertUsageRecord, UsageBodyCaptureState, UsageBodyCaptureStorage, UsageBodyField,
|
||||
extract_provider_actual_service_tier_from_response, resolve_provider_cache_ttl_minutes,
|
||||
StoredRequestUsageAudit, UpsertUsageRecord, UsageBodyCaptureState, UsageBodyCaptureStorage,
|
||||
UsageBodyField,
|
||||
};
|
||||
use serde_json::{json, Value};
|
||||
|
||||
@@ -2514,6 +2572,43 @@ mod tests {
|
||||
assert_eq!(usage.provider_service_tier(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn provider_cache_ttl_uses_final_openai_contract_then_preserved_metadata() {
|
||||
let mut usage = sample_usage();
|
||||
usage.model = "gpt-5.6-sol".to_string();
|
||||
usage.target_model = Some("gpt-5.6-sol".to_string());
|
||||
usage.provider_request_body = Some(json!({"model": "gpt-5.6-sol"}));
|
||||
usage.request_metadata = Some(json!({"provider_cache_ttl_minutes": 60}));
|
||||
|
||||
assert_eq!(usage.provider_cache_ttl_minutes(), Some(30));
|
||||
|
||||
usage.provider_request_body = None;
|
||||
usage.request_metadata = Some(json!({"provider_cache_ttl_minutes": 30}));
|
||||
assert_eq!(usage.provider_cache_ttl_minutes(), Some(30));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn provider_cache_ttl_prefers_final_body_model() {
|
||||
assert_eq!(
|
||||
resolve_provider_cache_ttl_minutes(
|
||||
Some("openai:responses"),
|
||||
Some("gpt-5.5"),
|
||||
Some("client-alias"),
|
||||
Some(&json!({"model": "gpt-5.6-sol"})),
|
||||
),
|
||||
Some(30)
|
||||
);
|
||||
assert_eq!(
|
||||
resolve_provider_cache_ttl_minutes(
|
||||
Some("openai:responses"),
|
||||
Some("gpt-5.6-sol"),
|
||||
Some("client-alias"),
|
||||
Some(&json!({"model": "gpt-5.5"})),
|
||||
),
|
||||
None
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn actual_service_tier_is_independent_from_requested_tier() {
|
||||
let mut usage = sample_usage();
|
||||
|
||||
Reference in New Issue
Block a user