use siumai::models;
use siumai::prelude::*;
use std::time::Duration;
use tokio::time::sleep;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("🛡️ Error Handling - Production-ready error management\n");
demonstrate_error_types().await;
demonstrate_retry_strategies().await;
demonstrate_rate_limit_handling().await;
demonstrate_graceful_degradation().await;
demonstrate_error_classification().await;
println!("\n✅ Error handling examples completed!");
Ok(())
}
async fn demonstrate_error_types() {
println!("🔍 Error Types and Classification:\n");
println!(" Testing Invalid API Key:");
match test_invalid_api_key().await {
Ok(_) => println!(" ❌ Expected error but got success"),
Err(e) => {
println!(" ✅ Got expected error: {e}");
demonstrate_error_handling(&e);
}
}
println!("\n Testing Invalid Model:");
match test_invalid_model().await {
Ok(_) => println!(" ❌ Expected error but got success"),
Err(e) => {
println!(" ✅ Got expected error: {e}");
demonstrate_error_handling(&e);
}
}
println!("\n Testing Network Timeout:");
match test_network_timeout().await {
Ok(_) => println!(" ❌ Expected error but got success"),
Err(e) => {
println!(" ✅ Got expected error: {e}");
demonstrate_error_handling(&e);
}
}
println!("\n Testing Rate Limit:");
match test_rate_limit().await {
Ok(_) => println!(" ❌ Expected error but got success"),
Err(e) => {
println!(" ✅ Got expected error: {e}");
demonstrate_error_handling(&e);
}
}
println!("\n Testing Invalid Request:");
match test_invalid_request().await {
Ok(_) => println!(" ❌ Expected error but got success"),
Err(e) => {
println!(" ✅ Got expected error: {e}");
demonstrate_error_handling(&e);
}
}
}
async fn demonstrate_retry_strategies() {
println!("🔄 Retry Strategies:\n");
let message = "Hello! This is a test message.";
println!(" Strategy 1: Exponential Backoff");
match retry_with_exponential_backoff(message, 3).await {
Ok(response) => {
println!(" ✅ Success after retries");
if let Some(text) = response.content_text() {
println!(" Response: {}", &text[..text.len().min(50)]);
}
}
Err(e) => {
println!(" ❌ Failed after all retries: {e}");
}
}
println!("\n Strategy 2: Retry with Jitter");
match retry_with_jitter(message, 3).await {
Ok(response) => {
println!(" ✅ Success with jitter strategy");
if let Some(text) = response.content_text() {
println!(" Response: {}", &text[..text.len().min(50)]);
}
}
Err(e) => {
println!(" ❌ Failed with jitter strategy: {e}");
}
}
println!();
}
async fn demonstrate_rate_limit_handling() {
println!("⏱️ Rate Limit Handling:\n");
println!(" Testing rate limit detection and handling...");
match handle_rate_limits("Test rate limit handling").await {
Ok(response) => {
println!(" ✅ Successfully handled rate limits");
if let Some(text) = response.content_text() {
println!(" Response: {}", &text[..text.len().min(100)]);
}
}
Err(e) => {
println!(" ❌ Rate limit handling failed: {e}");
}
}
println!();
}
async fn demonstrate_graceful_degradation() {
println!("🎭 Graceful Degradation:\n");
let user_message = "Explain quantum computing";
match chat_with_graceful_degradation(user_message).await {
Ok((provider, response)) => {
println!(" ✅ Successfully used provider: {provider}");
if let Some(text) = response.content_text() {
println!(" Response: {}", &text[..text.len().min(100)]);
}
}
Err(e) => {
println!(" ❌ All degradation strategies failed: {e}");
println!(" 💡 In production, you might return a cached response or error message");
}
}
println!();
}
async fn demonstrate_error_classification() {
println!("📊 Error Classification for Monitoring:\n");
let test_errors = vec![
LlmError::AuthenticationError("Invalid API key".to_string()),
LlmError::RateLimitError("Rate limit exceeded".to_string()),
LlmError::TimeoutError("Request timed out".to_string()),
LlmError::ModelNotSupported("gpt-5".to_string()),
LlmError::InternalError("Network error".to_string()),
];
for error in test_errors {
println!(" Error: {error}");
let classification = classify_error_for_monitoring(&error);
println!(" Classification: {classification:?}");
println!(" Action: {}", get_recommended_action(&classification));
println!();
}
}
async fn test_invalid_api_key() -> Result<ChatResponse, LlmError> {
let client = LlmBuilder::new()
.openai()
.api_key("invalid-key-12345")
.model(models::openai::GPT_4O_MINI)
.build()
.await?;
let messages = vec![user!("Hello")];
client.chat(messages).await
}
async fn test_invalid_model() -> Result<ChatResponse, LlmError> {
if let Ok(api_key) = std::env::var("OPENAI_API_KEY") {
let client = LlmBuilder::new()
.openai()
.api_key(&api_key)
.model("gpt-nonexistent-model")
.build()
.await?;
let messages = vec![user!("Hello")];
client.chat(messages).await
} else {
Err(LlmError::AuthenticationError("No API key".to_string()))
}
}
async fn test_network_timeout() -> Result<ChatResponse, LlmError> {
Err(LlmError::TimeoutError("Simulated timeout".to_string()))
}
async fn test_rate_limit() -> Result<ChatResponse, LlmError> {
Err(LlmError::RateLimitError("Rate limit exceeded".to_string()))
}
async fn test_invalid_request() -> Result<ChatResponse, LlmError> {
Err(LlmError::InternalError(
"Invalid request format".to_string(),
))
}
async fn retry_with_exponential_backoff(
message: &str,
max_retries: u32,
) -> Result<ChatResponse, LlmError> {
let mut delay = Duration::from_millis(100);
for attempt in 1..=max_retries {
match try_chat_request(message).await {
Ok(response) => {
println!(" ✅ Success on attempt {attempt}");
return Ok(response);
}
Err(e) if is_retryable_error(&e) && attempt < max_retries => {
println!(" ⏳ Attempt {attempt} failed, retrying in {delay:?}");
sleep(delay).await;
delay *= 2; }
Err(e) => {
println!(" ❌ Non-retryable error or max retries reached: {e}");
return Err(e);
}
}
}
Err(LlmError::InternalError("Max retries exceeded".to_string()))
}
async fn retry_with_jitter(message: &str, max_retries: u32) -> Result<ChatResponse, LlmError> {
use rand::Rng;
let mut rng = rand::thread_rng();
for attempt in 1..=max_retries {
match try_chat_request(message).await {
Ok(response) => {
println!(" ✅ Success on attempt {attempt} with jitter");
return Ok(response);
}
Err(e) if is_retryable_error(&e) && attempt < max_retries => {
let base_delay = 100 * (1 << (attempt - 1)); let jitter = rng.gen_range(0..=base_delay / 2); let delay = Duration::from_millis(base_delay + jitter);
println!(" ⏳ Attempt {attempt} failed, retrying in {delay:?} (with jitter)");
sleep(delay).await;
}
Err(e) => {
return Err(e);
}
}
}
Err(LlmError::InternalError("Max retries exceeded".to_string()))
}
async fn handle_rate_limits(message: &str) -> Result<ChatResponse, LlmError> {
match try_chat_request(message).await {
Ok(response) => Ok(response),
Err(LlmError::RateLimitError(_)) => {
println!(" ⏳ Rate limit detected, waiting 60 seconds...");
sleep(Duration::from_secs(60)).await;
try_chat_request(message).await
}
Err(e) => Err(e),
}
}
async fn chat_with_graceful_degradation(message: &str) -> Result<(String, ChatResponse), LlmError> {
if let Ok(response) = try_chat_request(message).await {
return Ok(("Primary".to_string(), response));
}
if let Ok(client) = LlmBuilder::new()
.ollama()
.base_url("http://localhost:11434")
.model("llama3.2")
.build()
.await
{
let messages = vec![user!(message)];
if let Ok(response) = client.chat(messages).await {
return Ok(("Ollama Fallback".to_string(), response));
}
}
Err(LlmError::InternalError(
"All providers unavailable".to_string(),
))
}
async fn try_chat_request(message: &str) -> Result<ChatResponse, LlmError> {
if let Ok(api_key) = std::env::var("OPENAI_API_KEY") {
let client = LlmBuilder::new()
.openai()
.api_key(&api_key)
.model(models::openai::GPT_4O_MINI)
.build()
.await?;
let messages = vec![user!(message)];
client.chat(messages).await
} else {
Err(LlmError::AuthenticationError(
"No API key available".to_string(),
))
}
}
const fn is_retryable_error(error: &LlmError) -> bool {
matches!(
error,
LlmError::TimeoutError(_) | LlmError::RateLimitError(_) | LlmError::InternalError(_)
)
}
const fn is_auth_error(error: &LlmError) -> bool {
matches!(error, LlmError::AuthenticationError(_))
}
const fn is_rate_limit_error(error: &LlmError) -> bool {
matches!(error, LlmError::RateLimitError(_))
}
const fn is_client_error(error: &LlmError) -> bool {
matches!(
error,
LlmError::AuthenticationError(_) | LlmError::ModelNotSupported(_)
)
}
#[derive(Debug)]
enum ErrorClassification {
Transient, Authentication, RateLimit, ClientError, ServerError, }
const fn classify_error_for_monitoring(error: &LlmError) -> ErrorClassification {
match error {
LlmError::AuthenticationError(_) => ErrorClassification::Authentication,
LlmError::RateLimitError(_) => ErrorClassification::RateLimit,
LlmError::TimeoutError(_) => ErrorClassification::Transient,
LlmError::ModelNotSupported(_) => ErrorClassification::ClientError,
LlmError::InternalError(_) => ErrorClassification::ServerError,
_ => ErrorClassification::ServerError, }
}
const fn get_recommended_action(classification: &ErrorClassification) -> &'static str {
match classification {
ErrorClassification::Transient => "Retry with exponential backoff",
ErrorClassification::Authentication => "Check API credentials",
ErrorClassification::RateLimit => "Implement rate limiting and backoff",
ErrorClassification::ClientError => "Fix request parameters",
ErrorClassification::ServerError => "Monitor and escalate if persistent",
}
}
fn demonstrate_error_handling(error: &LlmError) {
println!(" 📊 Error Analysis:");
println!(" - Retryable: {}", is_retryable_error(error));
println!(" - Auth error: {}", is_auth_error(error));
println!(" - Rate limit: {}", is_rate_limit_error(error));
println!(" - Client error: {}", is_client_error(error));
let classification = classify_error_for_monitoring(error);
println!(" - Classification: {classification:?}");
println!(
" - Recommended action: {}",
get_recommended_action(&classification)
);
}