deepseek_r1.rs 3.39 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// DeepSeek-R1 specific reasoning parser.
// This parser starts with in_reasoning=true, assuming all text is reasoning
// until an end token is encountered.

use crate::reasoning_parser::parsers::BaseReasoningParser;
use crate::reasoning_parser::traits::{ParseError, ParserConfig, ParserResult, ReasoningParser};

/// DeepSeek-R1 reasoning parser.
///
/// This parser assumes reasoning from the start of text (in_reasoning=true)
/// and uses <think> and </think> tokens.
pub struct DeepSeekR1Parser {
    base: BaseReasoningParser,
}

impl DeepSeekR1Parser {
    /// Create a new DeepSeek-R1 parser.
    pub fn new() -> Self {
        let config = ParserConfig {
            think_start_token: "<think>".to_string(),
            think_end_token: "</think>".to_string(),
            stream_reasoning: true,
            max_buffer_size: 65536,
            initial_in_reasoning: true, // Always starts with reasoning
        };

        Self {
            base: BaseReasoningParser::new(config).with_model_type("deepseek_r1".to_string()),
        }
    }
}

impl Default for DeepSeekR1Parser {
    fn default() -> Self {
        Self::new()
    }
}

impl ReasoningParser for DeepSeekR1Parser {
    fn detect_and_parse_reasoning(&mut self, text: &str) -> Result<ParserResult, ParseError> {
        self.base.detect_and_parse_reasoning(text)
    }

    fn parse_reasoning_streaming_incremental(
        &mut self,
        text: &str,
    ) -> Result<ParserResult, ParseError> {
        self.base.parse_reasoning_streaming_incremental(text)
    }

    fn reset(&mut self) {
        self.base.reset()
    }

    fn model_type(&self) -> &str {
        self.base.model_type()
    }
58
59
60
61

    fn is_in_reasoning(&self) -> bool {
        self.base.is_in_reasoning()
    }
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_deepseek_r1_initial_state() {
        let mut parser = DeepSeekR1Parser::new();

        // Should treat text as reasoning even without start token
        let result = parser
            .detect_and_parse_reasoning("This is reasoning content")
            .unwrap();
        assert_eq!(result.normal_text, "");
        assert_eq!(result.reasoning_text, "This is reasoning content");
    }

    #[test]
    fn test_deepseek_r1_with_end_token() {
        let mut parser = DeepSeekR1Parser::new();

        // Should extract reasoning until end token
        let result = parser
            .detect_and_parse_reasoning("reasoning content</think>normal content")
            .unwrap();
        assert_eq!(result.normal_text, "normal content");
        assert_eq!(result.reasoning_text, "reasoning content");
    }

    #[test]
    fn test_deepseek_r1_streaming() {
        let mut parser = DeepSeekR1Parser::new();

        // First chunk - all reasoning
        let result1 = parser
            .parse_reasoning_streaming_incremental("thinking about")
            .unwrap();
        assert_eq!(result1.reasoning_text, "thinking about");
        assert_eq!(result1.normal_text, "");

        // Second chunk - ends reasoning
        let result2 = parser
            .parse_reasoning_streaming_incremental(" the problem</think>answer")
            .unwrap();
        assert_eq!(result2.reasoning_text, "the problem"); // Text is trimmed
        assert_eq!(result2.normal_text, "answer");
    }

    #[test]
    fn test_model_type() {
        let parser = DeepSeekR1Parser::new();
        assert_eq!(parser.model_type(), "deepseek_r1");
    }
}