1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub enum ErrorCategory {
38 Network,
40 Api,
42 Authentication,
44 Authorization,
46 RateLimit,
48 Timeout,
50 Model,
52 Tool,
54 Policy,
56 Configuration,
58 Other,
60}
61
62impl ErrorCategory {
63 pub fn is_retriable(self) -> bool {
65 matches!(
66 self,
67 ErrorCategory::Network | ErrorCategory::Timeout | ErrorCategory::RateLimit
68 )
69 }
70
71 pub fn is_authentication_error(self) -> bool {
73 matches!(
74 self,
75 ErrorCategory::Authentication | ErrorCategory::Authorization
76 )
77 }
78
79 pub fn is_client_error(self) -> bool {
81 matches!(
82 self,
83 ErrorCategory::Authentication
84 | ErrorCategory::Authorization
85 | ErrorCategory::Configuration
86 | ErrorCategory::Policy
87 )
88 }
89}
90
91#[derive(Debug, Clone, PartialEq, Eq)]
107pub struct ErrorDiagnostic {
108 pub kind: String,
110 pub message: String,
112 pub retriable: bool,
114 pub source: Option<String>,
116 pub category: ErrorCategory,
118 pub status_code: Option<u16>,
120 pub retry_after: Option<std::time::Duration>,
122}
123
124impl Default for ErrorDiagnostic {
125 fn default() -> Self {
126 Self {
127 kind: "unknown".to_string(),
128 message: String::new(),
129 retriable: false,
130 source: None,
131 category: ErrorCategory::Other,
132 status_code: None,
133 retry_after: None,
134 }
135 }
136}
137
138pub trait DiagnosticError {
140 fn diagnostic(&self) -> ErrorDiagnostic;
142
143 fn is_retriable(&self) -> bool {
145 self.diagnostic().retriable
146 }
147
148 fn category(&self) -> ErrorCategory {
150 self.diagnostic().category
151 }
152}
153
154#[derive(thiserror::Error, Debug)]
166pub enum ProviderError {
167 #[error("网络错误:{message}")]
169 Network {
170 message: String,
171 #[source]
172 source: Option<Box<dyn std::error::Error + Send + Sync>>,
173 retriable: bool,
174 },
175
176 #[error("API 错误 ({status}): {message}")]
178 Api {
179 status: u16,
180 message: String,
181 code: Option<String>,
182 },
183
184 #[error("认证失败:{message}")]
186 Authentication { message: String },
187
188 #[error("请求频率过高:{message}")]
190 RateLimit {
191 message: String,
192 retry_after: Option<std::time::Duration>,
193 },
194
195 #[error("请求超时:{message}")]
197 Timeout {
198 message: String,
199 elapsed: std::time::Duration,
200 },
201
202 #[error("模型错误:{message}")]
204 Model { message: String },
205
206 #[error("provider error: {0}")]
208 Message(String),
209}
210
211impl ProviderError {
212 pub fn network(message: impl Into<String>) -> Self {
214 Self::Network {
215 message: message.into(),
216 source: None,
217 retriable: true,
218 }
219 }
220
221 pub fn authentication(message: impl Into<String>) -> Self {
223 Self::Authentication {
224 message: message.into(),
225 }
226 }
227
228 pub fn rate_limit(
230 message: impl Into<String>,
231 retry_after: Option<std::time::Duration>,
232 ) -> Self {
233 Self::RateLimit {
234 message: message.into(),
235 retry_after,
236 }
237 }
238
239 pub fn is_retriable(&self) -> bool {
241 match self {
242 ProviderError::Network { retriable, .. } => *retriable,
243 ProviderError::RateLimit { .. } => true,
244 ProviderError::Timeout { .. } => true,
245 ProviderError::Model { .. } => true,
246 ProviderError::Api { status, .. } => {
247 *status >= 500 && *status < 600
249 }
250 _ => false,
251 }
252 }
253
254 pub fn category(&self) -> ErrorCategory {
256 match self {
257 ProviderError::Network { .. } => ErrorCategory::Network,
258 ProviderError::Api { .. } => ErrorCategory::Api,
259 ProviderError::Authentication { .. } => ErrorCategory::Authentication,
260 ProviderError::RateLimit { .. } => ErrorCategory::RateLimit,
261 ProviderError::Timeout { .. } => ErrorCategory::Timeout,
262 ProviderError::Model { .. } => ErrorCategory::Model,
263 ProviderError::Message(_) => ErrorCategory::Other,
264 }
265 }
266}
267
268impl DiagnosticError for ProviderError {
269 fn diagnostic(&self) -> ErrorDiagnostic {
270 match self {
271 ProviderError::Network {
272 message, retriable, ..
273 } => ErrorDiagnostic {
274 kind: "provider".to_string(),
275 message: message.clone(),
276 retriable: *retriable,
277 source: None,
278 category: ErrorCategory::Network,
279 status_code: None,
280 retry_after: None,
281 },
282 ProviderError::Api {
283 status,
284 message,
285 code,
286 } => ErrorDiagnostic {
287 kind: "provider".to_string(),
288 message: message.clone(),
289 retriable: *status >= 500,
290 source: code.clone(),
291 category: ErrorCategory::Api,
292 status_code: Some(*status),
293 retry_after: None,
294 },
295 ProviderError::Authentication { message } => ErrorDiagnostic {
296 kind: "provider".to_string(),
297 message: message.clone(),
298 retriable: false,
299 source: None,
300 category: ErrorCategory::Authentication,
301 status_code: None,
302 retry_after: None,
303 },
304 ProviderError::RateLimit {
305 message,
306 retry_after,
307 } => ErrorDiagnostic {
308 kind: "provider".to_string(),
309 message: message.clone(),
310 retriable: true,
311 source: None,
312 category: ErrorCategory::RateLimit,
313 status_code: Some(429),
314 retry_after: *retry_after,
315 },
316 ProviderError::Timeout {
317 message,
318 elapsed: _,
319 } => ErrorDiagnostic {
320 kind: "provider".to_string(),
321 message: message.clone(),
322 retriable: true,
323 source: None,
324 category: ErrorCategory::Timeout,
325 status_code: None,
326 retry_after: None, },
328 ProviderError::Model { message } => ErrorDiagnostic {
329 kind: "provider".to_string(),
330 message: message.clone(),
331 retriable: false, source: None,
333 category: ErrorCategory::Model,
334 status_code: None,
335 retry_after: None,
336 },
337 ProviderError::Message(msg) => ErrorDiagnostic {
338 kind: "provider".to_string(),
339 message: msg.clone(),
340 retriable: true,
341 source: None,
342 category: ErrorCategory::Other,
343 status_code: None,
344 retry_after: None,
345 },
346 }
347 }
348}
349
350#[derive(thiserror::Error, Debug)]
352pub enum ToolError {
353 #[error("tool error: {0}")]
355 Message(String),
356
357 #[error("tool policy denied (rule_id={rule_id}): {reason}")]
359 PolicyDenied { rule_id: String, reason: String },
360
361 #[error("工具不存在:{name}")]
363 NotFound { name: String },
364
365 #[error("输入验证失败:{message}")]
367 ValidationError { message: String },
368
369 #[error("工具执行超时:{message}")]
371 Timeout { message: String },
372}
373
374impl DiagnosticError for ToolError {
375 fn diagnostic(&self) -> ErrorDiagnostic {
376 match self {
377 ToolError::Message(msg) => ErrorDiagnostic {
378 kind: "tool".to_string(),
379 message: msg.clone(),
380 retriable: false,
381 source: None,
382 category: ErrorCategory::Tool,
383 status_code: None,
384 retry_after: None,
385 },
386 ToolError::PolicyDenied { rule_id, reason } => ErrorDiagnostic {
387 kind: "tool".to_string(),
388 message: format!("policy denied (rule_id={rule_id}): {reason}"),
389 retriable: false,
390 source: Some(rule_id.clone()),
391 category: ErrorCategory::Policy,
392 status_code: None,
393 retry_after: None,
394 },
395 ToolError::NotFound { name } => ErrorDiagnostic {
396 kind: "tool".to_string(),
397 message: format!("工具不存在:{name}"),
398 retriable: false,
399 source: None,
400 category: ErrorCategory::Configuration,
401 status_code: None,
402 retry_after: None,
403 },
404 ToolError::ValidationError { message } => ErrorDiagnostic {
405 kind: "tool".to_string(),
406 message: format!("输入验证失败:{message}"),
407 retriable: false,
408 source: None,
409 category: ErrorCategory::Configuration,
410 status_code: None,
411 retry_after: None,
412 },
413 ToolError::Timeout { message } => ErrorDiagnostic {
414 kind: "tool".to_string(),
415 message: format!("工具执行超时:{message}"),
416 retriable: false, source: None,
418 category: ErrorCategory::Timeout,
419 status_code: None,
420 retry_after: None,
421 },
422 }
423 }
424}
425
426#[derive(thiserror::Error, Debug)]
428pub enum SkillError {
429 #[error("skill error: {0}")]
430 Message(String),
431
432 #[error("技能不存在:{name}")]
433 NotFound { name: String },
434
435 #[error("技能执行超时:{message}")]
436 Timeout { message: String },
437}
438
439impl DiagnosticError for SkillError {
440 fn diagnostic(&self) -> ErrorDiagnostic {
441 match self {
442 SkillError::Message(msg) => ErrorDiagnostic {
443 kind: "skill".to_string(),
444 message: msg.clone(),
445 retriable: false,
446 source: None,
447 category: ErrorCategory::Other,
448 status_code: None,
449 retry_after: None,
450 },
451 SkillError::NotFound { name } => ErrorDiagnostic {
452 kind: "skill".to_string(),
453 message: format!("技能不存在:{name}"),
454 retriable: false,
455 source: None,
456 category: ErrorCategory::Configuration,
457 status_code: None,
458 retry_after: None,
459 },
460 SkillError::Timeout { message } => ErrorDiagnostic {
461 kind: "skill".to_string(),
462 message: format!("技能执行超时:{message}"),
463 retriable: true,
464 source: None,
465 category: ErrorCategory::Timeout,
466 status_code: None,
467 retry_after: None,
468 },
469 }
470 }
471}
472
473#[derive(thiserror::Error, Debug)]
475pub enum AgentError {
476 #[error("agent error: {0}")]
478 Message(String),
479
480 #[error("超过最大步数限制:{max_steps}")]
482 MaxStepsExceeded { max_steps: usize },
483
484 #[error("Provider 错误:{source}")]
486 ProviderError {
487 #[source]
488 source: ProviderError,
489 },
490
491 #[error("此决策需要 Runtime 支持,请使用 Runtime 模式运行")]
493 RequiresRuntime,
494}
495
496impl DiagnosticError for AgentError {
497 fn diagnostic(&self) -> ErrorDiagnostic {
498 match self {
499 AgentError::Message(msg) => ErrorDiagnostic {
500 kind: "runtime".to_string(),
501 message: msg.clone(),
502 retriable: false,
503 source: None,
504 category: ErrorCategory::Other,
505 status_code: None,
506 retry_after: None,
507 },
508 AgentError::MaxStepsExceeded { max_steps } => ErrorDiagnostic {
509 kind: "runtime".to_string(),
510 message: format!("超过最大步数限制:{max_steps}"),
511 retriable: false,
512 source: None,
513 category: ErrorCategory::Configuration,
514 status_code: None,
515 retry_after: None,
516 },
517 AgentError::ProviderError { source } => {
518 let mut diag = source.diagnostic();
519 diag.kind = "runtime".to_string();
520 diag
521 }
522 AgentError::RequiresRuntime => ErrorDiagnostic {
523 kind: "runtime".to_string(),
524 message: "此决策需要 Runtime 支持,请使用 Runtime 模式运行".to_string(),
525 retriable: false,
526 source: None,
527 category: ErrorCategory::Configuration,
528 status_code: None,
529 retry_after: None,
530 },
531 }
532 }
533}
534
535#[derive(thiserror::Error, Debug)]
537pub enum MemoryError {
538 #[error("memory error: {0}")]
539 Message(String),
540
541 #[error("记忆不存在:{id}")]
542 NotFound { id: String },
543}
544
545impl DiagnosticError for MemoryError {
546 fn diagnostic(&self) -> ErrorDiagnostic {
547 match self {
548 MemoryError::Message(msg) => ErrorDiagnostic {
549 kind: "memory".to_string(),
550 message: msg.clone(),
551 retriable: false,
552 source: None,
553 category: ErrorCategory::Other,
554 status_code: None,
555 retry_after: None,
556 },
557 MemoryError::NotFound { id } => ErrorDiagnostic {
558 kind: "memory".to_string(),
559 message: format!("记忆不存在:{id}"),
560 retriable: false,
561 source: None,
562 category: ErrorCategory::Configuration,
563 status_code: None,
564 retry_after: None,
565 },
566 }
567 }
568}
569
570#[derive(thiserror::Error, Debug)]
572pub enum ChannelError {
573 #[error("channel error: {0}")]
574 Message(String),
575}
576
577impl DiagnosticError for ChannelError {
578 fn diagnostic(&self) -> ErrorDiagnostic {
579 match self {
580 ChannelError::Message(msg) => ErrorDiagnostic {
581 kind: "channel".to_string(),
582 message: msg.clone(),
583 retriable: false,
584 source: None,
585 category: ErrorCategory::Other,
586 status_code: None,
587 retry_after: None,
588 },
589 }
590 }
591}
592
593#[cfg(test)]
594mod tests {
595 use super::*;
596
597 #[test]
598 fn test_provider_error_retriable() {
599 let network = ProviderError::network("连接失败");
600 assert!(network.is_retriable());
601 assert_eq!(network.category(), ErrorCategory::Network);
602
603 let auth = ProviderError::authentication("API Key 无效");
604 assert!(!auth.is_retriable());
605 assert_eq!(auth.category(), ErrorCategory::Authentication);
606
607 let rate_limit =
608 ProviderError::rate_limit("限流", Some(std::time::Duration::from_secs(60)));
609 assert!(rate_limit.is_retriable());
610 assert_eq!(rate_limit.category(), ErrorCategory::RateLimit);
611 }
612
613 #[test]
614 fn test_error_category() {
615 assert!(ErrorCategory::Network.is_retriable());
616 assert!(ErrorCategory::Timeout.is_retriable());
617 assert!(ErrorCategory::RateLimit.is_retriable());
618 assert!(!ErrorCategory::Authentication.is_retriable());
619 assert!(!ErrorCategory::Policy.is_retriable());
620 }
621
622 #[test]
623 fn test_diagnostic() {
624 let error = ProviderError::Api {
625 status: 503,
626 message: "服务不可用".to_string(),
627 code: None,
628 };
629
630 let diag = error.diagnostic();
631 assert_eq!(diag.kind, "provider");
632 assert_eq!(diag.status_code, Some(503));
633 assert!(diag.retriable);
634 assert_eq!(diag.category, ErrorCategory::Api);
635 }
636}