use llm_cascade::Message;
use tokenizers::Tokenizer;
use crate::error::Result;
pub struct TokenCounter {
tokenizer: Option<Tokenizer>,
model_identifier: String,
}
impl TokenCounter {
pub fn new(model_identifier: &str) -> Result<Self> {
let tokenizer = match Tokenizer::from_pretrained(model_identifier, None) {
Ok(t) => {
tracing::info!("Loaded tokenizer: {}", model_identifier);
Some(t)
}
Err(e) => {
tracing::warn!(
"Failed to load tokenizer '{}': {}. Will use char-based estimate fallback.",
model_identifier,
e
);
None
}
};
Ok(Self {
tokenizer,
model_identifier: model_identifier.to_string(),
})
}
pub fn count_text(&self, text: &str) -> usize {
if let Some(ref tok) = self.tokenizer {
match tok.encode(text, false) {
Ok(encoding) => encoding.len(),
Err(e) => {
tracing::debug!("Tokenization failed, using estimate: {}", e);
estimate_tokens(text)
}
}
} else {
estimate_tokens(text)
}
}
pub fn count_messages(&self, messages: &[Message]) -> usize {
let mut total = 0;
for msg in messages {
total += self.count_text(&msg.content);
total += role_overhead(&msg.role);
total += 1; }
total += 3;
total
}
pub fn model_identifier(&self) -> &str {
&self.model_identifier
}
}
fn role_overhead(role: &llm_cascade::MessageRole) -> usize {
match role {
llm_cascade::MessageRole::System => 2, llm_cascade::MessageRole::User => 2, llm_cascade::MessageRole::Assistant => 3, llm_cascade::MessageRole::Tool => 2, }
}
fn estimate_tokens(text: &str) -> usize {
(text.len() / 4).max(1)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_estimate_tokens() {
assert_eq!(estimate_tokens("hello"), 1); assert_eq!(estimate_tokens("hello world this is a test"), 6); assert!(estimate_tokens("") >= 1); }
#[test]
fn test_count_messages_empty() {
let counter = TokenCounter {
tokenizer: None,
model_identifier: "test".into(),
};
assert_eq!(counter.count_messages(&[]), 3); }
#[test]
fn test_count_messages_with_roles() {
let counter = TokenCounter {
tokenizer: None,
model_identifier: "test".into(),
};
let msgs = vec![
Message::system("You are helpful."),
Message::user("Hello!"),
Message::assistant("Hi there!"),
];
let count = counter.count_messages(&msgs);
assert_eq!(count, 20);
}
}