serdes_ai_agent/
errors.rs1use serdes_ai_models::ModelError;
6use serdes_ai_tools::ToolError;
7use thiserror::Error;
8
9#[derive(Debug, Error)]
11pub enum AgentRunError {
12 #[error("Model error: {0}")]
14 Model(#[from] ModelError),
15
16 #[error("Tool error: {0}")]
18 Tool(#[from] ToolError),
19
20 #[error("Output validation failed: {0}")]
22 OutputValidationFailed(#[source] OutputValidationError),
23
24 #[error("Output parsing failed: {0}")]
26 OutputParseFailed(#[source] OutputParseError),
27
28 #[error("Usage limit exceeded: {0}")]
30 UsageLimitExceeded(#[from] UsageLimitError),
31
32 #[error("Model stopped unexpectedly without output")]
34 UnexpectedStop,
35
36 #[error("No output produced")]
38 NoOutput,
39
40 #[error("Max retries exceeded: {message}")]
42 MaxRetriesExceeded {
43 message: String,
45 },
46
47 #[error("Serialization error: {0}")]
49 Serialization(#[from] serde_json::Error),
50
51 #[error("Configuration error: {0}")]
53 Configuration(String),
54
55 #[error("Agent run was cancelled")]
57 Cancelled,
58
59 #[error("Agent run timed out after {seconds}s")]
61 Timeout {
62 seconds: u64,
64 },
65
66 #[error("Provider error: {0}")]
68 Provider(String),
69
70 #[error(transparent)]
72 Other(#[from] anyhow::Error),
73}
74
75impl AgentRunError {
76 pub fn max_retries(message: impl Into<String>) -> Self {
78 Self::MaxRetriesExceeded {
79 message: message.into(),
80 }
81 }
82
83 pub fn config(message: impl Into<String>) -> Self {
85 Self::Configuration(message.into())
86 }
87
88 pub fn timeout(seconds: u64) -> Self {
90 Self::Timeout { seconds }
91 }
92
93 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#[derive(Debug, Error)]
109pub enum OutputValidationError {
110 #[error("Validation failed: {message}")]
112 ValidationFailed {
113 message: String,
115 field: Option<String>,
117 },
118
119 #[error("Custom validation failed: {0}")]
121 Custom(String),
122
123 #[error("Type mismatch: expected {expected}, got {actual}")]
125 TypeMismatch {
126 expected: String,
128 actual: String,
130 },
131
132 #[error("Missing required field: {0}")]
134 MissingField(String),
135}
136
137impl OutputValidationError {
138 pub fn failed(message: impl Into<String>) -> Self {
140 Self::ValidationFailed {
141 message: message.into(),
142 field: None,
143 }
144 }
145
146 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 pub fn custom(message: impl Into<String>) -> Self {
156 Self::Custom(message.into())
157 }
158
159 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#[derive(Debug, Error)]
182pub enum OutputParseError {
183 #[error("JSON parse error: {0}")]
185 Json(#[from] serde_json::Error),
186
187 #[error("No output found in model response")]
189 NotFound,
190
191 #[error("Output tool was not called by the model")]
193 ToolNotCalled,
194
195 #[error("Invalid output format: {0}")]
197 InvalidFormat(String),
198
199 #[error("Output does not match schema: {0}")]
201 SchemaMismatch(String),
202}
203
204impl OutputParseError {
205 pub fn invalid_format(message: impl Into<String>) -> Self {
207 Self::InvalidFormat(message.into())
208 }
209
210 pub fn schema_mismatch(message: impl Into<String>) -> Self {
212 Self::SchemaMismatch(message.into())
213 }
214}
215
216#[derive(Debug, Error)]
218pub enum UsageLimitError {
219 #[error("Request token limit exceeded: {used} > {limit}")]
221 RequestTokens {
222 used: u64,
224 limit: u64,
226 },
227
228 #[error("Response token limit exceeded: {used} > {limit}")]
230 ResponseTokens {
231 used: u64,
233 limit: u64,
235 },
236
237 #[error("Total token limit exceeded: {used} > {limit}")]
239 TotalTokens {
240 used: u64,
242 limit: u64,
244 },
245
246 #[error("Request count limit exceeded: {count} > {limit}")]
248 RequestCount {
249 count: u32,
251 limit: u32,
253 },
254
255 #[error("Tool call limit exceeded: {count} > {limit}")]
257 ToolCalls {
258 count: u32,
260 limit: u32,
262 },
263
264 #[error("Time limit exceeded: {elapsed_seconds}s > {limit_seconds}s")]
266 TimeLimit {
267 elapsed_seconds: u64,
269 limit_seconds: u64,
271 },
272}
273
274#[derive(Debug, Error)]
276pub enum AgentBuildError {
277 #[error("Missing required field: {0}")]
279 MissingField(&'static str),
280
281 #[error("Invalid configuration: {0}")]
283 InvalidConfig(String),
284
285 #[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}