lellm_agent/tools/
retry.rs1use std::time::Duration;
4
5use super::ToolCallResult;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum ToolErrorKind {
10 Timeout,
11 PermissionDenied,
12 NotFound,
13 NetworkError,
14 ParseError,
15 Unknown,
16}
17
18impl ToolErrorKind {
19 pub fn is_retriable(self) -> bool {
20 matches!(self, Self::Timeout | Self::NetworkError | Self::Unknown)
21 }
22
23 pub fn max_attempts(self) -> u32 {
24 match self {
25 Self::Timeout => 5,
26 Self::NetworkError => 3,
27 Self::Unknown => 3,
28 _ => 0,
29 }
30 }
31
32 pub fn backoff_ms(self, attempt: u32) -> u64 {
33 match self {
34 Self::Timeout => (2_u64).saturating_pow(attempt + 1) * 1000,
35 Self::NetworkError | Self::Unknown => 3000,
36 _ => 0,
37 }
38 }
39
40 pub fn hint(self) -> &'static str {
41 match self {
42 Self::Timeout => "该操作超时,请检查参数或尝试更轻量的替代工具",
43 Self::PermissionDenied => "权限不足,请确认当前角色是否允许此操作",
44 Self::NotFound => "资源未找到,请检查参数拼写",
45 Self::NetworkError => "网络异常,请重试或考虑降级方案",
46 Self::ParseError => "输出格式不匹配,请严格遵循 JSON Schema",
47 Self::Unknown => "操作失败,请分析错误信息并调整策略",
48 }
49 }
50}
51
52#[derive(Debug, Clone)]
54pub enum BackoffStrategy {
55 Fixed(Duration),
56 Exponential { base: Duration, max: Duration },
57}
58
59pub struct RetryPolicy;
61
62impl RetryPolicy {
63 pub async fn execute_with_retry<F, Fut>(kind: ToolErrorKind, f: F) -> ToolCallResult
64 where
65 F: Fn() -> Fut,
66 Fut: std::future::Future<Output = ToolCallResult>,
67 {
68 let max = kind.max_attempts();
69 if max == 0 {
70 return f().await;
71 }
72
73 for attempt in 0..max {
74 let result = f().await;
75 match result {
76 ToolCallResult::Ok(_) => return result,
77 ToolCallResult::Err(msg) if attempt == max - 1 => {
78 return ToolCallResult::Err(format!(
79 "{} (retried {} times, hint: {})",
80 msg,
81 max,
82 kind.hint()
83 ));
84 }
85 ToolCallResult::Err(_) => {
86 let delay = kind.backoff_ms(attempt);
87 if delay > 0 {
88 tokio::time::sleep(Duration::from_millis(delay)).await;
89 }
90 }
91 }
92 }
93 f().await
94 }
95}