| name | sage-recovery-patterns |
| description | Sage 独有的恢复模式设计,包含熔断器、限流器、重试策略的最佳实践 |
| when_to_use | 当需要实现容错机制、处理 LLM API 故障、或设计弹性系统时使用 |
| allowed_tools | ["Read","Grep","Glob","Edit","Write"] |
| user_invocable | true |
| priority | 80 |
Sage 恢复模式指南
概述
Sage 的 recovery/ 模块是独有的竞争优势,提供生产级容错能力:
- 熔断器 (Circuit Breaker): 防止级联故障
- 限流器 (Rate Limiter): 控制 API 调用频率
- 重试策略 (Retry Policy): 智能重试失败操作
- 任务监督 (Supervisor): 管理长时间运行任务
熔断器模式
状态机
成功调用
┌────────┐
│ ▼
┌───────┐ 失败次数 < 阈值 ┌───────┐
│Closed │ ─────────────────▶│Closed │
└───────┘ └───┬───┘
│ │
│ 失败次数 >= 阈值 │
▼ │
┌───────┐ │
│ Open │ ◀────────────────────┘
└───┬───┘
│ 超时后
▼
┌──────────┐
│Half-Open │
└────┬─────┘
│
├── 成功 ──▶ Closed
│
└── 失败 ──▶ Open
配置
use sage_core::recovery::{CircuitBreaker, CircuitBreakerConfig};
let config = CircuitBreakerConfig {
failure_threshold: 5,
recovery_timeout: Duration::from_secs(30),
half_open_max_calls: 3,
success_rate_threshold: 0.5,
window_size: 10,
};
let breaker = CircuitBreaker::new("llm-api", config);
使用
let result = breaker.call(|| async {
llm_client.chat(request).await
}).await;
match result {
Ok(response) => handle_success(response),
Err(CircuitBreakerError::Open) => {
return fallback_response();
}
Err(CircuitBreakerError::Rejected(e)) => {
return Err(e);
}
Err(CircuitBreakerError::Failed(e)) => {
return Err(e);
}
}
if breaker.is_open() {
log::warn!("Circuit breaker is open, using fallback");
return fallback();
}
与 LLM 提供者集成
pub struct ResilientLlmClient {
client: Box<dyn LlmClient>,
breaker: CircuitBreaker,
}
impl ResilientLlmClient {
pub async fn chat(&self, request: ChatRequest) -> Result<LlmResponse> {
self.breaker.call(|| async {
self.client.chat(request.clone()).await
}).await
.map_err(|e| match e {
CircuitBreakerError::Open => {
LlmError::ServiceUnavailable("Circuit breaker open".into())
}
CircuitBreakerError::Failed(inner) => inner,
_ => LlmError::Unknown(e.to_string()),
})
}
}
限流器模式
滑动窗口限流
use sage_core::recovery::SlidingWindowRateLimiter;
let limiter = SlidingWindowRateLimiter::new(
100,
Duration::from_secs(60),
);
limiter.acquire().await?;
if limiter.try_acquire() {
} else {
return Err(RateLimitError::TooManyRequests);
}
令牌桶限流
use sage_core::recovery::TokenBucketRateLimiter;
let limiter = TokenBucketRateLimiter::new(
10,
Duration::from_millis(100),
);
limiter.acquire_n(5).await?;
按提供者限流
use sage_core::recovery::RateLimiterRegistry;
let registry = RateLimiterRegistry::new();
registry.register("anthropic", SlidingWindowRateLimiter::new(60, Duration::from_secs(60)));
registry.register("openai", SlidingWindowRateLimiter::new(100, Duration::from_secs(60)));
registry.register("google", SlidingWindowRateLimiter::new(30, Duration::from_secs(60)));
let limiter = registry.get("anthropic")?;
limiter.acquire().await?;
重试策略
指数退避
use sage_core::recovery::{RetryPolicy, BackoffConfig};
let policy = RetryPolicy::exponential(BackoffConfig {
initial_delay: Duration::from_millis(100),
max_delay: Duration::from_secs(30),
multiplier: 2.0,
max_retries: 5,
jitter: true,
});
let result = policy.retry(|| async {
llm_client.chat(request.clone()).await
}).await?;
错误分类重试
use sage_core::recovery::{RetryPolicy, ErrorClass};
let policy = RetryPolicy::with_classifier(|error: &LlmError| {
match error {
LlmError::RateLimit(_) => ErrorClass::Transient,
LlmError::Timeout(_) => ErrorClass::Transient,
LlmError::ServerError(_) => ErrorClass::Transient,
LlmError::InvalidRequest(_) => ErrorClass::Permanent,
LlmError::AuthError(_) => ErrorClass::Permanent,
_ => ErrorClass::Unknown,
}
});
组合策略
pub async fn resilient_call<F, T, E>(
breaker: &CircuitBreaker,
limiter: &RateLimiter,
retry_policy: &RetryPolicy,
f: F,
) -> Result<T, E>
where
F: Fn() -> Future<Output = Result<T, E>>,
E: Into<RecoverableError>,
{
if breaker.is_open() {
return Err(Error::CircuitOpen);
}
limiter.acquire().await?;
let result = retry_policy.retry(|| async {
breaker.call(|| f()).await
}).await;
result
}
任务监督
监督策略
use sage_core::recovery::{TaskSupervisor, SupervisionPolicy};
let supervisor = TaskSupervisor::new(SupervisionPolicy {
max_restarts: 3,
restart_window: Duration::from_secs(60),
restart_delay: BackoffConfig::exponential(
Duration::from_secs(1),
Duration::from_secs(30),
),
on_max_restarts: SupervisionAction::Stop,
});
监督长时间任务
supervisor.supervise("agent-execution", || async {
agent.execute(task).await
}).await?;
最佳实践
1. 分层恢复
let retry = RetryPolicy::exponential(config);
let breaker = CircuitBreaker::new(config);
let limiter = RateLimiter::new(config);
limiter.acquire().await?;
breaker.call(|| retry.retry(|| api_call())).await?;
2. 监控指标
telemetry.gauge("circuit_breaker.state", breaker.state() as i64);
telemetry.counter("circuit_breaker.failures", breaker.failure_count());
telemetry.counter("rate_limiter.rejected", limiter.rejected_count());
telemetry.histogram("rate_limiter.wait_time", limiter.avg_wait_time());
telemetry.histogram("retry.attempts", retry_count);
telemetry.counter("retry.exhausted", exhausted_count);
3. 降级策略
async fn with_fallback<T>(
primary: impl Future<Output = Result<T>>,
fallback: impl Future<Output = Result<T>>,
) -> Result<T> {
match primary.await {
Ok(result) => Ok(result),
Err(e) if is_recoverable(&e) => {
log::warn!("Primary failed, using fallback: {}", e);
fallback.await
}
Err(e) => Err(e),
}
}
let response = with_fallback(
claude_client.chat(request.clone()),
openai_client.chat(request.clone()),
).await?;
4. 健康检查
pub struct HealthChecker {
breakers: HashMap<String, CircuitBreaker>,
}
impl HealthChecker {
pub fn is_healthy(&self) -> bool {
self.breakers.values().all(|b| !b.is_open())
}
pub fn health_report(&self) -> HealthReport {
HealthReport {
services: self.breakers.iter().map(|(name, breaker)| {
(name.clone(), ServiceHealth {
state: breaker.state(),
failure_rate: breaker.failure_rate(),
last_failure: breaker.last_failure_time(),
})
}).collect(),
}
}
}
配置建议
LLM API 熔断器
CircuitBreakerConfig {
failure_threshold: 5,
recovery_timeout: 30.seconds(),
success_rate_threshold: 0.5,
window_size: 10,
}
LLM API 限流器
SlidingWindowRateLimiter::new(60, 60.seconds())
SlidingWindowRateLimiter::new(100, 60.seconds())
SlidingWindowRateLimiter::new(30, 60.seconds())
重试策略
RetryPolicy::exponential(BackoffConfig {
initial_delay: 100.milliseconds(),
max_delay: 30.seconds(),
multiplier: 2.0,
max_retries: 5,
jitter: true,
})