Skip to main content

reasoning_parser/
traits.rs

1use std::fmt;
2
3/// Result of parsing text for reasoning content.
4#[derive(Debug, Clone, Default, PartialEq)]
5pub struct ParserResult {
6    /// The normal text outside reasoning blocks.
7    pub normal_text: String,
8
9    /// The extracted reasoning text from within reasoning blocks.
10    pub reasoning_text: String,
11}
12
13impl ParserResult {
14    /// Create a new ParserResult with the given normal and reasoning text.
15    pub fn new(normal_text: String, reasoning_text: String) -> Self {
16        Self {
17            normal_text,
18            reasoning_text,
19        }
20    }
21
22    /// Create a result with only normal text.
23    pub fn normal(text: String) -> Self {
24        Self {
25            normal_text: text,
26            reasoning_text: String::new(),
27        }
28    }
29
30    /// Create a result with only reasoning text.
31    pub fn reasoning(text: String) -> Self {
32        Self {
33            normal_text: String::new(),
34            reasoning_text: text,
35        }
36    }
37
38    /// Check if this result contains any text.
39    pub fn is_empty(&self) -> bool {
40        self.normal_text.is_empty() && self.reasoning_text.is_empty()
41    }
42}
43
44/// Trait for parsing reasoning content from LLM outputs.
45pub trait ReasoningParser: Send + Sync {
46    /// Detects and parses reasoning from the input text (one-time parsing).
47    ///
48    /// This method is used for non-streaming scenarios where the complete
49    /// text is available at once.
50    ///
51    /// Returns an error if the text exceeds buffer limits or contains invalid UTF-8.
52    fn detect_and_parse_reasoning(&mut self, text: &str) -> Result<ParserResult, ParseError>;
53
54    /// Parses reasoning incrementally from streaming input.
55    ///
56    /// This method maintains internal state across calls to handle partial
57    /// tokens and chunk boundaries correctly.
58    ///
59    /// Returns an error if the buffer exceeds max_buffer_size.
60    fn parse_reasoning_streaming_incremental(
61        &mut self,
62        text: &str,
63    ) -> Result<ParserResult, ParseError>;
64
65    /// Reset the parser state for reuse.
66    ///
67    /// This should clear any buffers and reset flags to initial state.
68    fn reset(&mut self);
69
70    /// Get the model type this parser is designed for.
71    fn model_type(&self) -> &str;
72
73    /// Check if the parser is currently in reasoning mode.
74    ///
75    /// Returns true if the parser is currently parsing reasoning content.
76    fn is_in_reasoning(&self) -> bool;
77}
78
79/// Error types for reasoning parsing operations.
80#[derive(Debug, thiserror::Error)]
81pub enum ParseError {
82    #[error("Invalid UTF-8 in stream: {0}")]
83    Utf8Error(#[from] std::str::Utf8Error),
84
85    #[error("Buffer overflow: {0} bytes exceeds maximum")]
86    BufferOverflow(usize),
87
88    #[error("Unknown model type: {0}")]
89    UnknownModel(String),
90
91    #[error("Parser configuration error: {0}")]
92    ConfigError(String),
93}
94
95/// Default maximum buffer size for reasoning parsers (4MB).
96pub const DEFAULT_MAX_BUFFER_SIZE: usize = 4 * 1024 * 1024;
97
98/// Configuration for parser behavior.
99#[derive(Debug, Clone)]
100pub struct ParserConfig {
101    /// The token that marks the start of reasoning content.
102    pub think_start_token: String,
103
104    /// The token that marks the end of reasoning content.
105    pub think_end_token: String,
106
107    /// Whether to stream reasoning content as it arrives.
108    pub stream_reasoning: bool,
109
110    /// Maximum buffer size in bytes.
111    pub max_buffer_size: usize,
112
113    /// Initial state for in_reasoning flag (fixed per parser type).
114    pub initial_in_reasoning: bool,
115}
116
117impl Default for ParserConfig {
118    fn default() -> Self {
119        Self {
120            think_start_token: "<think>".to_string(),
121            think_end_token: "</think>".to_string(),
122            stream_reasoning: true,
123            max_buffer_size: DEFAULT_MAX_BUFFER_SIZE,
124            initial_in_reasoning: false, // Default to false (explicit reasoning)
125        }
126    }
127}
128
129impl fmt::Display for ParserResult {
130    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
131        write!(
132            f,
133            "ParserResult {{ normal: {} chars, reasoning: {} chars }}",
134            self.normal_text.len(),
135            self.reasoning_text.len()
136        )
137    }
138}