use multi_llm::{
error::ErrorCategory, DefaultLLMParams, LLMConfig, LlmError, LlmProvider, OpenAIConfig,
UnifiedLLMClient, UnifiedLLMRequest, UnifiedMessage,
};
fn demonstrate_error_types() {
println!("=== Error Types and Categories ===\n");
let config_error = LlmError::configuration_error("Missing API key");
print_error_info("ConfigurationError", &config_error);
let rate_limit = LlmError::rate_limit_exceeded(60);
print_error_info("RateLimitExceeded", &rate_limit);
let timeout = LlmError::timeout(30);
print_error_info("Timeout", &timeout);
let auth_error = LlmError::authentication_failed("Invalid API key");
print_error_info("AuthenticationFailed", &auth_error);
let token_error = LlmError::token_limit_exceeded(150_000, 128_000);
print_error_info("TokenLimitExceeded", &token_error);
}
fn print_error_info(name: &str, error: &LlmError) {
println!("{}:", name);
println!(" Display: {}", error);
println!(" Category: {:?}", error.category());
println!(" Severity: {:?}", error.severity());
println!(" Retryable: {}", error.is_retryable());
println!(" User message: {}", error.user_message());
println!();
}
fn demonstrate_error_routing() {
println!("=== Category-Based Error Routing ===\n");
let errors = vec![
LlmError::configuration_error("Bad config"),
LlmError::rate_limit_exceeded(30),
LlmError::timeout(60),
LlmError::authentication_failed("Bad key"),
];
for error in errors {
let action = match error.category() {
ErrorCategory::Client => "Fix configuration and retry",
ErrorCategory::Transient => "Retry with exponential backoff",
ErrorCategory::External => "Log and alert ops team",
ErrorCategory::Internal => "Log, alert, investigate bug",
ErrorCategory::BusinessLogic => "Handle as expected flow",
_ => "Unknown category - update error handling",
};
println!("Error: {}", error);
println!(" Category: {:?}", error.category());
println!(" Action: {}\n", action);
}
}
fn demonstrate_retry_pattern() {
println!("=== Retry Pattern for Transient Errors ===\n");
let test_errors = vec![
LlmError::rate_limit_exceeded(5),
LlmError::timeout(30),
LlmError::authentication_failed("Invalid key"),
LlmError::request_failed("Network error".to_string(), None),
];
println!("Checking which errors should be retried:\n");
for error in test_errors {
if error.is_retryable() {
println!(" {} -> RETRY", error);
} else {
println!(" {} -> DO NOT RETRY", error);
}
}
println!("\n--- Example Retry Loop Pattern ---\n");
println!(
r#"
async fn execute_with_retry(
client: &UnifiedLLMClient,
request: UnifiedLLMRequest,
) -> Result<Response, LlmError> {{
let max_retries = 3;
let mut delay = Duration::from_secs(1);
for attempt in 1..=max_retries {{
match client.execute_llm(request.clone(), None, None).await {{
Ok(response) => return Ok(response),
Err(e) if e.is_retryable() && attempt < max_retries => {{
println!("Attempt {{}} failed: {{}}", attempt, e);
tokio::time::sleep(delay).await;
delay *= 2; // Exponential backoff
}}
Err(e) => return Err(e),
}}
}}
unreachable!()
}}
"#
);
}
fn demonstrate_user_messages() {
println!("=== User-Friendly Error Messages ===\n");
println!("Technical errors should be translated to safe user messages:\n");
let errors = vec![
LlmError::rate_limit_exceeded(60),
LlmError::timeout(30),
LlmError::authentication_failed("sk-invalid-key-12345"),
LlmError::token_limit_exceeded(200_000, 128_000),
LlmError::configuration_error("Provider config validation failed: missing base_url"),
];
for error in errors {
println!("Technical: {}", error);
println!("User-safe: {}\n", error.user_message());
}
}
async fn demonstrate_real_error_handling() -> Result<(), LlmError> {
println!("=== Real Error Handling Example ===\n");
let config = LLMConfig {
provider: Box::new(OpenAIConfig {
api_key: Some("sk-invalid-key-for-demo".to_string()),
base_url: "https://api.openai.com".to_string(),
default_model: "gpt-4o-mini".to_string(),
max_context_tokens: 128_000,
retry_policy: Default::default(),
}),
default_params: DefaultLLMParams::default(),
};
let client = UnifiedLLMClient::from_config(config)?;
let request = UnifiedLLMRequest::new(vec![UnifiedMessage::user("Hello!")]);
println!("Attempting request with invalid API key...\n");
match client.execute_llm(request, None, None).await {
#[cfg(feature = "events")]
Ok((response, _events)) => {
println!("Unexpected success: {}", response.content);
}
#[cfg(not(feature = "events"))]
Ok(response) => {
println!("Unexpected success: {}", response.content);
}
Err(error) => {
println!("Error occurred (expected):");
println!(" Type: {}", error);
println!(" Category: {:?}", error.category());
println!(" Retryable: {}", error.is_retryable());
println!(" User message: {}", error.user_message());
println!();
match error.category() {
ErrorCategory::Client => {
println!(
"Action: This is a client error. Check your API key and configuration."
);
}
ErrorCategory::Transient => {
println!("Action: This is transient. Retry with backoff.");
}
ErrorCategory::External => {
println!("Action: Provider issue. Check status page or try later.");
}
_ => {
println!("Action: Investigate the error.");
}
}
}
}
Ok(())
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let args: Vec<String> = std::env::args().collect();
let run_live = args.contains(&"--live".to_string());
demonstrate_error_types();
demonstrate_error_routing();
demonstrate_retry_pattern();
demonstrate_user_messages();
if run_live {
println!("Running live error handling test...\n");
if let Err(e) = demonstrate_real_error_handling().await {
println!("Live test resulted in error: {}", e);
}
} else {
println!("=== Live Test Skipped ===\n");
println!("Run with --live flag to test with actual API calls:");
println!(" cargo run --example error_handling -- --live\n");
}
println!("=== Error Handling Patterns Summary ===\n");
println!("1. Use error.category() to route errors to appropriate handlers");
println!("2. Use error.is_retryable() to decide if retry makes sense");
println!("3. Use error.user_message() for safe user-facing messages");
println!("4. Use error.severity() for logging level decisions");
println!("5. The library's RetryPolicy handles transient errors automatically");
Ok(())
}