Lab 3.1: Tools - Error Handling & Validation¶
In this lab, you'll enhance your travel assistant's tools with proper error handling, input validation, and graceful fallbacks.
You'll learn how to build tools that handle edge cases, validate inputs, and provide helpful error messages to agents when things go wrong.
By the end of this lab, you will:
- ✅ Add input validation to prevent invalid tool calls
- ✅ Implement error handling for external dependencies (databases, APIs)
- ✅ Create graceful fallbacks when tools fail
- ✅ Return structured error messages that help the AI agent recover
Instructions¶
Step 1: Add Input Validation¶
Open FlightSearchTool class and locate the SearchFlights method.
Add these validation checks to catch common user input errors before making any external calls:
public async Task<string> SearchFlights(
[Description("Departure city or airport (e.g., 'Melbourne', 'MEL')")] string origin,
[Description("Destination city or airport (e.g., 'Tokyo', 'NRT', 'Paris', 'CDG')")] string destination,
[Description("Departure date in YYYY-MM-DD format (optional)")] string? departureDate = null,
[Description("Return date in YYYY-MM-DD format (optional)")] string? returnDate = null,
[Description("Maximum budget in AUD (optional)")] decimal? maxBudget = null,
[Description("User preferences for flight characteristics (e.g., 'comfortable flight with entertainment', 'budget-friendly', 'business travel') (optional)")] string? userPreferences = null)
{
// VALIDATION 1: Check for missing required fields
if (string.IsNullOrWhiteSpace(origin) || string.IsNullOrWhiteSpace(destination))
{
return JsonSerializer.Serialize(new
{
success = false,
error = "Missing required fields",
message = "Please provide both origin and destination cities.",
suggestedAction = "Ask the user to specify both departure and destination cities."
});
}
// VALIDATION 2: Check if searching same city
if (origin.Equals(destination, StringComparison.OrdinalIgnoreCase))
{
return JsonSerializer.Serialize(new
{
success = false,
error = "Invalid route",
message = "Origin and destination cannot be the same city.",
suggestedAction = "Clarify the intended destination with the user."
});
}
// VALIDATION 3: Check date format if provided
if (!string.IsNullOrEmpty(departureDate))
{
if (!DateTime.TryParse(departureDate, out var parsedDate))
{
return JsonSerializer.Serialize(new
{
success = false,
error = "Invalid date format",
message = $"The date '{departureDate}' is not valid. Please use YYYY-MM-DD format.",
suggestedAction = "Ask the user for a valid date in YYYY-MM-DD format."
});
}
// VALIDATION 4: Check if date is in the past
if (parsedDate.Date < DateTime.Today)
{
return JsonSerializer.Serialize(new
{
success = false,
error = "Invalid date",
message = "Departure date cannot be in the past.",
suggestedAction = "Ask the user for a future travel date."
});
}
}
// VALIDATION 5: Check return date is after departure date
if (!string.IsNullOrEmpty(departureDate) && !string.IsNullOrEmpty(returnDate))
{
if (DateTime.TryParse(departureDate, out var depDate) &&
DateTime.TryParse(returnDate, out var retDate))
{
if (retDate <= depDate)
{
return JsonSerializer.Serialize(new
{
success = false,
error = "Invalid date range",
message = "Return date must be after departure date.",
suggestedAction = "Ask the user to provide valid travel dates."
});
}
}
}
// VALIDATION 6: Validate budget is positive
if (maxBudget.HasValue && maxBudget.Value <= 0)
{
return JsonSerializer.Serialize(new
{
success = false,
error = "Invalid budget",
message = "Budget must be a positive amount.",
suggestedAction = "Ask the user for a realistic budget amount."
});
}
// Continue with existing logic...
}
Step 2: Add Error Handling for External Dependencies¶
Wrap the database operations in try-catch blocks to handle different failure scenarios:
try
{
// Query flights from Cosmos DB
var container = _database.GetContainer(_config.CosmosDbFlightsContainer!);
// Generate embedding for user preferences if provided
float[]? preferenceVector = null;
if (!string.IsNullOrEmpty(userPreferences))
{
try
{
var embeddingResponse = await _embeddingClient.GenerateEmbeddingAsync(userPreferences);
preferenceVector = embeddingResponse.Value.ToFloats().ToArray();
}
catch (Exception ex)
{
// Graceful fallback: Continue without semantic search
Console.WriteLine($"Failed to generate embedding: {ex.Message}");
// Don't fail the entire search, just skip semantic matching
}
}
// ... rest of existing query logic ...
}
catch (CosmosException ex) when (ex.StatusCode == System.Net.HttpStatusCode.TooManyRequests)
{
// Handle rate limiting (429)
return JsonSerializer.Serialize(new
{
success = false,
error = "Service temporarily busy",
message = "Flight search is experiencing high demand. Please try again in a moment.",
suggestedAction = "Apologize and ask the user to try their search again in a few seconds.",
retryable = true
});
}
catch (CosmosException ex) when (ex.StatusCode == System.Net.HttpStatusCode.ServiceUnavailable)
{
// Handle service unavailable (503)
return JsonSerializer.Serialize(new
{
success = false,
error = "Service unavailable",
message = "The flight database is temporarily unavailable.",
suggestedAction = "Apologize and suggest the user try again later or provide general travel advice.",
retryable = true
});
}
catch (CosmosException ex)
{
// Handle other Cosmos DB errors
Console.WriteLine($"Cosmos DB error: {ex.StatusCode} - {ex.Message}");
return JsonSerializer.Serialize(new
{
success = false,
error = "Database error",
message = "Unable to search flights due to a technical issue.",
suggestedAction = "Apologize and offer to help with other travel planning tasks.",
technicalDetails = $"Status: {ex.StatusCode}"
});
}
catch (Exception ex)
{
// Handle unexpected errors
Console.WriteLine($"Unexpected error in SearchFlights: {ex.Message}");
return JsonSerializer.Serialize(new
{
success = false,
error = "Unexpected error",
message = "An unexpected error occurred while searching for flights.",
suggestedAction = "Apologize and offer alternative assistance.",
technicalDetails = ex.GetType().Name
});
}
Step 3: Test the Error Handling¶
Run your backend and test these scenarios:
-
Test 1: Same City Validation
User: Find me flights from Tokyo to TokyoExpected Response: Agent should recognize the validation error and ask for clarification.
-
Test 2: Past Date Validation**
User: Find me flights to Paris on 2025-01-01Expected Response:: Agent should ask for a future date.
-
Test 3: Invalid Date Format
User: Find me flights to Sydney departing 2026-03-15 and returning 2026-03-10Expected Response:: Agent should recognize the date logic error.