fix(proxy): apply cost multiplier to total cost only

- Move multiplier calculation from per-item to total cost
- Add resolve_pricing_config for provider-level override
- Include request_model and cost_multiplier in usage logs
- Return new fields in get_request_logs API
This commit is contained in:
YoVinchen
2026-01-26 01:40:18 +08:00
parent bdef49fe0f
commit 63b874aff1
5 changed files with 395 additions and 103 deletions
+112 -8
View File
@@ -6,7 +6,7 @@ use crate::database::Database;
use crate::error::AppError;
use crate::services::usage_stats::find_model_pricing_row;
use rust_decimal::Decimal;
use std::time::SystemTime;
use std::{str::FromStr, time::SystemTime};
/// 请求日志
#[derive(Debug, Clone)]
@@ -15,6 +15,7 @@ pub struct RequestLog {
pub provider_id: String,
pub app_type: String,
pub model: String,
pub request_model: String,
pub usage: TokenUsage,
pub cost: Option<CostBreakdown>,
pub latency_ms: u64,
@@ -73,17 +74,18 @@ impl<'a> UsageLogger<'a> {
conn.execute(
"INSERT INTO proxy_request_logs (
request_id, provider_id, app_type, model,
request_id, provider_id, app_type, model, request_model,
input_tokens, output_tokens, cache_read_tokens, cache_creation_tokens,
input_cost_usd, output_cost_usd, cache_read_cost_usd, cache_creation_cost_usd, total_cost_usd,
latency_ms, first_token_ms, status_code, error_message, session_id,
provider_type, is_streaming, cost_multiplier, created_at
) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17, ?18, ?19, ?20, ?21, ?22)",
) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17, ?18, ?19, ?20, ?21, ?22, ?23)",
rusqlite::params![
log.request_id,
log.provider_id,
log.app_type,
log.model,
log.request_model,
log.usage.input_tokens,
log.usage.output_tokens,
log.usage.cache_read_tokens,
@@ -123,11 +125,13 @@ impl<'a> UsageLogger<'a> {
error_message: String,
latency_ms: u64,
) -> Result<(), AppError> {
let request_model = model.clone();
let log = RequestLog {
request_id,
provider_id,
app_type,
model,
request_model,
usage: TokenUsage::default(),
cost: None,
latency_ms,
@@ -160,11 +164,13 @@ impl<'a> UsageLogger<'a> {
session_id: Option<String>,
provider_type: Option<String>,
) -> Result<(), AppError> {
let request_model = model.clone();
let log = RequestLog {
request_id,
provider_id,
app_type,
model,
request_model,
usage: TokenUsage::default(),
cost: None,
latency_ms,
@@ -194,6 +200,96 @@ impl<'a> UsageLogger<'a> {
}
}
/// 获取有效的倍率与计费模式来源(供应商优先,未配置则回退全局默认)
pub async fn resolve_pricing_config(
&self,
provider_id: &str,
app_type: &str,
) -> (Decimal, String) {
let default_multiplier_raw = match self.db.get_default_cost_multiplier(app_type).await {
Ok(value) => value,
Err(e) => {
log::warn!(
"[USG-003] 获取默认倍率失败 (app_type={app_type}): {e}"
);
"1".to_string()
}
};
let default_multiplier = match Decimal::from_str(&default_multiplier_raw) {
Ok(value) => value,
Err(e) => {
log::warn!(
"[USG-003] 默认倍率解析失败 (app_type={app_type}): {default_multiplier_raw} - {e}"
);
Decimal::from(1)
}
};
let default_pricing_source_raw = match self.db.get_pricing_model_source(app_type).await {
Ok(value) => value,
Err(e) => {
log::warn!(
"[USG-003] 获取默认计费模式失败 (app_type={app_type}): {e}"
);
"response".to_string()
}
};
let default_pricing_source = if matches!(
default_pricing_source_raw.as_str(),
"response" | "request"
) {
default_pricing_source_raw
} else {
log::warn!(
"[USG-003] 默认计费模式无效 (app_type={app_type}): {default_pricing_source_raw}"
);
"response".to_string()
};
let provider = self
.db
.get_provider_by_id(provider_id, app_type)
.ok()
.flatten();
let (provider_multiplier, provider_pricing_source) = provider
.as_ref()
.and_then(|p| p.meta.as_ref())
.map(|meta| {
(
meta.cost_multiplier.as_deref(),
meta.pricing_model_source.as_deref(),
)
})
.unwrap_or((None, None));
let cost_multiplier = match provider_multiplier {
Some(value) => match Decimal::from_str(value) {
Ok(parsed) => parsed,
Err(e) => {
log::warn!(
"[USG-003] 供应商倍率解析失败 (provider_id={provider_id}): {value} - {e}"
);
default_multiplier
}
},
None => default_multiplier,
};
let pricing_model_source = match provider_pricing_source {
Some(value) if matches!(value, "response" | "request") => value.to_string(),
Some(value) => {
log::warn!(
"[USG-003] 供应商计费模式无效 (provider_id={provider_id}): {value}"
);
default_pricing_source.clone()
}
None => default_pricing_source.clone(),
};
(cost_multiplier, pricing_model_source)
}
/// 计算并记录请求
#[allow(clippy::too_many_arguments)]
pub fn log_with_calculation(
@@ -202,6 +298,8 @@ impl<'a> UsageLogger<'a> {
provider_id: String,
app_type: String,
model: String,
request_model: String,
pricing_model: String,
usage: TokenUsage,
cost_multiplier: Decimal,
latency_ms: u64,
@@ -211,10 +309,12 @@ impl<'a> UsageLogger<'a> {
provider_type: Option<String>,
is_streaming: bool,
) -> Result<(), AppError> {
let pricing = self.get_model_pricing(&model)?;
let pricing = self.get_model_pricing(&pricing_model)?;
if pricing.is_none() {
log::warn!("[USG-002] 模型定价未找到,成本将记录为 0");
log::warn!(
"[USG-002] 模型定价未找到,成本将记录为 0: {pricing_model}"
);
}
let cost = CostCalculator::try_calculate(&usage, pricing.as_ref(), cost_multiplier);
@@ -224,6 +324,7 @@ impl<'a> UsageLogger<'a> {
provider_id,
app_type,
model,
request_model,
usage,
cost,
latency_ms,
@@ -274,6 +375,8 @@ mod tests {
"provider-1".to_string(),
"claude".to_string(),
"test-model".to_string(),
"req-model".to_string(),
"test-model".to_string(),
usage,
Decimal::from(1),
100,
@@ -286,14 +389,15 @@ mod tests {
// 验证记录已插入
let conn = crate::database::lock_conn!(db.conn);
let count: i64 = conn
let (count, request_model): (i64, String) = conn
.query_row(
"SELECT COUNT(*) FROM proxy_request_logs WHERE request_id = 'req-123'",
"SELECT COUNT(*), request_model FROM proxy_request_logs WHERE request_id = 'req-123'",
[],
|row| row.get(0),
|row| Ok((row.get(0)?, row.get(1)?)),
)
.unwrap();
assert_eq!(count, 1);
assert_eq!(request_model, "req-model");
Ok(())
}