use anyhow::Result;
#[cfg(feature = "tiktoken")]
use tiktoken_rs::CoreBPE;
use crate::llmtrim::ir::ProviderKind;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)]
pub struct Tokens(pub usize);
impl std::fmt::Display for Tokens {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
pub trait TokenCounter: Send + Sync {
fn count(&self, text: &str) -> usize;
fn is_exact(&self) -> bool;
fn label(&self) -> &str;
}
#[cfg(feature = "tiktoken")]
pub struct TiktokenCounter {
bpe: &'static CoreBPE,
label: &'static str,
exact: bool,
}
#[cfg(feature = "tiktoken")]
impl TokenCounter for TiktokenCounter {
fn count(&self, text: &str) -> usize {
self.bpe.encode_with_special_tokens(text).len()
}
fn is_exact(&self) -> bool {
self.exact
}
fn label(&self) -> &str {
self.label
}
}
fn estimate_tokens(text: &str) -> usize {
const CHARS_PER_WORD_TOKEN: usize = 4;
const CALIB: f64 = 0.72;
let mut raw = 0usize;
let mut run = 0usize; let mut ws = 0usize; let price_ws = |ws: usize| ws.saturating_sub(1).div_ceil(CHARS_PER_WORD_TOKEN);
for c in text.chars() {
if c.is_alphanumeric() {
raw += price_ws(ws);
ws = 0;
run += 1;
} else {
if run > 0 {
raw += run.div_ceil(CHARS_PER_WORD_TOKEN);
run = 0;
}
if c.is_whitespace() {
ws += 1;
} else {
raw += price_ws(ws);
ws = 0;
raw += 1; }
}
}
raw += run.div_ceil(CHARS_PER_WORD_TOKEN);
raw += price_ws(ws);
(raw as f64 * CALIB).round() as usize
}
pub struct ApproxCounter {
label: &'static str,
}
impl TokenCounter for ApproxCounter {
fn count(&self, text: &str) -> usize {
estimate_tokens(text)
}
fn is_exact(&self) -> bool {
false
}
fn label(&self) -> &str {
self.label
}
}
pub fn counter_for(provider: ProviderKind, model: Option<&str>) -> Result<Box<dyn TokenCounter>> {
match provider {
ProviderKind::OpenAi => {
#[cfg(feature = "tiktoken")]
{
let (bpe, label, exact): (_, &'static str, bool) =
match model.and_then(|m| tiktoken_rs::bpe_for_model(m).ok()) {
Some(bpe) => (bpe, "tiktoken", true),
None => (tiktoken_rs::o200k_base_singleton(), "o200k-approx", false),
};
Ok(Box::new(TiktokenCounter { bpe, label, exact }))
}
#[cfg(not(feature = "tiktoken"))]
{
let _ = model;
Ok(Box::new(ApproxCounter {
label: "approx(openai)",
}))
}
}
ProviderKind::Anthropic => Ok(Box::new(ApproxCounter {
label: "approx(anthropic)",
})),
ProviderKind::Google => Ok(Box::new(ApproxCounter {
label: "approx(google)",
})),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "tiktoken")]
#[test]
fn openai_counter_is_exact_and_counts() {
let c = counter_for(ProviderKind::OpenAi, Some("gpt-4o")).unwrap();
assert!(c.is_exact());
assert_eq!(c.count(""), 0);
assert!(c.count("hello world") >= 2);
assert!(c.count("hello world, this is a longer sentence") > c.count("hello world"));
}
#[cfg(not(feature = "tiktoken"))]
#[test]
fn openai_counter_falls_back_to_approx_without_tiktoken() {
let c = counter_for(ProviderKind::OpenAi, Some("gpt-4o")).unwrap();
assert!(!c.is_exact());
assert!(c.label().contains("approx"));
assert!(c.count("hello world") >= 1);
assert!(c.count("hello world, this is a longer sentence") > c.count("hello world"));
}
#[test]
fn anthropic_counter_is_flagged_approximate() {
let c = counter_for(ProviderKind::Anthropic, None).unwrap();
assert!(!c.is_exact());
assert!(c.label().contains("approx"));
assert!(c.count("some tokens here") > 0);
}
#[test]
fn unknown_openai_model_falls_back() {
let c = counter_for(ProviderKind::OpenAi, Some("gpt-99-superfuture")).unwrap();
assert!(c.count("x") >= 1);
}
}