use rstructor::{AnthropicClient, Instructor, RStructorError};
type Result<T> = rstructor::Result<T>;
use chrono::{NaiveDate, NaiveTime};
use serde::{Deserialize, Serialize};
use std::{
env,
io::{IsTerminal, stdin},
};
#[derive(Instructor, Serialize, Deserialize, Debug, Clone)]
#[llm(description = "Represents a contact person")]
struct Contact {
#[llm(description = "Name of the contact person", example = "John Smith")]
name: String,
#[llm(description = "Email address", example = "john.smith@example.com")]
email: Option<String>,
#[llm(description = "Phone number", example = "555-123-4567")]
phone: Option<String>,
#[llm(description = "Role or relationship", example = "Event organizer")]
role: Option<String>,
}
#[derive(Instructor, Serialize, Deserialize, Debug, Clone)]
#[llm(description = "Represents a location")]
struct Location {
#[llm(
description = "Name of the venue or location",
example = "Grand Plaza Hotel"
)]
name: String,
#[llm(description = "Street address", example = "123 Main St")]
address: String,
#[llm(description = "City", example = "New York")]
city: String,
#[llm(description = "State or province", example = "NY")]
state: Option<String>,
#[llm(description = "Postal/ZIP code", example = "10001")]
zip: Option<String>,
#[llm(description = "Country", example = "USA")]
country: Option<String>,
#[llm(
description = "Any special instructions for finding or accessing the location",
example = "Enter through the south entrance"
)]
instructions: Option<String>,
}
#[derive(Instructor, Serialize, Deserialize, Debug, Clone)]
#[llm(description = "Represents a scheduled activity within an event")]
struct Activity {
#[llm(
description = "Name or title of the activity",
example = "Welcome Reception"
)]
name: String,
#[llm(description = "Start time in HH:MM format", example = "18:30")]
start_time: String,
#[llm(description = "End time in HH:MM format", example = "20:00")]
end_time: String,
#[llm(
description = "Description of the activity",
example = "Casual networking with drinks and appetizers"
)]
description: Option<String>,
#[llm(
description = "Location of this activity, if different from main event",
example = "Garden Terrace"
)]
location: Option<String>,
}
#[derive(Instructor, Serialize, Deserialize, Debug, Clone)]
#[llm(description = "Information about an event to be organized",
validate = "validate_event_plan",
examples = [
::serde_json::json!({
"event_name": "Annual Tech Conference",
"event_type": "Conference",
"description": "A gathering of industry professionals to discuss emerging trends in technology",
"date": "2025-06-15",
"start_time": "09:00",
"end_time": "17:00",
"location": {
"name": "Metro Convention Center",
"address": "400 Convention Way",
"city": "San Francisco",
"state": "CA",
"zip": "94103",
"country": "USA",
"instructions": "Parking available in the south garage"
},
"estimated_attendees": 250,
"contact": {
"name": "Jane Smith",
"email": "jane.smith@example.com",
"phone": "555-987-6543",
"role": "Event Coordinator"
},
"activities": [
{
"name": "Registration & Breakfast",
"start_time": "08:00",
"end_time": "09:00",
"description": "Check-in and continental breakfast",
"location": "Main Lobby"
},
{
"name": "Keynote Speech",
"start_time": "09:15",
"end_time": "10:30",
"description": "Opening address by CEO",
"location": "Grand Ballroom"
}
],
"special_requirements": "Vegetarian lunch options, AV equipment for presentations",
"estimated_budget": 15000
})
])]
struct EventPlan {
#[llm(description = "Name of the event", example = "Company Holiday Party")]
event_name: String,
#[llm(description = "Type of event", example = "Party")]
event_type: String,
#[llm(
description = "Description of the event",
example = "Annual celebration for employees and their families"
)]
description: String,
#[llm(
description = "Date of the event in YYYY-MM-DD format",
example = "2023-12-15"
)]
date: String,
#[llm(description = "Start time in HH:MM format", example = "18:00")]
start_time: String,
#[llm(description = "End time in HH:MM format", example = "22:00")]
end_time: String,
#[llm(description = "Location details for the event")]
location: Location,
#[llm(description = "Estimated number of attendees", example = 100)]
estimated_attendees: u32,
#[llm(description = "Primary contact person for the event")]
contact: Contact,
#[llm(description = "Schedule of activities during the event")]
activities: Vec<Activity>,
#[llm(
description = "Any special requirements or notes",
example = "Need vegetarian food options"
)]
special_requirements: Option<String>,
#[llm(description = "Estimated budget in dollars", example = 5000)]
estimated_budget: Option<f32>,
}
fn validate_event_plan(plan: &EventPlan) -> rstructor::Result<()> {
if NaiveDate::parse_from_str(&plan.date, "%Y-%m-%d").is_err() {
return Err(RStructorError::ValidationError(format!(
"Invalid date format: {}. Expected YYYY-MM-DD",
plan.date
)));
}
let validate_time = |time: &str| -> rstructor::Result<()> {
if NaiveTime::parse_from_str(time, "%H:%M").is_err() {
return Err(RStructorError::ValidationError(format!(
"Invalid time format: {}. Expected HH:MM",
time
)));
}
Ok(())
};
validate_time(&plan.start_time)?;
validate_time(&plan.end_time)?;
for activity in &plan.activities {
validate_time(&activity.start_time)?;
validate_time(&activity.end_time)?;
}
let event_start = NaiveTime::parse_from_str(&plan.start_time, "%H:%M").unwrap();
let event_end = NaiveTime::parse_from_str(&plan.end_time, "%H:%M").unwrap();
for activity in &plan.activities {
let activity_start = NaiveTime::parse_from_str(&activity.start_time, "%H:%M").unwrap();
let activity_end = NaiveTime::parse_from_str(&activity.end_time, "%H:%M").unwrap();
if activity_start < event_start || activity_end > event_end {
return Err(RStructorError::ValidationError(format!(
"Activity '{}' time ({}-{}) is outside event hours ({}-{})",
activity.name,
activity.start_time,
activity.end_time,
plan.start_time,
plan.end_time
)));
}
}
if plan.contact.email.is_none() && plan.contact.phone.is_none() {
return Err(RStructorError::ValidationError(
"Contact must have either email or phone specified".to_string(),
));
}
Ok(())
}
async fn process_event_request(
client: &(impl rstructor::LLMClient + std::marker::Sync),
description: &str,
) -> Result<EventPlan> {
let prompt = format!(
"Create a detailed event plan based on this description:\n\n{}",
description
);
client.materialize::<EventPlan>(&prompt).await
}
#[tokio::main]
async fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
let sample_description = "I need to plan a team-building retreat for my company's marketing department. \
We have about 20 people and want to do it next month on a Friday. \
We'd like some outdoor activities and team exercises, ideally at a nice location \
near nature. Our budget is approximately $5000.";
let description = if stdin().is_terminal() {
println!("Welcome to the AI Event Planner!");
println!(
"Please describe the event you want to plan (type 'done' on a new line when finished):"
);
let mut description = String::new();
let mut line = String::new();
loop {
line.clear();
stdin().read_line(&mut line)?;
if line.trim() == "done" {
break;
}
description.push_str(&line);
}
if description.trim().is_empty() {
println!("No description provided. Using a sample description instead.");
sample_description.to_string()
} else {
description
}
} else {
println!("Running in non-interactive mode. Using sample description.");
sample_description.to_string()
};
let api_key =
env::var("ANTHROPIC_API_KEY").expect("Please set ANTHROPIC_API_KEY environment variable");
println!("\nProcessing your request with Anthropic...\n");
let client = AnthropicClient::new(api_key)?.temperature(0.3);
match process_event_request(&client, &description).await {
Ok(plan) => print_event_plan(&plan),
Err(e) => {
println!("Error: {}", e);
if let rstructor::RStructorError::ValidationError(msg) = &e {
println!("\nValidation error details: {}", msg);
}
}
}
Ok(())
}
fn print_event_plan(plan: &EventPlan) {
println!("===== EVENT PLAN =====");
println!("Name: {}", plan.event_name);
println!("Type: {}", plan.event_type);
println!("Description: {}", plan.description);
println!("Date: {}", plan.date);
println!("Time: {} to {}", plan.start_time, plan.end_time);
println!("Estimated Attendees: {}", plan.estimated_attendees);
println!("\n--- LOCATION ---");
println!("Venue: {}", plan.location.name);
println!("Address: {}", plan.location.address);
println!("City: {}", plan.location.city);
if let Some(state) = &plan.location.state {
println!("State: {}", state);
}
if let Some(zip) = &plan.location.zip {
println!("ZIP: {}", zip);
}
if let Some(country) = &plan.location.country {
println!("Country: {}", country);
}
if let Some(instructions) = &plan.location.instructions {
println!("Instructions: {}", instructions);
}
println!("\n--- CONTACT ---");
println!("Name: {}", plan.contact.name);
if let Some(email) = &plan.contact.email {
println!("Email: {}", email);
}
if let Some(phone) = &plan.contact.phone {
println!("Phone: {}", phone);
}
if let Some(role) = &plan.contact.role {
println!("Role: {}", role);
}
println!("\n--- SCHEDULE ---");
for activity in &plan.activities {
println!(
"• {} to {}: {}",
activity.start_time, activity.end_time, activity.name
);
if let Some(desc) = &activity.description {
println!(" {}", desc);
}
if let Some(loc) = &activity.location {
println!(" Location: {}", loc);
}
}
if let Some(requirements) = &plan.special_requirements {
println!("\nSpecial Requirements: {}", requirements);
}
if let Some(budget) = plan.estimated_budget {
println!("\nEstimated Budget: ${:.2}", budget);
}
}