telos_agent/model/
tokens.rs1use std::sync::OnceLock;
9use tiktoken_rs::CoreBPE;
10
11static CL100K: OnceLock<Option<CoreBPE>> = OnceLock::new();
13
14fn get_bpe() -> Option<&'static CoreBPE> {
15 CL100K
16 .get_or_init(|| {
17 let bpe = tiktoken_rs::cl100k_base().ok();
18 if bpe.is_none() {
19 tracing::warn!(
20 "tiktoken-rs failed to load cl100k_base vocabulary; \
21 falling back to char/4 heuristic"
22 );
23 }
24 bpe
25 })
26 .as_ref()
27}
28
29pub fn count_tokens(text: &str) -> usize {
34 if text.is_empty() {
35 return 0;
36 }
37 match get_bpe() {
38 Some(bpe) => bpe.encode_ordinary(text).len(),
39 None => {
40 (text.len() as f64 / 4.0_f64).ceil() as usize
43 }
44 }
45}
46
47#[cfg(test)]
48mod tests {
49 use super::*;
50
51 #[test]
52 fn empty_string_is_zero() {
53 assert_eq!(count_tokens(""), 0);
54 }
55
56 #[test]
57 fn english_text_is_counted() {
58 let count = count_tokens("hello world");
60 assert!(count > 0, "token count should be > 0");
61 assert!(count <= 10, "short text shouldn't be many tokens");
62 }
63
64 #[test]
65 fn chinese_text_is_counted() {
66 let count = count_tokens("你好世界");
69 assert!(count > 0);
70 assert!(count >= 2, "Chinese chars should be > 1 token (old heuristic was wrong)");
73 }
74
75 #[test]
76 fn code_text_is_counted() {
77 let count = count_tokens("fn main() { println!(\"hi\"); }");
78 assert!(count > 0);
79 assert!(count <= 50);
80 }
81
82 #[test]
83 fn larger_text_is_reasonable() {
84 let text = "This is a longer piece of text that should be counted. ".repeat(100);
85 let count = count_tokens(&text);
86 let chars = text.len();
87 assert!(count < chars);
89 assert!(count > chars / 5, "token count should be within reasonable range");
91 }
92
93 #[test]
94 fn count_is_stable() {
95 let text = "The quick brown fox jumps over the lazy dog.";
96 let a = count_tokens(text);
97 let b = count_tokens(text);
98 assert_eq!(a, b, "same text should always give same count");
99 }
100}