Skip to main content

llm_cost_cap/
prices.rs

1//! Built-in price table for common LLM models.
2//!
3//! All rates are USD per million tokens, reflecting published provider
4//! pricing as of 2026-05-24. Where a vendor publishes a cached read rate
5//! distinct from the standard input rate (currently Anthropic), it is
6//! exposed via [`ModelPrice::cached_input_per_million_usd`].
7
8use std::collections::HashMap;
9
10#[cfg(feature = "serde")]
11use serde::{Deserialize, Serialize};
12
13/// Per-million-token pricing for one model.
14///
15/// `cached_input_per_million_usd` covers vendor prompt-cache reads. Pass
16/// `None` when the vendor does not publish a distinct cached read rate.
17#[derive(Debug, Clone, Copy, PartialEq)]
18#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
19pub struct ModelPrice {
20    pub input_per_million_usd: f64,
21    pub output_per_million_usd: f64,
22    pub cached_input_per_million_usd: Option<f64>,
23}
24
25impl ModelPrice {
26    pub const fn new(input_per_million_usd: f64, output_per_million_usd: f64) -> Self {
27        Self {
28            input_per_million_usd,
29            output_per_million_usd,
30            cached_input_per_million_usd: None,
31        }
32    }
33
34    pub const fn with_cached(
35        input_per_million_usd: f64,
36        output_per_million_usd: f64,
37        cached_input_per_million_usd: f64,
38    ) -> Self {
39        Self {
40            input_per_million_usd,
41            output_per_million_usd,
42            cached_input_per_million_usd: Some(cached_input_per_million_usd),
43        }
44    }
45}
46
47// Canonical models. USD per 1M tokens as of 2026-05-24.
48const BUILTIN: &[(&str, ModelPrice)] = &[
49    // Anthropic
50    ("claude-opus-4-7", ModelPrice::with_cached(15.00, 75.00, 1.50)),
51    ("claude-opus-4-6", ModelPrice::with_cached(15.00, 75.00, 1.50)),
52    ("claude-opus-4-5", ModelPrice::with_cached(15.00, 75.00, 1.50)),
53    ("claude-sonnet-4-6", ModelPrice::with_cached(3.00, 15.00, 0.30)),
54    ("claude-sonnet-4-5", ModelPrice::with_cached(3.00, 15.00, 0.30)),
55    ("claude-haiku-4-5", ModelPrice::with_cached(0.80, 4.00, 0.08)),
56    // OpenAI
57    ("gpt-5.4", ModelPrice::new(1.25, 10.00)),
58    ("gpt-5", ModelPrice::new(1.25, 10.00)),
59    ("gpt-5-mini", ModelPrice::new(0.25, 2.00)),
60    ("gpt-5-nano", ModelPrice::new(0.05, 0.40)),
61    // Google Gemini
62    ("gemini-2.5-pro", ModelPrice::new(1.25, 10.00)),
63    ("gemini-2.5-flash", ModelPrice::new(0.30, 2.50)),
64    // AWS Bedrock - Anthropic
65    ("anthropic.claude-opus-4-7-v1:0", ModelPrice::with_cached(15.00, 75.00, 1.50)),
66    ("anthropic.claude-sonnet-4-6-v1:0", ModelPrice::with_cached(3.00, 15.00, 0.30)),
67    ("anthropic.claude-haiku-4-5-v1:0", ModelPrice::with_cached(0.80, 4.00, 0.08)),
68    // AWS Bedrock - Meta
69    ("meta.llama3-1-70b-instruct-v1:0", ModelPrice::new(2.65, 3.50)),
70];
71
72const ALIASES: &[(&str, &str)] = &[
73    ("opus", "claude-opus-4-7"),
74    ("sonnet", "claude-sonnet-4-6"),
75    ("haiku", "claude-haiku-4-5"),
76    ("gpt5", "gpt-5"),
77    ("gemini-pro", "gemini-2.5-pro"),
78    ("gemini-flash", "gemini-2.5-flash"),
79];
80
81/// Flat slice of (model, input_per_million_usd, output_per_million_usd)
82/// for callers that want a quick read of the canonical table. Cached
83/// rates are dropped; use [`builtin_prices`] for the full picture.
84pub const MODEL_PRICES: &[(&str, f64, f64)] = &[
85    ("claude-opus-4-7", 15.00, 75.00),
86    ("claude-opus-4-6", 15.00, 75.00),
87    ("claude-opus-4-5", 15.00, 75.00),
88    ("claude-sonnet-4-6", 3.00, 15.00),
89    ("claude-sonnet-4-5", 3.00, 15.00),
90    ("claude-haiku-4-5", 0.80, 4.00),
91    ("gpt-5.4", 1.25, 10.00),
92    ("gpt-5", 1.25, 10.00),
93    ("gpt-5-mini", 0.25, 2.00),
94    ("gpt-5-nano", 0.05, 0.40),
95    ("gemini-2.5-pro", 1.25, 10.00),
96    ("gemini-2.5-flash", 0.30, 2.50),
97    ("anthropic.claude-opus-4-7-v1:0", 15.00, 75.00),
98    ("anthropic.claude-sonnet-4-6-v1:0", 3.00, 15.00),
99    ("anthropic.claude-haiku-4-5-v1:0", 0.80, 4.00),
100    ("meta.llama3-1-70b-instruct-v1:0", 2.65, 3.50),
101];
102
103/// Return a fresh map of the built-in price table with aliases resolved.
104///
105/// Callers receive an owned copy and can mutate it freely.
106pub fn builtin_prices() -> HashMap<String, ModelPrice> {
107    let mut table: HashMap<String, ModelPrice> = BUILTIN
108        .iter()
109        .map(|(id, p)| ((*id).to_string(), *p))
110        .collect();
111    for (alias, canonical) in ALIASES {
112        if let Some(price) = table.get(*canonical).copied() {
113            table.insert((*alias).to_string(), price);
114        }
115    }
116    table
117}
118
119/// Sorted list of model ids (including aliases) the built-in table knows.
120pub fn known_models() -> Vec<String> {
121    let mut ids: Vec<String> = BUILTIN.iter().map(|(id, _)| (*id).to_string()).collect();
122    for (alias, _) in ALIASES {
123        ids.push((*alias).to_string());
124    }
125    ids.sort();
126    ids.dedup();
127    ids
128}