#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ModelPrice {
pub input_usd_per_1m: f64,
pub output_usd_per_1m: f64,
}
const PRICE_TABLE: &[(&str, ModelPrice)] = &[
("claude-3-5-sonnet", ModelPrice { input_usd_per_1m: 3.0, output_usd_per_1m: 15.0 }),
("claude-3-5-haiku", ModelPrice { input_usd_per_1m: 0.80, output_usd_per_1m: 4.0 }),
("claude-3-opus", ModelPrice { input_usd_per_1m: 15.0, output_usd_per_1m: 75.0 }),
("claude-3-sonnet", ModelPrice { input_usd_per_1m: 3.0, output_usd_per_1m: 15.0 }),
("claude-3-haiku", ModelPrice { input_usd_per_1m: 0.25, output_usd_per_1m: 1.25 }),
("claude", ModelPrice { input_usd_per_1m: 3.0, output_usd_per_1m: 15.0 }),
("gpt-4o-mini", ModelPrice { input_usd_per_1m: 0.15, output_usd_per_1m: 0.60 }),
("gpt-4o", ModelPrice { input_usd_per_1m: 2.50, output_usd_per_1m: 10.0 }),
("gpt-4-turbo", ModelPrice { input_usd_per_1m: 10.0, output_usd_per_1m: 30.0 }),
("gpt-4", ModelPrice { input_usd_per_1m: 30.0, output_usd_per_1m: 60.0 }),
("gpt-3.5-turbo", ModelPrice { input_usd_per_1m: 0.50, output_usd_per_1m: 1.50 }),
("deepseek-reasoner", ModelPrice { input_usd_per_1m: 0.55, output_usd_per_1m: 2.19 }),
("deepseek-chat", ModelPrice { input_usd_per_1m: 0.27, output_usd_per_1m: 1.10 }),
("qwen", ModelPrice { input_usd_per_1m: 0.50, output_usd_per_1m: 2.0 }),
];
pub fn price_for(model: &str) -> Option<ModelPrice> {
let lower = model.to_ascii_lowercase();
PRICE_TABLE
.iter()
.find(|(pattern, _)| lower.contains(pattern))
.map(|(_, price)| *price)
}
pub fn estimate_cost_usd(prompt_tokens: usize, completion_tokens: usize, model: &str) -> Option<f64> {
let price = price_for(model)?;
Some(
prompt_tokens as f64 / 1e6 * price.input_usd_per_1m
+ completion_tokens as f64 / 1e6 * price.output_usd_per_1m,
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn specific_beats_generic_claude() {
let opus = price_for("claude-3-opus-20240229").unwrap();
assert_eq!(opus.input_usd_per_1m, 15.0);
let sonnet = price_for("claude-3-sonnet-20240229").unwrap();
assert_eq!(sonnet.input_usd_per_1m, 3.0);
}
#[test]
fn gpt_4o_mini_beats_gpt_4o() {
let mini = price_for("gpt-4o-mini").unwrap();
assert_eq!(mini.input_usd_per_1m, 0.15);
let full = price_for("gpt-4o").unwrap();
assert_eq!(full.input_usd_per_1m, 2.50);
}
#[test]
fn unknown_model_returns_none() {
assert!(price_for("some-future-model").is_none());
assert!(estimate_cost_usd(10, 10, "unknown").is_none());
}
#[test]
fn estimate_is_per_1m_scaled() {
let cost = estimate_cost_usd(1_000_000, 1_000_000, "claude-3-5-sonnet").unwrap();
assert!((cost - 18.0).abs() < 1e-9, "got {cost}");
let small = estimate_cost_usd(1000, 1000, "claude-3-5-sonnet").unwrap();
assert!((small - 0.018).abs() < 1e-9, "got {small}");
}
}