use rstructor::{
AnthropicClient, Instructor, LLMClient,
logging::{LogLevel, init_logging},
};
use serde::{Deserialize, Serialize};
#[derive(Instructor, Serialize, Deserialize, Debug)]
#[llm(
description = "Weather forecast for a location",
validate = "validate_weather_forecast"
)]
struct WeatherForecast {
#[llm(description = "Location/city name")]
location: String,
#[llm(description = "Current temperature in Celsius", example = 25.5)]
current_temperature: f32,
#[llm(description = "Forecast for upcoming days")]
forecast: Vec<DayForecast>,
}
#[derive(Instructor, Serialize, Deserialize, Debug)]
#[llm(
description = "Weather forecast for a specific day",
validate = "validate_day_forecast"
)]
struct DayForecast {
#[llm(description = "Day of the week", example = "Monday")]
day: String,
#[llm(description = "Temperature in Celsius", example = 28.5)]
temperature: f32,
#[llm(description = "Weather conditions", example = "Sunny")]
#[serde(alias = "weather")]
conditions: String,
}
fn validate_weather_forecast(forecast: &WeatherForecast) -> rstructor::Result<()> {
if forecast.location.trim().is_empty() {
return Err(rstructor::RStructorError::ValidationError(
"Location cannot be empty".to_string(),
));
}
if forecast.current_temperature < -100.0 || forecast.current_temperature > 70.0 {
return Err(rstructor::RStructorError::ValidationError(format!(
"Current temperature must be between -100 and 70°C, got {}",
forecast.current_temperature
)));
}
if forecast.forecast.is_empty() {
return Err(rstructor::RStructorError::ValidationError(
"Forecast must include at least one day".to_string(),
));
}
Ok(())
}
fn validate_day_forecast(day: &DayForecast) -> rstructor::Result<()> {
if day.day.trim().is_empty() {
return Err(rstructor::RStructorError::ValidationError(
"Day cannot be empty".to_string(),
));
}
if day.temperature < -100.0 || day.temperature > 70.0 {
return Err(rstructor::RStructorError::ValidationError(format!(
"Forecast temperature must be between -100 and 70°C, got {}",
day.temperature
)));
}
if day.conditions.trim().is_empty() {
return Err(rstructor::RStructorError::ValidationError(
"Weather conditions cannot be empty".to_string(),
));
}
Ok(())
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
init_logging(LogLevel::Debug);
println!("Starting weather forecast example with detailed logging...");
println!("This example demonstrates retry logic with validation errors.");
let api_key = match std::env::var("ANTHROPIC_API_KEY") {
Ok(key) => key,
Err(_) => {
eprintln!("⚠️ ANTHROPIC_API_KEY environment variable not set!");
eprintln!("Please set it with: export ANTHROPIC_API_KEY=your_api_key");
eprintln!(
"For demonstration purposes, using a fake key (will show API errors in logs)"
);
"dummy-key-for-demonstration".to_string()
}
};
let client = AnthropicClient::new(api_key)?.temperature(0.7);
println!("\nSending request to Anthropic API with increased randomness...");
println!("Will retry up to 3 times on validation errors with detailed logging.\n");
let prompt = "What's the weather forecast for Tokyo for the next 3 days?";
let forecast_result = client.materialize::<WeatherForecast>(prompt).await;
match forecast_result {
Ok(forecast) => {
println!("\n✅ Successfully generated forecast after potential retries!");
println!("\nGenerated forecast for {}", forecast.location);
println!("Current temperature: {}°C", forecast.current_temperature);
println!("\nUpcoming forecast:");
for day in forecast.forecast {
println!("- {}: {}°C, {}", day.day, day.temperature, day.conditions);
}
}
Err(e) => {
println!("\n❌ Failed to generate forecast after retries");
println!("Error: {}", e);
println!("\nThe logs above show the detailed retry attempts and validation errors.");
}
}
Ok(())
}