use lc_schema::Message;
pub trait TokenCounter: Send + Sync {
fn count_tokens(&self, text: &str) -> u32;
fn count_messages(&self, messages: &[Message]) -> u32;
}
#[derive(Debug, Clone)]
pub struct CharRatioCounter {
ratio: u32,
}
impl CharRatioCounter {
pub fn new(ratio: u32) -> Self {
Self {
ratio: ratio.max(1),
}
}
}
impl TokenCounter for CharRatioCounter {
fn count_tokens(&self, text: &str) -> u32 {
text.len() as u32 / self.ratio
}
fn count_messages(&self, messages: &[Message]) -> u32 {
let mut total = 0u32;
for msg in messages {
total += 4; total += self.count_tokens(&msg.content);
if let Some(name) = &msg.name {
total += self.count_tokens(name);
}
for _ in &msg.images {
total += 1000;
}
}
total += 2; total
}
}
#[cfg(test)]
mod char_ratio_tests {
use super::*;
#[test]
fn test_char_ratio_counts_bytes() {
let counter = CharRatioCounter::new(4);
assert_eq!(counter.count_tokens(""), 0);
assert_eq!(counter.count_tokens("Hello"), 1); assert_eq!(counter.count_tokens("Hello World"), 2); }
#[test]
fn test_char_ratio_ratio_at_least_one() {
let counter = CharRatioCounter::new(0);
assert!(counter.count_tokens("x") >= 1);
}
#[test]
fn test_char_ratio_count_messages_matches_structure() {
let counter = CharRatioCounter::new(4);
let msgs = vec![Message::system("You are helpful."), Message::human("Hi")];
let n = counter.count_messages(&msgs);
assert!(n >= 10);
}
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct TrackerTokenUsage {
pub prompt_tokens: u32,
pub completion_tokens: u32,
pub total_tokens: u32,
}
impl TrackerTokenUsage {
pub fn new() -> Self {
Self::default()
}
pub fn add(&mut self, prompt: u32, completion: u32) {
self.prompt_tokens += prompt;
self.completion_tokens += completion;
self.total_tokens = self.prompt_tokens + self.completion_tokens;
}
pub fn reset(&mut self) {
*self = Self::default();
}
}
pub use TrackerTokenUsage as TokenUsage;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_usage_add() {
let mut u = TrackerTokenUsage::new();
u.add(10, 20);
assert_eq!(u.prompt_tokens, 10);
assert_eq!(u.completion_tokens, 20);
assert_eq!(u.total_tokens, 30);
}
#[test]
fn test_usage_accumulate() {
let mut u = TrackerTokenUsage::new();
u.add(10, 20);
u.add(5, 5);
assert_eq!(u.prompt_tokens, 15);
assert_eq!(u.total_tokens, 40);
}
#[test]
fn test_usage_reset() {
let mut u = TrackerTokenUsage::new();
u.add(10, 20);
u.reset();
assert_eq!(u, TrackerTokenUsage::new());
}
}