Skip to main content

reasoning_parser/parsers/
glm45.rs

1// GLM45 specific reasoning parser.
2// Uses the same format as Qwen3 but has its own implementation for debugging.
3
4use crate::{
5    parsers::BaseReasoningParser,
6    traits::{ParseError, ParserConfig, ParserResult, ReasoningParser, DEFAULT_MAX_BUFFER_SIZE},
7};
8
9/// GLM45 reasoning parser.
10///
11/// This parser uses the same format as Qwen3 (<think>...</think>) but has
12/// its own implementation for better debugging and potential future customization.
13pub struct Glm45Parser {
14    base: BaseReasoningParser,
15}
16
17impl Glm45Parser {
18    /// Create a new GLM45 parser.
19    pub fn new() -> Self {
20        let config = ParserConfig {
21            think_start_token: "<think>".to_string(),
22            think_end_token: "</think>".to_string(),
23            stream_reasoning: true,
24            max_buffer_size: DEFAULT_MAX_BUFFER_SIZE,
25            always_in_reasoning: false,
26        };
27
28        Self {
29            base: BaseReasoningParser::new(config).with_model_type("glm45".to_string()),
30        }
31    }
32}
33
34impl Default for Glm45Parser {
35    fn default() -> Self {
36        Self::new()
37    }
38}
39
40impl ReasoningParser for Glm45Parser {
41    fn detect_and_parse_reasoning(&mut self, text: &str) -> Result<ParserResult, ParseError> {
42        self.base.detect_and_parse_reasoning(text)
43    }
44
45    fn parse_reasoning_streaming_incremental(
46        &mut self,
47        text: &str,
48    ) -> Result<ParserResult, ParseError> {
49        self.base.parse_reasoning_streaming_incremental(text)
50    }
51
52    fn reset(&mut self) {
53        self.base.reset();
54    }
55
56    fn model_type(&self) -> &str {
57        self.base.model_type()
58    }
59
60    fn is_in_reasoning(&self) -> bool {
61        self.base.is_in_reasoning()
62    }
63
64    fn mark_reasoning_started(&mut self) {
65        self.base.mark_reasoning_started();
66    }
67
68    fn mark_think_start_stripped(&mut self) {
69        self.base.mark_think_start_stripped();
70    }
71}
72
73#[cfg(test)]
74mod tests {
75    use super::*;
76
77    #[test]
78    fn test_glm45_initial_state() {
79        let mut parser = Glm45Parser::new();
80
81        // Should NOT treat text as reasoning without start token
82        let result = parser
83            .detect_and_parse_reasoning("This is normal content")
84            .unwrap();
85        assert_eq!(result.normal_text, "This is normal content");
86        assert_eq!(result.reasoning_text, "");
87    }
88
89    #[test]
90    fn test_glm45_with_tokens() {
91        let mut parser = Glm45Parser::new();
92
93        // Should extract reasoning with proper tokens
94        let result = parser
95            .detect_and_parse_reasoning("<think>reasoning content</think>answer")
96            .unwrap();
97        assert_eq!(result.normal_text, "answer");
98        assert_eq!(result.reasoning_text, "reasoning content");
99    }
100
101    #[test]
102    fn test_glm45_streaming() {
103        let mut parser = Glm45Parser::new();
104
105        // First chunk - normal text
106        let result1 = parser
107            .parse_reasoning_streaming_incremental("normal text ")
108            .unwrap();
109        assert_eq!(result1.normal_text, "normal text ");
110        assert_eq!(result1.reasoning_text, "");
111
112        // Second chunk - enters reasoning
113        let result2 = parser
114            .parse_reasoning_streaming_incremental("<think>reasoning")
115            .unwrap();
116        assert_eq!(result2.normal_text, "");
117        assert_eq!(result2.reasoning_text, "reasoning");
118
119        // Third chunk - exits reasoning
120        let result3 = parser
121            .parse_reasoning_streaming_incremental("</think>answer")
122            .unwrap();
123        assert_eq!(result3.normal_text, "answer");
124        assert_eq!(result3.reasoning_text, "");
125    }
126
127    #[test]
128    fn test_model_type() {
129        let parser = Glm45Parser::new();
130        assert_eq!(parser.model_type(), "glm45");
131    }
132}