use backon::ExponentialBuilder;
const TRANSIENT_RETRY_ATTEMPTS: usize = 10;
const STATUS_RETRY_PATTERNS: &[&str] = &[
"500",
"502",
"503",
"504",
"429",
"Internal Server Error",
"Bad Gateway",
"Service Unavailable",
"Gateway Timeout",
"Too Many Requests",
"rate limit",
"Rate limit",
"timed out",
"connection closed",
"connection refused",
"connection reset",
"broken pipe",
"incomplete message",
"unexpected EOF",
"request timeout",
"dns error",
"network error",
];
pub fn is_transient_error(err: &anyhow::Error) -> bool {
for cause in err.chain() {
let text = cause.to_string();
for pattern in STATUS_RETRY_PATTERNS {
if text.contains(pattern) {
return true;
}
}
}
false
}
pub fn llm_backoff() -> ExponentialBuilder {
ExponentialBuilder::default().with_max_times(TRANSIENT_RETRY_ATTEMPTS)
}
#[cfg(test)]
mod tests {
use super::*;
use backon::BackoffBuilder;
use std::time::Duration;
#[test]
fn llm_backoff_uses_backon_defaults_with_ten_attempts() {
let delays: Vec<_> = llm_backoff().build().collect();
assert_eq!(delays.len(), 10);
assert_eq!(delays[0], Duration::from_secs(1));
assert_eq!(delays[1], Duration::from_secs(2));
assert_eq!(delays[2], Duration::from_secs(4));
assert_eq!(delays[6], Duration::from_secs(60));
}
}