use crate::SlmRole;
use crate::formatter::{SlmFormatter, SlmToolStyle};
pub struct MistralFormatter {
flavor: MistralFlavor,
thinking: bool,
}
pub enum MistralFlavor {
V3Tekken,
Legacy,
}
impl MistralFormatter {
pub fn new(flavor: MistralFlavor, thinking: bool) -> Self {
Self { flavor, thinking }
}
}
impl SlmFormatter for MistralFormatter {
fn bos(&self) -> Option<&str> {
Some("<s>")
}
fn turn_start(&self, role: &SlmRole) -> String {
match self.flavor {
MistralFlavor::V3Tekken => match role {
SlmRole::System => "[SYSTEM_PROMPT]".to_string(),
SlmRole::User => "[INST]".to_string(),
SlmRole::Assistant => String::new(),
SlmRole::Tool(_) => String::new(),
},
MistralFlavor::Legacy => match role {
SlmRole::System => "[INST] ".to_string(),
SlmRole::User => "[INST]".to_string(),
SlmRole::Assistant => String::new(),
SlmRole::Tool(_) => String::new(),
},
}
}
fn turn_end(&self, role: &SlmRole) -> String {
match self.flavor {
MistralFlavor::V3Tekken => match role {
SlmRole::System => " [/SYSTEM_PROMPT]\n".to_string(),
SlmRole::User => " [/INST]\n".to_string(),
SlmRole::Assistant => "</s>".to_string(),
SlmRole::Tool(_) => String::new(),
},
MistralFlavor::Legacy => match role {
SlmRole::System => "\n\n".to_string(), SlmRole::User => "[/INST]".to_string(),
SlmRole::Assistant => "</s>".to_string(),
SlmRole::Tool(_) => String::new(),
},
}
}
fn reasoning_bounds(&self) -> Option<(&str, &str)> {
if self.thinking {
Some(("<think>\n", "\n</think>"))
} else {
None
}
}
fn wrap_reasoning(&self, content: &str) -> String {
if self.thinking {
format!("<think>\n{}\n</think>", content.trim())
} else {
content.to_string()
}
}
fn reasoning_trigger(&self) -> Option<&str> {
if self.thinking {
Some("<think>\n")
} else {
None
}
}
fn tool_style(&self) -> SlmToolStyle {
SlmToolStyle::Inline
}
fn format_tool_call(&self, name: &str, arguments: &str) -> String {
let args = arguments.trim();
format!(
r#"[TOOL_CALLS][{{"name": "{}", "arguments": {}}}][\/TOOL_CALLS]"#,
name, args
)
}
fn format_tool_response(&self, _name: &str, content: &str) -> String {
format!("[TOOL_RESULTS]{}[/TOOL_RESULTS]", content.trim())
}
fn strip_tags(&self, text: &str) -> String {
let mut cleaned = text.to_string();
let mistral_structural_tags = [
"<s>",
"</s>",
"[SYSTEM_PROMPT]",
"[/SYSTEM_PROMPT]",
"[INST]",
"[/INST]",
];
for tag in mistral_structural_tags {
cleaned = cleaned.replace(tag, "");
}
let mistral_channels = [
"[TOOL_CALLS]",
"[/TOOL_CALLS]",
"[TOOL_RESULTS]",
"[/TOOL_RESULTS]",
"<think>",
"</think>",
];
for tag in mistral_channels {
cleaned = cleaned.replace(tag, "");
}
while let Some(start_idx) = cleaned.find("[TOOL_CALLS]") {
if let Some(end_idx) = cleaned[start_idx..].find("[/TOOL_CALLS]") {
let absolute_end_idx = start_idx + end_idx + "[/TOOL_CALLS]".len();
cleaned.drain(start_idx..absolute_end_idx);
} else {
cleaned.drain(start_idx..);
break;
}
}
while let Some(start_idx) = cleaned.find("[TOOL_RESULTS]") {
if let Some(end_idx) = cleaned[start_idx..].find("[/TOOL_RESULTS]") {
let absolute_end_idx = start_idx + end_idx + "[/TOOL_RESULTS]".len();
cleaned.drain(start_idx..absolute_end_idx);
} else {
cleaned.drain(start_idx..);
break;
}
}
cleaned.trim().to_string()
}
}