Skip to main content

reasoning_parser/parsers/
nano_v3.rs

1// NanoV3 specific reasoning parser.
2// Uses the same format as DeepSeek-R1 (<think>...</think>) with initial_in_reasoning=true.
3
4use crate::{
5    parsers::BaseReasoningParser,
6    traits::{ParseError, ParserConfig, ParserResult, ReasoningParser},
7};
8
9/// NanoV3 reasoning parser.
10///
11/// Uses the same reasoning format as DeepSeek-R1: `<think>...</think>`.
12/// Starts with `initial_in_reasoning=true`, assuming all output is reasoning
13/// until a `</think>` token is encountered.
14pub struct NanoV3Parser {
15    base: BaseReasoningParser,
16}
17
18impl NanoV3Parser {
19    /// Create a new NanoV3 parser.
20    pub fn new() -> Self {
21        let config = ParserConfig {
22            think_start_token: "<think>".to_string(),
23            think_end_token: "</think>".to_string(),
24            stream_reasoning: true,
25            max_buffer_size: 65536,
26            initial_in_reasoning: true,
27        };
28
29        Self {
30            base: BaseReasoningParser::new(config).with_model_type("nano_v3".to_string()),
31        }
32    }
33}
34
35impl Default for NanoV3Parser {
36    fn default() -> Self {
37        Self::new()
38    }
39}
40
41impl ReasoningParser for NanoV3Parser {
42    fn detect_and_parse_reasoning(&mut self, text: &str) -> Result<ParserResult, ParseError> {
43        self.base.detect_and_parse_reasoning(text)
44    }
45
46    fn parse_reasoning_streaming_incremental(
47        &mut self,
48        text: &str,
49    ) -> Result<ParserResult, ParseError> {
50        self.base.parse_reasoning_streaming_incremental(text)
51    }
52
53    fn reset(&mut self) {
54        self.base.reset()
55    }
56
57    fn model_type(&self) -> &str {
58        self.base.model_type()
59    }
60
61    fn is_in_reasoning(&self) -> bool {
62        self.base.is_in_reasoning()
63    }
64}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69
70    #[test]
71    fn test_nano_v3_initial_state() {
72        let mut parser = NanoV3Parser::new();
73
74        // Should treat text as reasoning even without start token
75        let result = parser
76            .detect_and_parse_reasoning("This is reasoning content")
77            .unwrap();
78        assert_eq!(result.normal_text, "");
79        assert_eq!(result.reasoning_text, "This is reasoning content");
80    }
81
82    #[test]
83    fn test_nano_v3_with_end_token() {
84        let mut parser = NanoV3Parser::new();
85
86        let result = parser
87            .detect_and_parse_reasoning("reasoning content</think>answer")
88            .unwrap();
89        assert_eq!(result.normal_text, "answer");
90        assert_eq!(result.reasoning_text, "reasoning content");
91    }
92
93    #[test]
94    fn test_nano_v3_with_both_tokens() {
95        let mut parser = NanoV3Parser::new();
96
97        let result = parser
98            .detect_and_parse_reasoning("<think>reasoning content</think>answer")
99            .unwrap();
100        assert_eq!(result.normal_text, "answer");
101        assert_eq!(result.reasoning_text, "reasoning content");
102    }
103
104    #[test]
105    fn test_nano_v3_streaming() {
106        let mut parser = NanoV3Parser::new();
107
108        let result1 = parser
109            .parse_reasoning_streaming_incremental("reasoning text ")
110            .unwrap();
111        assert_eq!(result1.normal_text, "");
112        assert_eq!(result1.reasoning_text, "reasoning text ");
113
114        let result2 = parser
115            .parse_reasoning_streaming_incremental("more reasoning</think>answer")
116            .unwrap();
117        assert_eq!(result2.normal_text, "answer");
118        assert_eq!(result2.reasoning_text, "more reasoning");
119    }
120
121    #[test]
122    fn test_model_type() {
123        let parser = NanoV3Parser::new();
124        assert_eq!(parser.model_type(), "nano_v3");
125    }
126}