use crate::providers::ChatMessage;
#[ derive( Debug, Clone, Copy, PartialEq, Eq ) ]
pub enum CountingStrategy
{
Estimation,
WordBased,
CharacterBased,
}
impl Default for CountingStrategy
{
#[ inline ]
fn default() -> Self
{
Self::CharacterBased
}
}
#[ derive( Debug, Clone, Copy, PartialEq, Eq ) ]
pub struct TokenCount
{
pub total : usize,
pub characters : usize,
pub strategy : CountingStrategy,
}
impl TokenCount
{
#[ inline ]
#[ must_use ]
pub fn new( total : usize, characters : usize, strategy : CountingStrategy ) -> Self
{
Self {
total,
characters,
strategy,
}
}
#[ inline ]
#[ must_use ]
pub fn cost_units( &self ) -> f64
{
self.total as f64 / 1000.0
}
}
#[ derive( Debug, Clone ) ]
pub struct TokenCounter
{
strategy : CountingStrategy,
}
impl TokenCounter
{
#[ inline ]
#[ must_use ]
pub fn new( strategy : CountingStrategy ) -> Self
{
Self { strategy }
}
#[ inline ]
#[ must_use ]
pub fn count_tokens( &self, text : &str ) -> TokenCount
{
let characters = text.chars( ).count( );
let total = match self.strategy
{
CountingStrategy::Estimation => Self::estimate_tokens( text ),
CountingStrategy::WordBased => Self::word_based_count( text ),
CountingStrategy::CharacterBased => Self::character_based_count( text ),
};
TokenCount::new( total, characters, self.strategy )
}
#[ inline ]
#[ must_use ]
pub fn count_texts( &self, texts : &[&str ] ) -> TokenCount
{
let total_text = texts.join( "" );
self.count_tokens( &total_text )
}
#[ inline ]
#[ must_use ]
pub fn count_messages( &self, messages : &[ChatMessage ] ) -> TokenCount
{
let mut total_chars = 0;
let mut total_tokens = 0;
for message in messages
{
let role_count = self.count_tokens( &message.role );
total_chars += role_count.characters;
total_tokens += role_count.total;
let content_count = self.count_tokens( &message.content );
total_chars += content_count.characters;
total_tokens += content_count.total;
total_tokens += 4; }
TokenCount::new( total_tokens, total_chars, self.strategy )
}
#[ inline ]
fn estimate_tokens( text : &str ) -> usize
{
let chars = text.chars( ).count( );
( chars + 3 ) / 4 }
#[ inline ]
fn word_based_count( text : &str ) -> usize
{
let words = text.split_whitespace( ).count( );
( words * 13 + 9 ) / 10 }
#[ inline ]
fn character_based_count( text : &str ) -> usize
{
let chars = text.chars( ).count( );
( chars * 2 + 6 ) / 7 }
#[ inline ]
#[ must_use ]
pub fn strategy( &self ) -> CountingStrategy
{
self.strategy
}
#[ inline ]
pub fn set_strategy( &mut self, strategy : CountingStrategy )
{
self.strategy = strategy;
}
}
impl Default for TokenCounter
{
#[ inline ]
fn default() -> Self
{
Self::new( CountingStrategy::default( ))
}
}
#[ derive( Debug ) ]
pub enum TokenCountError
{
TextTooLarge {
size : usize,
max_size : usize,
},
}
impl core::fmt::Display for TokenCountError
{
#[ inline ]
fn fmt( &self, f : &mut core::fmt::Formatter< '_ > ) -> core::fmt::Result
{
match self
{
Self::TextTooLarge { size, max_size } => {
write!( f, "Text too large : {size} characters ( max : {max_size} )" )
}
}
}
}
impl std::error::Error for TokenCountError {}