mattrguerra/cortexmainPrivate on GitHub, published here in full

cost.rs

Language Rust2.7 KB78 lines

crates/cortex-core/src/cost.rs

  1. //! Money. Stored as whole nano-dollars (1e-9 USD) in an i64, so sums over a
  2. //! month never drift the way floats do. A message keeps the prices it was
  3. //! billed at, so a later price change never rewrites history.
  4. use crate::types::{Prices, Usage};
  5. pub const NANO_PER_USD: f64 = 1e9;
  6. /// Cost of one completion. Cached prompt tokens bill at the cache-read price
  7. /// when the model has one, otherwise at the input price.
  8. pub fn cost_nanousd(usage: &Usage, prices: &Prices) -> i64 {
  9. let cached = usage.cached_tokens.clamp(0, usage.prompt_tokens.max(0));
  10. let uncached = (usage.prompt_tokens - cached).max(0);
  11. let cache_price = prices.cache_read_per_m.unwrap_or(prices.input_per_m);
  12. // $/1M tokens * tokens / 1e6 * 1e9 nano = price * tokens * 1000
  13. let nano = uncached as f64 * prices.input_per_m * 1000.0
  14. + cached as f64 * cache_price * 1000.0
  15. + usage.completion_tokens.max(0) as f64 * prices.output_per_m * 1000.0;
  16. nano.round() as i64
  17. }
  18. pub fn to_usd(nano: i64) -> f64 {
  19. nano as f64 / NANO_PER_USD
  20. }
  21. /// "$0.0012", "$1.24", "<$0.0001", "$0".
  22. pub fn format_usd(nano: i64) -> String {
  23. if nano == 0 {
  24. return "$0".into();
  25. }
  26. let usd = to_usd(nano);
  27. if usd < 0.0001 {
  28. "<$0.0001".into()
  29. } else if usd < 1.0 {
  30. format!("${usd:.4}")
  31. } else {
  32. format!("${usd:.2}")
  33. }
  34. }
  35. #[cfg(test)]
  36. mod tests {
  37. use super::*;
  38. const P: Prices = Prices { input_per_m: 0.5, output_per_m: 1.5, cache_read_per_m: Some(0.1) };
  39. #[test]
  40. fn hand_calculated() {
  41. // 1,000 in at $0.50/M = $0.0005; 2,000 out at $1.50/M = $0.003. Total $0.0035.
  42. let u = Usage { prompt_tokens: 1000, completion_tokens: 2000, cached_tokens: 0 };
  43. assert_eq!(cost_nanousd(&u, &P), 3_500_000);
  44. assert_eq!(format_usd(3_500_000), "$0.0035");
  45. }
  46. #[test]
  47. fn cached_tokens_bill_at_cache_price() {
  48. // 600 uncached * 0.5 + 400 cached * 0.1 = 300,000 + 40,000 nano (x1000 scale)
  49. let u = Usage { prompt_tokens: 1000, completion_tokens: 0, cached_tokens: 400 };
  50. assert_eq!(cost_nanousd(&u, &P), 340_000);
  51. let no_cache_price = Prices { cache_read_per_m: None, ..P };
  52. assert_eq!(cost_nanousd(&u, &no_cache_price), 500_000);
  53. }
  54. #[test]
  55. fn odd_inputs_are_safe() {
  56. let u = Usage { prompt_tokens: 10, completion_tokens: -5, cached_tokens: 99 };
  57. assert_eq!(cost_nanousd(&u, &P), 1_000); // cached clamps to 10, negatives to 0
  58. assert_eq!(cost_nanousd(&Usage::default(), &Prices::default()), 0);
  59. }
  60. #[test]
  61. fn formatting() {
  62. assert_eq!(format_usd(0), "$0");
  63. assert_eq!(format_usd(12), "<$0.0001");
  64. assert_eq!(format_usd(1_240_000_000), "$1.24");
  65. assert_eq!(format_usd(120_000), "$0.0001");
  66. }
  67. }