mod deepseek;
mod gemma4;
mod gemma4_strict;
pub(crate) mod harmony;
mod llama;
mod mistral_nemo;
mod qwen;
use hanzo_ml::Result;
use llguidance::api::TopLevelGrammar;
use crate::Tool;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ToolCallFormat {
Qwen,
Llama,
MistralNemo,
DeepSeek,
Gemma4,
}
pub trait ToolFormatParser: Send + Sync {
fn could_be_tool_call(&self, text: &str) -> bool;
fn parse(&self, message: &str) -> Result<Option<String>>;
fn format(&self) -> ToolCallFormat;
fn tool_call_grammar(&self, tools: &[Tool], text: &str) -> TopLevelGrammar;
}
static PARSERS: std::sync::LazyLock<Vec<Box<dyn ToolFormatParser>>> =
std::sync::LazyLock::new(|| {
vec![
Box::new(gemma4::Gemma4Parser),
Box::new(llama::LlamaParser),
Box::new(qwen::QwenParser),
Box::new(mistral_nemo::MistralNemoParser),
Box::new(deepseek::DeepSeekParser),
]
});
pub fn contains_tool_call_prefix(text: &str) -> bool {
PARSERS.iter().any(|p| p.could_be_tool_call(text))
}
pub fn build_tool_call_grammar(text: &str, tools: &[Tool]) -> Option<TopLevelGrammar> {
for parser in PARSERS.iter() {
if parser.could_be_tool_call(text) {
if parser.format() == ToolCallFormat::DeepSeek && !text.contains("```json\n") {
return None;
}
return Some(parser.tool_call_grammar(tools, text));
}
}
None
}
pub fn process_model_specific_message(message: &str) -> Result<String> {
for parser in PARSERS.iter() {
if let Some(json) = parser.parse(message)? {
return Ok(json);
}
}
Ok(message.to_string())
}