fix(usage): correct cache cost semantics and silence pricing warn storm

- Split CostCalculator into per-app cache semantics: Anthropic's
  input_tokens is already fresh input, while Codex/Gemini include
  cached tokens in their prompt count. The old shared formula
  double-subtracted cache_read for Claude, under-billing input cost.
- Backfill now reads cost_multiplier from the per-log snapshot column
  instead of re-querying providers.meta, so historical rows are no
  longer rewritten with the current multiplier.
- Move the "pricing not found" warn out of find_model_pricing_row;
  emit it only when a brand new log is written, and skip placeholder
  models (unknown / empty / null / none) entirely.
- Broaden model id normalization: strip namespace prefixes
  (anthropic./openai./global./bedrock.), bedrock-style -vN suffixes,
  reasoning effort suffixes (-low/-medium/-high/-xhigh/-minimal),
  Claude Desktop's claude-<non-anthropic> wrapper, dot-to-dash for
  Claude, and try a LIKE prefix match for Claude short route ids
  (e.g. claude-haiku-4-5 -> claude-haiku-4-5-20251001).
- Fall back to request_model when the stored model is missing, so
  early Codex session rows with model=unknown can still be priced.
This commit is contained in:
Jason
2026-05-13 17:51:35 +08:00
parent 4b57f7e113
commit aa5e58d060
5 changed files with 505 additions and 102 deletions
+13 -3
View File
@@ -4,7 +4,7 @@ use super::calculator::{CostBreakdown, CostCalculator, ModelPricing};
use super::parser::TokenUsage;
use crate::database::Database;
use crate::error::AppError;
use crate::services::usage_stats::find_model_pricing_row;
use crate::services::usage_stats::{find_model_pricing_row, is_placeholder_pricing_model};
use rust_decimal::Decimal;
use std::{str::FromStr, time::SystemTime};
@@ -303,11 +303,21 @@ impl<'a> UsageLogger<'a> {
) -> Result<(), AppError> {
let pricing = self.get_model_pricing(&pricing_model)?;
if pricing.is_none() {
let has_usage = usage.input_tokens > 0
|| usage.output_tokens > 0
|| usage.cache_read_tokens > 0
|| usage.cache_creation_tokens > 0;
if pricing.is_none() && has_usage && !is_placeholder_pricing_model(&pricing_model) {
log::warn!("[USG-002] 模型定价未找到,成本将记录为 0: {pricing_model}");
}
let cost = CostCalculator::try_calculate(&usage, pricing.as_ref(), cost_multiplier);
let cost = CostCalculator::try_calculate_for_app(
&app_type,
&usage,
pricing.as_ref(),
cost_multiplier,
);
let log = RequestLog {
request_id,