use crate::TokenUsage;
use std::sync::atomic::{AtomicU64, Ordering};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Price {
pub input_per_token: u64,
pub output_per_token: u64,
}
impl Price {
pub const HAIKU: Self = Self {
input_per_token: 250_000,
output_per_token: 1_250_000,
};
pub const OLLAMA: Self = Self {
input_per_token: 0,
output_per_token: 0,
};
}
#[derive(Debug, Default)]
pub struct CostTracker {
input_tokens: AtomicU64,
output_tokens: AtomicU64,
scaled_cost_micro_cents: AtomicU64,
}
impl CostTracker {
pub fn new() -> Self {
Self::default()
}
pub fn record(&self, usage: TokenUsage, price: Price) {
self.input_tokens
.fetch_add(usage.input as u64, Ordering::Relaxed);
self.output_tokens
.fetch_add(usage.output as u64, Ordering::Relaxed);
let product = (usage.input as u64).saturating_mul(price.input_per_token)
+ (usage.output as u64).saturating_mul(price.output_per_token);
self.scaled_cost_micro_cents
.fetch_add(product, Ordering::Relaxed);
}
pub fn spent_micro_cents(&self) -> u64 {
self.scaled_cost_micro_cents.load(Ordering::Relaxed) / 1_000_000
}
pub fn input_tokens(&self) -> u64 {
self.input_tokens.load(Ordering::Relaxed)
}
pub fn output_tokens(&self) -> u64 {
self.output_tokens.load(Ordering::Relaxed)
}
pub fn log_session(&self) {
tracing::info!(
target: "evolve::cost",
input_tokens = self.input_tokens(),
output_tokens = self.output_tokens(),
micro_cents = self.spent_micro_cents(),
"evolve llm usage"
);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn record_twice_accumulates() {
let tracker = CostTracker::new();
tracker.record(
TokenUsage {
input: 100,
output: 50,
},
Price::HAIKU,
);
tracker.record(
TokenUsage {
input: 200,
output: 75,
},
Price::HAIKU,
);
assert_eq!(tracker.input_tokens(), 300);
assert_eq!(tracker.output_tokens(), 125);
}
#[test]
fn haiku_cost_math_matches_hand_calc() {
let tracker = CostTracker::new();
tracker.record(
TokenUsage {
input: 1_000_000,
output: 0,
},
Price::HAIKU,
);
assert_eq!(tracker.spent_micro_cents(), 250_000);
let tracker2 = CostTracker::new();
tracker2.record(
TokenUsage {
input: 0,
output: 1_000_000,
},
Price::HAIKU,
);
assert_eq!(tracker2.spent_micro_cents(), 1_250_000);
}
#[test]
fn typical_challenger_call_cost_is_under_one_cent() {
let tracker = CostTracker::new();
tracker.record(
TokenUsage {
input: 500,
output: 200,
},
Price::HAIKU,
);
assert_eq!(tracker.spent_micro_cents(), 375);
}
#[test]
fn ollama_records_zero_cost() {
let tracker = CostTracker::new();
tracker.record(
TokenUsage {
input: 10_000,
output: 10_000,
},
Price::OLLAMA,
);
assert_eq!(tracker.spent_micro_cents(), 0);
assert_eq!(tracker.input_tokens(), 10_000);
assert_eq!(tracker.output_tokens(), 10_000);
}
}