difflore_core/observability/
cost.rs1#[derive(Debug, Clone, Copy, PartialEq)]
15pub struct ModelPricing {
16 pub input_usd_per_1k: f64,
17 pub output_usd_per_1k: f64,
18}
19
20pub fn pricing_for(model: &str) -> Option<ModelPricing> {
23 match model {
24 "claude-sonnet-4-20250514" | "claude-sonnet-4-6" => Some(ModelPricing {
28 input_usd_per_1k: 0.003,
29 output_usd_per_1k: 0.015,
30 }),
31 "claude-haiku-4-5-20251001" | "claude-haiku-4-5" => Some(ModelPricing {
32 input_usd_per_1k: 0.0008,
33 output_usd_per_1k: 0.004,
34 }),
35 "claude-opus-4-6" | "claude-opus-4-7" => Some(ModelPricing {
36 input_usd_per_1k: 0.015,
37 output_usd_per_1k: 0.075,
38 }),
39 "gpt-4o" => Some(ModelPricing {
41 input_usd_per_1k: 0.005,
42 output_usd_per_1k: 0.015,
43 }),
44 "gpt-4o-mini" => Some(ModelPricing {
45 input_usd_per_1k: 0.00015,
46 output_usd_per_1k: 0.0006,
47 }),
48 _ => None,
49 }
50}
51
52pub fn estimate_cost_usd(model: &str, input_tokens: u32, output_tokens: u32) -> Option<f64> {
58 let p = pricing_for(model)?;
59 let cost = (f64::from(input_tokens) / 1000.0).mul_add(
60 p.input_usd_per_1k,
61 (f64::from(output_tokens) / 1000.0) * p.output_usd_per_1k,
62 );
63 Some(cost)
64}
65
66#[cfg(test)]
67#[allow(
68 clippy::expect_used,
69 clippy::panic,
70 clippy::unwrap_used,
71 clippy::float_cmp
72)] mod tests {
74 use super::*;
75
76 #[test]
77 fn pricing_for_known_models_table() {
78 let cases: &[(&str, f64, f64)] = &[
81 ("claude-sonnet-4-20250514", 0.003, 0.015),
82 ("claude-sonnet-4-6", 0.003, 0.015),
83 ("claude-haiku-4-5-20251001", 0.0008, 0.004),
84 ("claude-opus-4-6", 0.015, 0.075),
85 ("gpt-4o", 0.005, 0.015),
86 ("gpt-4o-mini", 0.00015, 0.0006),
87 ];
88 for (model, input, output) in cases {
89 let p = pricing_for(model).unwrap_or_else(|| panic!("missing: {model}"));
90 assert_eq!(p.input_usd_per_1k, *input, "model: {model}");
91 assert_eq!(p.output_usd_per_1k, *output, "model: {model}");
92 }
93 }
94
95 #[test]
96 fn pricing_for_unknown_or_miscased_model_returns_none() {
97 for m in ["", "gpt-5-secret", "CLAUDE-SONNET-4-20250514", "GPT-4o"] {
100 assert!(pricing_for(m).is_none(), "unexpectedly priced: {m}");
101 }
102 }
103
104 #[test]
105 fn estimate_cost_usd_linear_and_edge_cases() {
106 let cost = estimate_cost_usd("claude-sonnet-4-20250514", 1000, 500).unwrap();
108 assert!((cost - 0.0105).abs() < 1e-9, "sonnet got {cost}");
109
110 let cost = estimate_cost_usd("gpt-4o-mini", 10_000, 1_000).unwrap();
112 assert!((cost - 0.0021).abs() < 1e-9, "mini got {cost}");
113
114 assert_eq!(
116 estimate_cost_usd("claude-sonnet-4-20250514", 0, 0).unwrap(),
117 0.0
118 );
119 assert!(estimate_cost_usd("unknown-model", 1000, 500).is_none());
120
121 let cost = estimate_cost_usd("claude-sonnet-4-20250514", 2_000, 500).unwrap();
123 let input_cost = (2_000.0 / 1000.0) * 0.003;
124 let output_cost = (500.0 / 1000.0) * 0.015;
125 assert!((cost - (input_cost + output_cost)).abs() < 1e-9);
126 assert!(output_cost > input_cost);
127 }
128}