Skip to main content

lellm_agent/runtime/
fallback.rs

1//! 降级策略 — Provider 错误时的恢复决策。
2//!
3//! v0.1 范围:仅处理 `LlmError`(Provider 调用失败),支持 `Retry` / `Abort`。
4//! v0.2 扩展:`ToolError`、`LoopDetected`、`MaxIterationsReached` 等触发条件。
5
6use std::sync::Arc;
7
8use async_trait::async_trait;
9use lellm_core::{LlmError, Message};
10
11/// Fallback 上下文 — 提供给策略做决策的依据。
12///
13/// **设计原则:**
14/// - Runtime 只报告事实(第几次失败、什么错误),策略自己决定是否继续
15/// - `error` 为借用 — Context 是**观察窗口**,不成为错误的临时仓库
16/// - 错误所有权始终留在 Retry Loop 手中,Abort 时直接 `err.clone()` 返回
17pub struct FallbackContext<'a> {
18    /// Provider 调用失败的具体错误(借用,Context 只观察)
19    pub error: &'a LlmError,
20    /// 当前失败次数(从 1 开始,首次失败为 1)
21    pub attempt: usize,
22    /// Agent Loop 已完成的迭代轮次
23    pub iterations: usize,
24    /// 当前对话历史(不可变快照)
25    pub conversation: Arc<[Message]>,
26}
27
28/// Fallback 动作 — 策略返回的决策结果。
29///
30/// v0.1 仅使用 `Retry` / `Abort`。
31#[derive(Debug, Clone)]
32pub enum FallbackAction {
33    /// 重试同一请求(Runtime 重新调用 provider)
34    Retry,
35    /// 终止并返回错误
36    Abort,
37    // TODO(v0.2): 注入干预消息后重试
38    // RetryWithMessages(Vec<Message>),
39    // TODO(v0.2): 切换到备选 provider
40    // SwitchProvider(String),
41    // TODO(v0.2): 策略直接给出最终响应
42    // Complete(ChatResponse),
43}
44
45/// Fallback 策略 trait — 可注入的恢复决策点。
46#[async_trait]
47pub trait FallbackStrategy: Send + Sync {
48    /// 处理降级信号,返回决策动作。
49    async fn handle(&self, ctx: &FallbackContext) -> FallbackAction;
50}
51
52/// 默认 fallback 策略 — 可重试错误自动重试,其余直接终止。
53pub struct DefaultFallback {
54    max_retries: usize,
55}
56
57impl DefaultFallback {
58    pub fn new(max_retries: usize) -> Self {
59        Self { max_retries }
60    }
61
62    /// 判断 LlmError 是否可重试
63    fn is_retriable(error: &LlmError) -> bool {
64        match error {
65            LlmError::Timeout { .. } | LlmError::Network { .. } => true,
66            LlmError::Provider {
67                status: Some(s), ..
68            } => *s >= 500,
69            _ => false,
70        }
71    }
72}
73
74impl Default for DefaultFallback {
75    fn default() -> Self {
76        Self::new(3)
77    }
78}
79
80#[async_trait]
81impl FallbackStrategy for DefaultFallback {
82    async fn handle(&self, ctx: &FallbackContext) -> FallbackAction {
83        if Self::is_retriable(ctx.error) && ctx.attempt < self.max_retries {
84            FallbackAction::Retry
85        } else {
86            FallbackAction::Abort
87        }
88    }
89}