dynamo_parsers/reasoning/
minimax_append_think_parser.rs1use crate::{ParserResult, ReasoningParser};
5
6#[derive(Debug, Default)]
26pub struct MiniMaxAppendThinkParser {
27 prefix_emitted: bool,
30}
31
32impl MiniMaxAppendThinkParser {
33 pub fn new() -> Self {
34 Self::default()
35 }
36}
37
38const THINK_START_TOKEN: &str = "<think>";
39
40impl ReasoningParser for MiniMaxAppendThinkParser {
41 fn detect_and_parse_reasoning(&mut self, text: &str, _token_ids: &[u32]) -> ParserResult {
42 ParserResult {
45 normal_text: format!("{THINK_START_TOKEN}{text}"),
46 reasoning_text: String::new(),
47 }
48 }
49
50 fn parse_reasoning_streaming_incremental(
51 &mut self,
52 text: &str,
53 _token_ids: &[u32],
54 ) -> ParserResult {
55 let normal_text = if !self.prefix_emitted {
56 self.prefix_emitted = true;
57 format!("{THINK_START_TOKEN}{text}")
58 } else {
59 text.to_string()
60 };
61 ParserResult {
62 normal_text,
63 reasoning_text: String::new(),
64 }
65 }
66}
67
68#[cfg(test)]
69mod tests {
70 use super::*;
71
72 #[test] fn test_detect_and_parse_prepends_think_all_as_normal_text() {
74 let mut parser = MiniMaxAppendThinkParser::new();
75 let result = parser.detect_and_parse_reasoning("reasoning content here", &[]);
76 assert_eq!(result.normal_text, "<think>reasoning content here");
78 assert_eq!(result.reasoning_text, "");
79 }
80
81 #[test] fn test_detect_and_parse_with_end_token_is_still_normal_text() {
83 let mut parser = MiniMaxAppendThinkParser::new();
84 let result =
85 parser.detect_and_parse_reasoning("reasoning content</think>normal response", &[]);
86 assert_eq!(
89 result.normal_text,
90 "<think>reasoning content</think>normal response"
91 );
92 assert_eq!(result.reasoning_text, "");
93 }
94
95 #[test] fn test_streaming_first_chunk_gets_prefix_rest_pass_through() {
97 let mut parser = MiniMaxAppendThinkParser::new();
98
99 let r1 = parser.parse_reasoning_streaming_incremental("I need to ", &[]);
100 assert_eq!(r1.normal_text, "<think>I need to ");
101 assert_eq!(r1.reasoning_text, "");
102
103 let r2 = parser.parse_reasoning_streaming_incremental("check the weather", &[]);
104 assert_eq!(r2.normal_text, "check the weather");
105 assert_eq!(r2.reasoning_text, "");
106
107 let r3 = parser.parse_reasoning_streaming_incremental("</think>The weather is sunny.", &[]);
108 assert_eq!(r3.normal_text, "</think>The weather is sunny.");
110 assert_eq!(r3.reasoning_text, "");
111 }
112
113 #[test] fn test_streaming_bare_json_tool_call_is_normal_text() {
115 let mut parser = MiniMaxAppendThinkParser::new();
120 let r = parser.parse_reasoning_streaming_incremental(
121 r#"[{"name":"get_weather","parameters":{"location":"San Francisco"}}]"#,
122 &[],
123 );
124 assert_eq!(
125 r.normal_text,
126 r#"<think>[{"name":"get_weather","parameters":{"location":"San Francisco"}}]"#
127 );
128 assert_eq!(r.reasoning_text, "");
129 }
130
131 #[test] fn test_streaming_tool_call_after_reasoning_is_all_normal_text() {
133 let mut parser = MiniMaxAppendThinkParser::new();
134
135 let r1 = parser.parse_reasoning_streaming_incremental("let me call a tool", &[]);
136 assert_eq!(r1.normal_text, "<think>let me call a tool");
137
138 let r2 = parser.parse_reasoning_streaming_incremental(
139 "</think><minimax:tool_call><invoke name=\"get_weather\">",
140 &[],
141 );
142 assert_eq!(
144 r2.normal_text,
145 "</think><minimax:tool_call><invoke name=\"get_weather\">"
146 );
147 assert_eq!(r2.reasoning_text, "");
148 }
149}