1pub mod as_of;
4pub mod refresh;
5pub mod store;
6pub use store::{current_path, embedded, pricing_dir, PriceStore};
7
8use crate::model::MessageUsage;
9use serde::{Deserialize, Serialize};
10
11#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
14pub struct ModelPrice {
15 pub input: f64,
16 pub output: f64,
17 #[serde(default)]
18 pub cache_read: f64,
19 #[serde(default)]
20 pub cache_write: f64, #[serde(default)]
22 pub cache_write_1h: Option<f64>,
23 #[serde(default)]
24 pub tier_boundary: Option<u64>,
25 #[serde(default)]
26 pub input_above: Option<f64>,
27 #[serde(default)]
28 pub output_above: Option<f64>,
29 #[serde(default)]
30 pub cache_read_above: Option<f64>,
31 #[serde(default)]
32 pub cache_write_above: Option<f64>,
33}
34
35pub fn cost_for(price: &ModelPrice, u: &MessageUsage) -> f64 {
37 let above = price
38 .tier_boundary
39 .is_some_and(|b| u.request_input_tokens > b);
40 let pick = |base: f64, over: Option<f64>| if above { over.unwrap_or(base) } else { base };
41
42 let r_in = pick(price.input, price.input_above);
43 let r_out = pick(price.output, price.output_above);
44 let r_cr = pick(price.cache_read, price.cache_read_above);
45 let r_cw5 = pick(price.cache_write, price.cache_write_above);
46 let r_cw1 = price.cache_write_1h.unwrap_or(price.cache_write);
47
48 (u.input_uncached as f64 * r_in
49 + u.output as f64 * r_out
50 + u.cache_read as f64 * r_cr
51 + u.cache_write_5m as f64 * r_cw5
52 + u.cache_write_1h as f64 * r_cw1)
53 / 1_000_000.0
54}
55
56#[cfg(test)]
57mod tests {
58 use super::*;
59 use crate::model::Provider;
60
61 fn usage(input: u64, cr: u64, cw5: u64, out: u64, req_in: u64) -> MessageUsage {
62 MessageUsage {
63 model: "claude-opus-4-8".into(),
64 provider: Provider::Anthropic,
65 namespace: "litellm".into(),
66 input_uncached: input,
67 cache_read: cr,
68 cache_write_5m: cw5,
69 cache_write_1h: 0,
70 output: out,
71 request_input_tokens: req_in,
72 service_tier: Some("standard".into()),
73 }
74 }
75
76 fn opus() -> ModelPrice {
78 ModelPrice {
79 input: 5.0,
80 output: 25.0,
81 cache_read: 0.5,
82 cache_write: 6.25,
83 cache_write_1h: Some(10.0),
84 tier_boundary: None,
85 input_above: None,
86 output_above: None,
87 cache_read_above: None,
88 cache_write_above: None,
89 }
90 }
91
92 #[test]
93 fn flat_model_costs_each_bucket() {
94 let u = usage(1_000_000, 1_000_000, 1_000_000, 1_000_000, 3_000_000);
96 let c = cost_for(&opus(), &u);
97 assert!((c - (5.0 + 0.5 + 6.25 + 25.0)).abs() < 1e-9, "got {c}");
98 }
99
100 #[test]
101 fn tiered_model_switches_rates_above_boundary() {
102 let price = ModelPrice {
104 input: 3.0,
105 output: 15.0,
106 cache_read: 0.3,
107 cache_write: 3.75,
108 cache_write_1h: None,
109 tier_boundary: Some(200_000),
110 input_above: Some(6.0),
111 output_above: Some(22.5),
112 cache_read_above: Some(0.6),
113 cache_write_above: Some(7.5),
114 };
115 let u = usage(1_000_000, 0, 0, 1_000_000, 300_000);
117 let c = cost_for(&price, &u);
118 assert!((c - (6.0 + 22.5)).abs() < 1e-9, "got {c}");
119 let u2 = usage(1_000_000, 0, 0, 1_000_000, 100_000);
121 let c2 = cost_for(&price, &u2);
122 assert!((c2 - (3.0 + 15.0)).abs() < 1e-9, "got {c2}");
123 }
124}