Skip to main content

serdes_ai_agent/
errors.rs

1//! Agent-specific error types.
2//!
3//! This module defines all errors that can occur during agent execution.
4
5use serdes_ai_models::ModelError;
6use serdes_ai_tools::ToolError;
7use thiserror::Error;
8
9/// Errors that can occur during agent run execution.
10#[derive(Debug, Error)]
11pub enum AgentRunError {
12    /// Model returned an error.
13    #[error("Model error: {0}")]
14    Model(#[from] ModelError),
15
16    /// Tool execution failed.
17    #[error("Tool error: {0}")]
18    Tool(#[from] ToolError),
19
20    /// Output validation failed after retries.
21    #[error("Output validation failed: {0}")]
22    OutputValidationFailed(#[source] OutputValidationError),
23
24    /// Failed to parse model output.
25    #[error("Output parsing failed: {0}")]
26    OutputParseFailed(#[source] OutputParseError),
27
28    /// Usage limit was exceeded.
29    #[error("Usage limit exceeded: {0}")]
30    UsageLimitExceeded(#[from] UsageLimitError),
31
32    /// Model stopped without producing output.
33    #[error("Model stopped unexpectedly without output")]
34    UnexpectedStop,
35
36    /// No output was produced after all steps.
37    #[error("No output produced")]
38    NoOutput,
39
40    /// Maximum retries exceeded.
41    #[error("Max retries exceeded: {message}")]
42    MaxRetriesExceeded {
43        /// Description of what was being retried.
44        message: String,
45    },
46
47    /// Serialization/deserialization error.
48    #[error("Serialization error: {0}")]
49    Serialization(#[from] serde_json::Error),
50
51    /// Configuration error.
52    #[error("Configuration error: {0}")]
53    Configuration(String),
54
55    /// Agent was cancelled.
56    #[error("Agent run was cancelled")]
57    Cancelled,
58
59    /// Timeout occurred.
60    #[error("Agent run timed out after {seconds}s")]
61    Timeout {
62        /// Timeout in seconds.
63        seconds: u64,
64    },
65
66    /// Provider error.
67    #[error("Provider error: {0}")]
68    Provider(String),
69
70    /// Other error.
71    #[error(transparent)]
72    Other(#[from] anyhow::Error),
73}
74
75impl AgentRunError {
76    /// Create a max retries error.
77    pub fn max_retries(message: impl Into<String>) -> Self {
78        Self::MaxRetriesExceeded {
79            message: message.into(),
80        }
81    }
82
83    /// Create a configuration error.
84    pub fn config(message: impl Into<String>) -> Self {
85        Self::Configuration(message.into())
86    }
87
88    /// Create a timeout error.
89    pub fn timeout(seconds: u64) -> Self {
90        Self::Timeout { seconds }
91    }
92
93    /// Check if this error is retryable.
94    pub fn is_retryable(&self) -> bool {
95        match self {
96            Self::Model(e) => e.is_retryable(),
97            Self::Tool(e) => e.is_retryable(),
98            Self::UsageLimitExceeded(_) => false,
99            Self::Cancelled => false,
100            Self::Timeout { .. } => false,
101            Self::MaxRetriesExceeded { .. } => false,
102            _ => true,
103        }
104    }
105}
106
107/// Output validation error.
108#[derive(Debug, Error)]
109pub enum OutputValidationError {
110    /// Validation rule failed.
111    #[error("Validation failed: {message}")]
112    ValidationFailed {
113        /// Error message.
114        message: String,
115        /// Field that failed (if applicable).
116        field: Option<String>,
117    },
118
119    /// Custom validator failed.
120    #[error("Custom validation failed: {0}")]
121    Custom(String),
122
123    /// Type mismatch.
124    #[error("Type mismatch: expected {expected}, got {actual}")]
125    TypeMismatch {
126        /// Expected type.
127        expected: String,
128        /// Actual type.
129        actual: String,
130    },
131
132    /// Missing required field.
133    #[error("Missing required field: {0}")]
134    MissingField(String),
135}
136
137impl OutputValidationError {
138    /// Create a validation failed error.
139    pub fn failed(message: impl Into<String>) -> Self {
140        Self::ValidationFailed {
141            message: message.into(),
142            field: None,
143        }
144    }
145
146    /// Create a validation failed error for a specific field.
147    pub fn field_failed(field: impl Into<String>, message: impl Into<String>) -> Self {
148        Self::ValidationFailed {
149            message: message.into(),
150            field: Some(field.into()),
151        }
152    }
153
154    /// Create a custom validation error.
155    pub fn custom(message: impl Into<String>) -> Self {
156        Self::Custom(message.into())
157    }
158
159    /// Get the error message for retry prompts.
160    pub fn retry_message(&self) -> String {
161        match self {
162            Self::ValidationFailed { message, field } => {
163                if let Some(f) = field {
164                    format!("Validation failed for field '{}': {}", f, message)
165                } else {
166                    format!("Validation failed: {}", message)
167                }
168            }
169            Self::Custom(msg) => msg.clone(),
170            Self::TypeMismatch { expected, actual } => {
171                format!("Type mismatch: expected {}, got {}", expected, actual)
172            }
173            Self::MissingField(field) => {
174                format!("Missing required field: {}", field)
175            }
176        }
177    }
178}
179
180/// Output parsing error.
181#[derive(Debug, Error)]
182pub enum OutputParseError {
183    /// JSON parsing failed.
184    #[error("JSON parse error: {0}")]
185    Json(#[from] serde_json::Error),
186
187    /// No output found in response.
188    #[error("No output found in model response")]
189    NotFound,
190
191    /// Output tool not called.
192    #[error("Output tool was not called by the model")]
193    ToolNotCalled,
194
195    /// Invalid format.
196    #[error("Invalid output format: {0}")]
197    InvalidFormat(String),
198
199    /// Schema mismatch.
200    #[error("Output does not match schema: {0}")]
201    SchemaMismatch(String),
202}
203
204impl OutputParseError {
205    /// Create an invalid format error.
206    pub fn invalid_format(message: impl Into<String>) -> Self {
207        Self::InvalidFormat(message.into())
208    }
209
210    /// Create a schema mismatch error.
211    pub fn schema_mismatch(message: impl Into<String>) -> Self {
212        Self::SchemaMismatch(message.into())
213    }
214}
215
216/// Usage limit error.
217#[derive(Debug, Error)]
218pub enum UsageLimitError {
219    /// Request token limit exceeded.
220    #[error("Request token limit exceeded: {used} > {limit}")]
221    RequestTokens {
222        /// Tokens used.
223        used: u64,
224        /// Token limit.
225        limit: u64,
226    },
227
228    /// Response token limit exceeded.
229    #[error("Response token limit exceeded: {used} > {limit}")]
230    ResponseTokens {
231        /// Tokens used.
232        used: u64,
233        /// Token limit.
234        limit: u64,
235    },
236
237    /// Total token limit exceeded.
238    #[error("Total token limit exceeded: {used} > {limit}")]
239    TotalTokens {
240        /// Tokens used.
241        used: u64,
242        /// Token limit.
243        limit: u64,
244    },
245
246    /// Request count limit exceeded.
247    #[error("Request count limit exceeded: {count} > {limit}")]
248    RequestCount {
249        /// Number of requests.
250        count: u32,
251        /// Request limit.
252        limit: u32,
253    },
254
255    /// Tool call limit exceeded.
256    #[error("Tool call limit exceeded: {count} > {limit}")]
257    ToolCalls {
258        /// Number of tool calls.
259        count: u32,
260        /// Tool call limit.
261        limit: u32,
262    },
263
264    /// Time limit exceeded.
265    #[error("Time limit exceeded: {elapsed_seconds}s > {limit_seconds}s")]
266    TimeLimit {
267        /// Elapsed time in seconds.
268        elapsed_seconds: u64,
269        /// Time limit in seconds.
270        limit_seconds: u64,
271    },
272}
273
274/// Agent build error.
275#[derive(Debug, Error)]
276pub enum AgentBuildError {
277    /// Missing required field.
278    #[error("Missing required field: {0}")]
279    MissingField(&'static str),
280
281    /// Invalid configuration.
282    #[error("Invalid configuration: {0}")]
283    InvalidConfig(String),
284
285    /// Tool registration error.
286    #[error("Tool registration error: {0}")]
287    ToolRegistration(String),
288}
289
290#[cfg(test)]
291mod tests {
292    use super::*;
293
294    #[test]
295    fn test_agent_run_error_display() {
296        let err = AgentRunError::NoOutput;
297        assert_eq!(err.to_string(), "No output produced");
298
299        let err = AgentRunError::max_retries("tool call");
300        assert!(err.to_string().contains("tool call"));
301    }
302
303    #[test]
304    fn test_output_validation_error() {
305        let err = OutputValidationError::failed("invalid value");
306        assert!(err.retry_message().contains("invalid value"));
307
308        let err = OutputValidationError::field_failed("name", "too short");
309        assert!(err.retry_message().contains("name"));
310        assert!(err.retry_message().contains("too short"));
311    }
312
313    #[test]
314    fn test_usage_limit_error() {
315        let err = UsageLimitError::TotalTokens {
316            used: 1000,
317            limit: 500,
318        };
319        assert!(err.to_string().contains("1000"));
320        assert!(err.to_string().contains("500"));
321    }
322
323    #[test]
324    fn test_is_retryable() {
325        assert!(!AgentRunError::Cancelled.is_retryable());
326        assert!(!AgentRunError::timeout(60).is_retryable());
327        assert!(AgentRunError::NoOutput.is_retryable());
328    }
329}