mod glm47;
#[cfg(test)]
mod glm47_tests;
mod parsers;
#[cfg(test)]
mod tests;
use std::str::FromStr;
use crate::tokenizer::ChatTokenizer;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ReasoningWire {
ReasoningContent,
Vllm,
}
pub trait ReasoningParser: Send + Sync {
fn name(&self) -> &str;
fn start_tag(&self) -> &str;
fn end_tag(&self) -> &str;
fn end_token_id(&self, tokenizer: &ChatTokenizer) -> Option<u32> {
match tokenizer.encode(self.end_tag()) {
Ok(ids) if ids.len() == 1 => Some(ids[0]),
_ => None,
}
}
fn tool_call_closes_reasoning(&self) -> bool {
false
}
fn shapes_stream_whitespace(&self) -> bool {
false
}
fn reasoning_wire(&self) -> ReasoningWire {
ReasoningWire::ReasoningContent
}
fn extract_thinking(&self, text: &str, enable_thinking: bool) -> (Option<String>, String);
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ReasoningFormat {
Qwen,
DeepSeekR1,
MiniMax,
Mistral,
Gemma4,
Glm47,
}
impl FromStr for ReasoningFormat {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"qwen" | "qwen3" => Ok(Self::Qwen),
"deepseek_r1" | "deepseek" | "nemotron" | "nemotron_h" | "nano_v3" => {
Ok(Self::DeepSeekR1)
}
"minimax" | "minimax_m2" => Ok(Self::MiniMax),
"mistral" => Ok(Self::Mistral),
"gemma4" | "gemma" => Ok(Self::Gemma4),
"glm47" | "glm45" => Ok(Self::Glm47),
other => Err(format!(
"Unknown reasoning parser '{other}'. Supported: qwen, \
deepseek_r1, minimax, mistral, gemma4, glm47"
)),
}
}
}
impl ReasoningFormat {
pub fn into_parser(self) -> Box<dyn ReasoningParser> {
parsers::build(self)
}
}