use anda_core::BoxError;
use serde::{Deserialize, Deserializer};
use std::{
panic::{AssertUnwindSafe, catch_unwind},
sync::OnceLock,
};
pub const TOKENIZER: &str = "o200k_base@tiktoken-rs-0.12";
pub const LEGACY_TOKENIZER: &str = "o200k_base@tiktoken-rs-0.12.0";
pub fn canonical_tokenizer(name: &str) -> Option<&'static str> {
matches!(name, TOKENIZER | LEGACY_TOKENIZER).then_some(TOKENIZER)
}
pub(super) fn deserialize_tokenizer<'de, D: Deserializer<'de>>(
deserializer: D,
) -> Result<String, D::Error> {
let name = String::deserialize(deserializer)?;
Ok(canonical_tokenizer(&name).map_or(name, str::to_string))
}
static ENCODING: OnceLock<Result<tiktoken_rs::CoreBPE, String>> = OnceLock::new();
pub fn count(text: &str) -> Result<usize, BoxError> {
match ENCODING.get_or_init(|| {
catch_unwind(tiktoken_rs::o200k_base)
.map_err(|_| "pinned tokenizer initialization panicked".to_string())
.and_then(|result| result.map_err(|error| error.to_string()))
}) {
Ok(encoding) => catch_unwind(AssertUnwindSafe(|| encoding.encode_ordinary(text).len()))
.map_err(|_| "Recall tokenizer failed to encode ordinary text".into()),
Err(error) => Err(format!("Recall tokenizer initialization failed: {error}").into()),
}
}