use metrale_grammar::{GrammarCompiler, TokenizerInfo, VocabType, detect_metadata_from_hf};
const GRAMMAR_CACHE_BUDGET_BYTES: isize = 1024 * 1024 * 1024;
use super::extract_ordered_vocab;
pub struct GrammarEngine {
pub(super) compiler: GrammarCompiler,
vocab_size: usize,
pub(super) snapshot: Option<super::mask_cache::MaskSnapshot>,
}
unsafe impl Send for GrammarEngine {}
#[derive(Debug, thiserror::Error)]
pub enum GrammarError {
#[error("grammar compilation failed: {0}")]
Compilation(String),
#[error("invalid JSON schema: {0}")]
InvalidSchema(String),
#[error("no tools provided")]
NoTools,
}
impl GrammarEngine {
pub fn new(vocab: &[String], stop_token_ids: &[i32]) -> Result<Self, GrammarError> {
let stop: Option<Box<[i32]>> = if stop_token_ids.is_empty() {
None
} else {
Some(stop_token_ids.to_vec().into_boxed_slice())
};
let tokenizer_info = TokenizerInfo::new(vocab, VocabType::RAW, &stop, false)
.map_err(GrammarError::Compilation)?;
Self::from_tokenizer_info(tokenizer_info)
}
pub fn from_tokenizer(
tokenizer: &tokenizers::Tokenizer,
vocab_size: Option<usize>,
stop_token_ids: &[i32],
) -> Result<Self, GrammarError> {
let backend_str = tokenizer
.to_string(false)
.map_err(|e| GrammarError::Compilation(format!("tokenizer serialize failed: {e}")))?;
let metadata = detect_metadata_from_hf(&backend_str).map_err(GrammarError::Compilation)?;
let detected_label = match metadata.vocab_type {
VocabType::RAW => "raw",
VocabType::BYTE_FALLBACK => "byte_fallback",
VocabType::BYTE_LEVEL => "byte_level",
};
tracing::info!(
"Grammar: detected tokenizer vocab_type={detected_label}, add_prefix_space={}",
metadata.add_prefix_space,
);
let ordered_vocab = extract_ordered_vocab(tokenizer);
let model_vocab_size = vocab_size.unwrap_or(ordered_vocab.len());
let mut sized_vocab = if model_vocab_size > ordered_vocab.len() {
let mut v = ordered_vocab;
v.resize(model_vocab_size, String::new());
v
} else if model_vocab_size < ordered_vocab.len() {
let mut v = ordered_vocab;
v.truncate(model_vocab_size);
v
} else {
ordered_vocab
};
let _ = &mut sized_vocab;
let stop: Option<Box<[i32]>> = if stop_token_ids.is_empty() {
None
} else {
Some(stop_token_ids.to_vec().into_boxed_slice())
};
let tokenizer_info = TokenizerInfo::new(
&sized_vocab,
metadata.vocab_type,
&stop,
metadata.add_prefix_space,
)
.map_err(GrammarError::Compilation)?;
Self::from_tokenizer_info(tokenizer_info)
}
fn from_tokenizer_info(tokenizer_info: TokenizerInfo) -> Result<Self, GrammarError> {
let vocab_size = tokenizer_info.vocab_size();
let compiler = GrammarCompiler::new(&tokenizer_info, 1, true, GRAMMAR_CACHE_BUDGET_BYTES)
.map_err(GrammarError::Compilation)?;
Ok(Self {
compiler,
vocab_size,
snapshot: None,
})
}
pub fn vocab_size(&self) -> usize {
self.vocab_size
}
}