1use async_trait::async_trait;
8use ferrum_types::{Result, SpecialTokens, TokenId};
9use serde::{Deserialize, Serialize};
10use std::collections::HashMap;
11
12pub trait Tokenizer: Send + Sync {
14 fn encode(&self, text: &str, add_special: bool) -> Result<Vec<TokenId>>;
16
17 fn decode(&self, tokens: &[TokenId], skip_special: bool) -> Result<String>;
19
20 fn decode_incremental(&self, prev: &[TokenId], next: TokenId) -> Result<String>;
23
24 fn vocab_size(&self) -> usize;
26
27 fn special_tokens(&self) -> &SpecialTokens;
29
30 fn token_id(&self, text: &str) -> Option<TokenId>;
32
33 fn token_text(&self, token_id: TokenId) -> Option<&str>;
35
36 fn token_bytes(&self, token_id: TokenId) -> Option<Vec<u8>> {
44 self.decode(&[token_id], false)
45 .ok()
46 .map(String::into_bytes)
47 .or_else(|| {
48 self.token_text(token_id)
49 .map(|text| text.as_bytes().to_vec())
50 })
51 }
52
53 fn is_special_token(&self, token_id: TokenId) -> bool {
55 let special = self.special_tokens();
56 let fallback = TokenId::MAX;
57 token_id == special.bos_token.unwrap_or(fallback)
58 || token_id == special.eos_token.unwrap_or(fallback)
59 || token_id == special.unk_token.unwrap_or(fallback)
60 || token_id == special.pad_token.unwrap_or(fallback)
61 }
62
63 fn apply_chat_template(&self, messages: &[ChatMessage]) -> Result<String> {
65 let mut result = String::new();
67 for msg in messages {
68 result.push_str(&format!("{}: {}\n", msg.role, msg.content));
69 }
70 Ok(result.trim_end().to_string())
71 }
72
73 fn info(&self) -> TokenizerInfo;
75}
76
77#[async_trait]
79pub trait AsyncTokenizer: Tokenizer {
80 async fn encode_async(&self, text: &str, add_special: bool) -> Result<Vec<TokenId>>;
82
83 async fn decode_async(&self, tokens: &[TokenId], skip_special: bool) -> Result<String>;
85
86 async fn encode_batch(&self, texts: &[&str], add_special: bool) -> Result<Vec<Vec<TokenId>>>;
88
89 async fn decode_batch(
91 &self,
92 token_sequences: &[&[TokenId]],
93 skip_special: bool,
94 ) -> Result<Vec<String>>;
95}
96
97pub trait TokenizerCapabilities: Tokenizer {
99 fn token_probability(&self, text: &str, token_id: TokenId) -> Option<f32>;
101
102 fn get_prefix_tokens(&self, prefix: &str) -> Result<Vec<TokenId>>;
104
105 fn can_extend(&self, tokens: &[TokenId], next_token: TokenId) -> bool;
107
108 fn token_type(&self, token_id: TokenId) -> TokenType;
110
111 fn normalize_text(&self, text: &str) -> String;
113
114 fn pre_tokenize(&self, text: &str) -> Vec<String>;
116}
117
118#[async_trait]
120pub trait TokenizerFactory: Send + Sync {
121 async fn load_from_file(&self, path: &str) -> Result<Box<dyn Tokenizer>>;
123
124 async fn load_from_bytes(&self, data: &[u8]) -> Result<Box<dyn Tokenizer>>;
126
127 async fn load_from_hub(
129 &self,
130 repo_id: &str,
131 revision: Option<&str>,
132 ) -> Result<Box<dyn Tokenizer>>;
133
134 async fn create_from_config(&self, config: &TokenizerConfig) -> Result<Box<dyn Tokenizer>>;
136
137 fn supported_types(&self) -> Vec<TokenizerType>;
139}
140
141#[derive(Debug, Clone, Serialize, Deserialize)]
143pub struct TokenizerInfo {
144 pub tokenizer_type: TokenizerType,
146 pub vocab_size: usize,
148 pub special_tokens: SpecialTokens,
150 pub supports_incremental: bool,
152 pub supports_chat_template: bool,
154 pub max_token_length: Option<usize>,
156 pub model_name: Option<String>,
158}
159
160#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
162pub enum TokenizerType {
163 BPE,
165 WordPiece,
167 SentencePiece,
169 Tiktoken,
171 Custom,
173}
174
175#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
177pub enum TokenType {
178 Word,
180 Subword,
182 Punctuation,
184 Number,
186 Special,
188 Unknown,
190}
191
192#[derive(Debug, Clone, Serialize, Deserialize)]
194pub struct ChatMessage {
195 pub role: String,
197 pub content: String,
199 pub metadata: HashMap<String, serde_json::Value>,
201}
202
203impl ChatMessage {
204 pub fn user(content: impl Into<String>) -> Self {
206 Self {
207 role: "user".to_string(),
208 content: content.into(),
209 metadata: HashMap::new(),
210 }
211 }
212
213 pub fn assistant(content: impl Into<String>) -> Self {
215 Self {
216 role: "assistant".to_string(),
217 content: content.into(),
218 metadata: HashMap::new(),
219 }
220 }
221
222 pub fn system(content: impl Into<String>) -> Self {
224 Self {
225 role: "system".to_string(),
226 content: content.into(),
227 metadata: HashMap::new(),
228 }
229 }
230}
231
232#[derive(Debug, Clone, Serialize, Deserialize)]
234pub struct TokenizerConfig {
235 pub tokenizer_type: TokenizerType,
237 pub path: String,
239 pub add_special_tokens: bool,
241 pub use_fast: bool,
243 pub truncation: Option<TruncationConfig>,
245 pub padding: Option<PaddingConfig>,
247 pub chat_template: Option<String>,
249 pub extra_options: HashMap<String, serde_json::Value>,
251}
252
253#[derive(Debug, Clone, Serialize, Deserialize)]
255pub struct TruncationConfig {
256 pub max_length: usize,
258 pub strategy: TruncationStrategy,
260 pub stride: Option<usize>,
262}
263
264#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
266pub enum TruncationStrategy {
267 TruncateEnd,
269 TruncateStart,
271 TruncateBoth,
273 SlidingWindow,
275}
276
277#[derive(Debug, Clone, Serialize, Deserialize)]
279pub struct PaddingConfig {
280 pub strategy: PaddingStrategy,
282 pub token_id: TokenId,
284 pub length: Option<usize>,
286 pub direction: PaddingDirection,
288}
289
290#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
292pub enum PaddingStrategy {
293 None,
295 Longest,
297 MultipleOf(usize),
299 Fixed,
301}
302
303#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
305pub enum PaddingDirection {
306 Right,
308 Left,
310}
311
312pub trait IncrementalTokenizer: Tokenizer {
314 type State: Send + Sync;
316
317 fn create_state(&self) -> Self::State;
319
320 fn decode_incremental_with_state(
322 &self,
323 state: &mut Self::State,
324 token: TokenId,
325 ) -> Result<String>;
326
327 fn reset_state(&self, state: &mut Self::State);
329
330 fn get_decoded_text(&self, state: &Self::State) -> String;
332}
333
334pub trait TextProcessor: Send + Sync {
336 fn preprocess(&self, text: &str) -> String;
338
339 fn postprocess(&self, text: &str) -> String;
341
342 fn detect_language(&self, text: &str) -> Option<String>;
344
345 fn sentence_split(&self, text: &str) -> Vec<String>;
347
348 fn estimate_token_count(&self, text: &str) -> usize;
350}
351
352#[derive(Debug, Clone, Serialize, Deserialize)]
354pub struct TokenizerStats {
355 pub encode_operations: u64,
357 pub decode_operations: u64,
359 pub tokens_processed: u64,
361 pub avg_encode_time_per_char_us: f64,
363 pub avg_decode_time_per_token_us: f64,
365 pub incremental_cache_hit_rate: f32,
367}
368
369pub trait TokenizerRegistry: Send + Sync {
371 fn register(&mut self, name: &str, tokenizer: Box<dyn Tokenizer>) -> Result<()>;
373
374 fn get(&self, name: &str) -> Option<&dyn Tokenizer>;
376
377 fn remove(&mut self, name: &str) -> Option<Box<dyn Tokenizer>>;
379
380 fn list_names(&self) -> Vec<String>;
382
383 fn contains(&self, name: &str) -> bool;
385}