use crate::SlmRole;
use crate::formatter::{SlmFormatter, SlmToolStyle};
pub struct Phi4Formatter {
thinking: bool,
}
impl Phi4Formatter {
pub fn new(thinking: bool) -> Self {
Self { thinking }
}
}
impl SlmFormatter for Phi4Formatter {
fn bos(&self) -> Option<&str> {
None
}
fn turn_start(&self, role: &SlmRole) -> String {
match role {
SlmRole::System => "<|system|>\n".to_string(),
SlmRole::User => "<|user|>\n".to_string(),
SlmRole::Assistant => "<|assistant|>\n".to_string(),
SlmRole::Tool(_) => String::new(), }
}
fn turn_end(&self, role: &SlmRole) -> String {
match role {
SlmRole::Tool(_) => String::new(),
_ => "<|end|>\n".to_string(),
}
}
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 {
format!(
r#"{{"name": "{}", "arguments": {}}}"#,
name,
arguments.trim()
)
}
fn format_tool_response(&self, _name: &str, content: &str) -> String {
format!("<|tool_response|>\n{}\n<|end|>\n", content.trim())
}
fn strip_tags(&self, text: &str) -> String {
let mut cleaned = text.to_string();
let phi4_structural_tags = [
"<|system|>",
"<|user|>",
"<|assistant|>",
"<|end|>",
"<|tool_response|>",
];
for tag in phi4_structural_tags {
cleaned = cleaned.replace(tag, "");
}
let phi4_channels = ["<think>", "</think>"];
for tag in phi4_channels {
cleaned = cleaned.replace(tag, "");
}
cleaned.trim().to_string()
}
}