use crate::{ParserResult, ReasoningParser};
use super::base_parser::BasicReasoningParser;
#[derive(Debug)]
pub struct MiniMaxAppendThinkParser {
inner: BasicReasoningParser,
is_first_chunk: bool,
}
impl Default for MiniMaxAppendThinkParser {
fn default() -> Self {
Self {
inner: BasicReasoningParser::new(
"<think>".into(),
"</think>".into(),
false, true, ),
is_first_chunk: true,
}
}
}
impl MiniMaxAppendThinkParser {
pub fn new() -> Self {
Self::default()
}
}
impl ReasoningParser for MiniMaxAppendThinkParser {
fn detect_and_parse_reasoning(&mut self, text: &str, token_ids: &[u32]) -> ParserResult {
let augmented = format!("<think>{}", text);
self.inner.detect_and_parse_reasoning(&augmented, token_ids)
}
fn parse_reasoning_streaming_incremental(
&mut self,
text: &str,
token_ids: &[u32],
) -> ParserResult {
if self.is_first_chunk {
self.is_first_chunk = false;
let augmented = format!("<think>{}", text);
self.inner
.parse_reasoning_streaming_incremental(&augmented, token_ids)
} else {
self.inner
.parse_reasoning_streaming_incremental(text, token_ids)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_detect_and_parse_no_end_token() {
let mut parser = MiniMaxAppendThinkParser::new();
let result = parser.detect_and_parse_reasoning("reasoning content here", &[]);
assert_eq!(result.reasoning_text, "reasoning content here");
assert_eq!(result.normal_text, "");
}
#[test]
fn test_detect_and_parse_with_end_token() {
let mut parser = MiniMaxAppendThinkParser::new();
let result =
parser.detect_and_parse_reasoning("reasoning content</think>normal response", &[]);
assert_eq!(result.reasoning_text, "reasoning content");
assert_eq!(result.normal_text, "normal response");
}
#[test]
fn test_streaming_basic_flow() {
let mut parser = MiniMaxAppendThinkParser::new();
let r1 = parser.parse_reasoning_streaming_incremental("I need to ", &[]);
assert_eq!(r1.reasoning_text, "I need to ");
assert_eq!(r1.normal_text, "");
let r2 = parser.parse_reasoning_streaming_incremental("check the weather", &[]);
assert_eq!(r2.reasoning_text, "check the weather");
assert_eq!(r2.normal_text, "");
let r3 = parser.parse_reasoning_streaming_incremental("</think>The weather is sunny.", &[]);
assert_eq!(r3.reasoning_text, "");
assert_eq!(r3.normal_text, "The weather is sunny.");
}
#[test]
fn test_streaming_end_token_split_across_chunks() {
let mut parser = MiniMaxAppendThinkParser::new();
let r1 = parser.parse_reasoning_streaming_incremental("reasoning", &[]);
assert_eq!(r1.reasoning_text, "reasoning");
assert_eq!(r1.normal_text, "");
let r2 = parser.parse_reasoning_streaming_incremental("</thi", &[]);
assert_eq!(r2.reasoning_text, "");
assert_eq!(r2.normal_text, "");
let r3 = parser.parse_reasoning_streaming_incremental("nk>normal text", &[]);
assert_eq!(r3.reasoning_text, "");
assert_eq!(r3.normal_text, "normal text");
}
#[test]
fn test_streaming_only_reasoning_no_end() {
let mut parser = MiniMaxAppendThinkParser::new();
let r1 = parser.parse_reasoning_streaming_incremental("still thinking", &[]);
assert_eq!(r1.reasoning_text, "still thinking");
assert_eq!(r1.normal_text, "");
let r2 = parser.parse_reasoning_streaming_incremental(" more thought", &[]);
assert_eq!(r2.reasoning_text, " more thought");
assert_eq!(r2.normal_text, "");
}
#[test]
fn test_streaming_with_tool_call_after_reasoning() {
let mut parser = MiniMaxAppendThinkParser::new();
let r1 = parser.parse_reasoning_streaming_incremental("let me call a tool", &[]);
assert_eq!(r1.reasoning_text, "let me call a tool");
let r2 = parser.parse_reasoning_streaming_incremental(
"</think><minimax:tool_call><invoke name=\"get_weather\">",
&[],
);
assert_eq!(r2.reasoning_text, "");
assert!(
r2.normal_text
.contains("<minimax:tool_call><invoke name=\"get_weather\">")
);
}
#[test]
fn test_streaming_tool_call_angle_bracket_split_tokens() {
let mut parser = MiniMaxAppendThinkParser::new();
let r1 = parser.parse_reasoning_streaming_incremental("let me check the weather", &[]);
assert_eq!(r1.reasoning_text, "let me check the weather");
let r2 = parser.parse_reasoning_streaming_incremental("</think>", &[]);
assert_eq!(r2.reasoning_text, "");
assert_eq!(r2.normal_text, "");
let r3 = parser.parse_reasoning_streaming_incremental("<minimax:tool_call>", &[]);
assert_eq!(r3.normal_text, "<minimax:tool_call>");
let r4 = parser.parse_reasoning_streaming_incremental("\n", &[]);
assert_eq!(r4.normal_text, "\n");
let r5 = parser.parse_reasoning_streaming_incremental("<", &[]);
assert_eq!(r5.normal_text, "<");
let r6 = parser.parse_reasoning_streaming_incremental("invoke name=\"get_weather\">", &[]);
assert_eq!(r6.normal_text, "invoke name=\"get_weather\">");
}
}