wanderlust.ai / src /wanderlust_ai /agents /intelligent_flight_agent.py
BlakeL's picture
Upload 115 files
3f9f85b verified
"""
Intelligent Flight Agent with Advanced Decision-Making Logic
This module implements a sophisticated FlightAgent that goes beyond simple data retrieval
to provide intelligent flight recommendations based on multiple factors and strategies.
"""
import asyncio
import hashlib
import json
import logging
import time
from datetime import datetime, timedelta, timezone
from decimal import Decimal
from enum import Enum
from typing import Dict, List, Optional, Any, Tuple, Union
from dataclasses import dataclass, field
from collections import defaultdict
from pydantic import BaseModel, Field
from ..models.flight_models import FlightOption, Airline
from ..utils.config import get_settings
class SearchStrategy(str, Enum):
"""Search strategy enumeration."""
CHEAPEST = "cheapest"
FASTEST = "fastest"
BEST_VALUE = "best_value"
MOST_CONVENIENT = "most_convenient"
LEAST_STOPS = "least_stops"
class TripType(str, Enum):
"""Trip type enumeration."""
ONE_WAY = "one_way"
ROUND_TRIP = "round_trip"
MULTI_CITY = "multi_city"
@dataclass
class FlightSearchRequest:
"""Comprehensive flight search request."""
departure_city: str
arrival_city: str
departure_date: datetime
return_date: Optional[datetime] = None
passengers: int = 1
travel_class: str = "Economy"
strategy: SearchStrategy = SearchStrategy.BEST_VALUE
trip_type: TripType = TripType.ONE_WAY
max_stops: int = 2
max_duration_hours: float = 24.0
budget_max: Optional[Decimal] = None
flexible_dates: bool = False
date_flexibility_days: int = 3
multi_city_legs: Optional[List[Dict[str, Any]]] = None
def __post_init__(self):
"""Validate request after initialization."""
if self.passengers < 1 or self.passengers > 9:
raise ValueError("Passengers must be between 1 and 9")
if self.max_stops < 0 or self.max_stops > 3:
raise ValueError("Max stops must be between 0 and 3")
if self.date_flexibility_days < 0 or self.date_flexibility_days > 7:
raise ValueError("Date flexibility must be between 0 and 7 days")
@dataclass
class FlightSearchResult:
"""Comprehensive flight search result."""
flights: List[FlightOption]
search_request: FlightSearchRequest
search_metadata: Dict[str, Any] = field(default_factory=dict)
search_duration_ms: float = 0.0
cache_hit: bool = False
total_results_found: int = 0
filtered_out: int = 0
ranking_scores: Dict[str, float] = field(default_factory=dict)
class FlexibleDateGenerator:
"""
Generates flexible date ranges for flight searches.
This class implements intelligent date flexibility logic that considers
business rules, seasonal patterns, and user preferences.
"""
def __init__(self):
self.weekend_days = {5, 6} # Friday, Saturday
self.holiday_multiplier = 1.5 # More flexibility during holidays
def generate_date_range(
self,
base_date: datetime,
flexibility_days: int,
include_weekends: bool = True,
prefer_weekdays: bool = False
) -> List[datetime]:
"""
Generate a range of dates around the base date.
Strategy:
1. Consider weekend vs weekday preferences
2. Apply holiday awareness
3. Optimize for common travel patterns
"""
dates = []
base_weekday = base_date.weekday()
# Generate dates before and after base date
for day_offset in range(-flexibility_days, flexibility_days + 1):
candidate_date = base_date + timedelta(days=day_offset)
candidate_weekday = candidate_date.weekday()
# Skip weekends if not preferred
if not include_weekends and candidate_weekday in self.weekend_days:
continue
# Prioritize weekdays if preferred
if prefer_weekdays and candidate_weekday in self.weekend_days:
continue
dates.append(candidate_date)
# Sort by proximity to base date
dates.sort(key=lambda d: abs((d - base_date).days))
return dates
def is_holiday_period(self, date: datetime) -> bool:
"""Check if date falls within a holiday period."""
# Simplified holiday detection - would be more sophisticated in production
month = date.month
day = date.day
# Major US holidays (simplified)
holidays = [
(12, 25), # Christmas
(12, 31), # New Year's Eve
(1, 1), # New Year's Day
(7, 4), # Independence Day
(11, 28), # Thanksgiving (simplified)
]
return (month, day) in holidays
class FlightRankingEngine:
"""
Advanced ranking engine for flight options.
This class implements sophisticated ranking algorithms that consider
multiple factors and user preferences to score flight options.
"""
def __init__(self):
# Weight configuration for different factors
self.weights = {
"price": 0.3,
"duration": 0.25,
"stops": 0.2,
"departure_time": 0.15,
"airline_reliability": 0.1
}
# Time preferences (0-1 score based on departure time)
self.time_preferences = {
"morning": (6, 11), # 6 AM - 11 AM
"afternoon": (11, 17), # 11 AM - 5 PM
"evening": (17, 22), # 5 PM - 10 PM
"night": (22, 6) # 10 PM - 6 AM (next day)
}
# Airline reliability scores (based on historical data)
self.airline_scores = {
"Delta Air Lines": 0.9,
"American Airlines": 0.85,
"United Airlines": 0.8,
"Southwest Airlines": 0.88,
"JetBlue Airways": 0.87,
"Alaska Airlines": 0.91,
"Spirit Airlines": 0.6,
"Frontier Airlines": 0.65,
"Other": 0.7
}
def calculate_composite_score(
self,
flight: FlightOption,
strategy: SearchStrategy,
preferences: Dict[str, Any] = None
) -> float:
"""
Calculate composite score for a flight option.
Strategy:
1. Normalize individual factors to 0-1 scale
2. Apply strategy-specific weighting
3. Combine factors using weighted average
4. Apply bonus/penalty adjustments
"""
if preferences is None:
preferences = {}
# Calculate individual factor scores
price_score = self._calculate_price_score(flight, strategy, preferences)
duration_score = self._calculate_duration_score(flight, strategy)
stops_score = self._calculate_stops_score(flight, strategy)
time_score = self._calculate_departure_time_score(flight, preferences)
airline_score = self._calculate_airline_score(flight)
# Apply strategy-specific weights
weights = self._get_strategy_weights(strategy)
# Calculate weighted composite score
composite_score = (
price_score * weights["price"] +
duration_score * weights["duration"] +
stops_score * weights["stops"] +
time_score * weights["departure_time"] +
airline_score * weights["airline_reliability"]
)
# Apply bonus/penalty adjustments
composite_score = self._apply_adjustments(flight, composite_score, preferences)
return min(max(composite_score, 0.0), 1.0) # Clamp to 0-1 range
def _calculate_price_score(
self,
flight: FlightOption,
strategy: SearchStrategy,
preferences: Dict[str, Any]
) -> float:
"""Calculate price-based score."""
if strategy == SearchStrategy.CHEAPEST:
# For cheapest strategy, lower prices get higher scores
# This would be normalized against all available prices
return 1.0 - min(float(flight.price) / 1000, 1.0)
elif strategy == SearchStrategy.BEST_VALUE:
# Best value considers price per hour
if flight.duration_minutes > 0:
price_per_hour = float(flight.price) / flight.duration_hours
return 1.0 - min(price_per_hour / 100, 1.0)
# Default: moderate price preference
return 1.0 - min(float(flight.price) / 2000, 1.0)
def _calculate_duration_score(
self,
flight: FlightOption,
strategy: SearchStrategy
) -> float:
"""Calculate duration-based score."""
if strategy == SearchStrategy.FASTEST:
# For fastest strategy, shorter flights get higher scores
return 1.0 - min(flight.duration_minutes / 1440, 1.0) # 24 hours max
# Default: moderate duration preference
return 1.0 - min(flight.duration_minutes / 720, 1.0) # 12 hours max
def _calculate_stops_score(
self,
flight: FlightOption,
strategy: SearchStrategy
) -> float:
"""Calculate stops-based score."""
if strategy == SearchStrategy.LEAST_STOPS or strategy == SearchStrategy.MOST_CONVENIENT:
# Direct flights get highest score
return 1.0 if flight.stops == 0 else 0.8 - (flight.stops * 0.2)
# Default: slight preference for fewer stops
return 1.0 - (flight.stops * 0.3)
def _calculate_departure_time_score(
self,
flight: FlightOption,
preferences: Dict[str, Any]
) -> float:
"""Calculate departure time-based score."""
departure_hour = flight.departure_time.hour
# Check user time preferences
preferred_time = preferences.get("preferred_departure_time", "morning")
if preferred_time in self.time_preferences:
start_hour, end_hour = self.time_preferences[preferred_time]
if start_hour <= end_hour:
# Normal time range (e.g., 6-11)
if start_hour <= departure_hour < end_hour:
return 1.0
else:
# Overnight range (e.g., 22-6)
if departure_hour >= start_hour or departure_hour < end_hour:
return 1.0
# Default scoring based on time of day
if 8 <= departure_hour <= 18: # Business hours
return 0.8
elif 6 <= departure_hour < 8 or 18 < departure_hour <= 22: # Extended hours
return 0.6
else: # Very early or late
return 0.4
def _calculate_airline_score(self, flight: FlightOption) -> float:
"""Calculate airline reliability score."""
# Handle both enum and string values
airline_name = flight.airline.value if hasattr(flight.airline, 'value') else flight.airline
return self.airline_scores.get(airline_name, self.airline_scores["Other"])
def _get_strategy_weights(self, strategy: SearchStrategy) -> Dict[str, float]:
"""Get weights adjusted for specific strategy."""
base_weights = self.weights.copy()
if strategy == SearchStrategy.CHEAPEST:
base_weights.update({"price": 0.6, "duration": 0.1, "stops": 0.1})
elif strategy == SearchStrategy.FASTEST:
base_weights.update({"duration": 0.5, "stops": 0.3, "price": 0.1})
elif strategy == SearchStrategy.LEAST_STOPS:
base_weights.update({"stops": 0.5, "duration": 0.2, "price": 0.2})
elif strategy == SearchStrategy.MOST_CONVENIENT:
base_weights.update({"departure_time": 0.3, "stops": 0.25, "duration": 0.2})
return base_weights
def _apply_adjustments(
self,
flight: FlightOption,
score: float,
preferences: Dict[str, Any]
) -> float:
"""Apply bonus/penalty adjustments to the score."""
adjustments = 0.0
# Direct flight bonus
if flight.is_direct_flight:
adjustments += 0.1
# Reasonable connection time bonus (if applicable)
if flight.stops > 0:
# This would check connection times in a real implementation
adjustments += 0.05
# Airline preference bonus
preferred_airlines = preferences.get("preferred_airlines", [])
airline_name = flight.airline.value if hasattr(flight.airline, 'value') else flight.airline
if airline_name in preferred_airlines:
adjustments += 0.15
# Avoided airlines penalty
avoided_airlines = preferences.get("avoided_airlines", [])
if airline_name in avoided_airlines:
adjustments -= 0.2
return score + adjustments
class FlightFilterEngine:
"""
Intelligent filtering engine for flight options.
This class implements sophisticated filtering logic to remove
unrealistic or undesirable flight options.
"""
def __init__(self):
self.min_connection_time_minutes = 45 # Minimum connection time
self.max_connection_time_hours = 4 # Maximum connection time
self.max_layover_time_hours = 8 # Maximum layover time
self.min_flight_duration_minutes = 30 # Minimum flight segment duration
def filter_flights(
self,
flights: List[FlightOption],
request: FlightSearchRequest
) -> Tuple[List[FlightOption], List[str]]:
"""
Filter flights based on business rules and user preferences.
Returns:
Tuple of (filtered_flights, filter_reasons)
"""
filtered_flights = []
filter_reasons = []
for flight in flights:
is_valid, reasons = self._validate_flight(flight, request)
if is_valid:
filtered_flights.append(flight)
else:
filter_reasons.extend(reasons)
return filtered_flights, filter_reasons
def _validate_flight(
self,
flight: FlightOption,
request: FlightSearchRequest
) -> Tuple[bool, List[str]]:
"""Validate individual flight against business rules."""
reasons = []
# Check stops limit
if flight.stops > request.max_stops:
reasons.append(f"Too many stops ({flight.stops} > {request.max_stops})")
# Check duration limit
max_duration_minutes = request.max_duration_hours * 60
if flight.duration_minutes > max_duration_minutes:
reasons.append(f"Flight too long ({flight.duration_minutes}min > {max_duration_minutes}min)")
# Check budget limit
if request.budget_max and flight.price > request.budget_max:
reasons.append(f"Over budget (${flight.price} > ${request.budget_max})")
# Check minimum flight duration
if flight.duration_minutes < self.min_flight_duration_minutes:
reasons.append(f"Flight too short ({flight.duration_minutes}min < {self.min_flight_duration_minutes}min)")
# Check for unrealistic prices
if flight.price < Decimal("50") or flight.price > Decimal("5000"):
reasons.append(f"Unrealistic price (${flight.price})")
# Check for past departure times
if flight.departure_time < datetime.now(timezone.utc):
reasons.append("Departure time in the past")
# Additional validation for connecting flights
if flight.stops > 0:
# This would check connection times in a real implementation
# For now, we'll just flag flights with too many stops
if flight.stops > 2:
reasons.append("Too many connections for comfort")
is_valid = len(reasons) == 0
return is_valid, reasons
class FlightCacheManager:
"""
Intelligent caching manager for flight searches.
This class implements sophisticated caching strategies with
appropriate expiration times and cache invalidation logic.
"""
def __init__(self):
self.cache = {}
self.cache_metadata = {}
# Cache expiration times (in minutes)
self.expiration_times = {
"flight_prices": 30, # Flight prices change frequently
"flight_schedules": 120, # Schedules change less frequently
"airline_info": 1440, # Airline info changes rarely
"route_info": 720 # Route info changes moderately
}
def get_cache_key(self, request: FlightSearchRequest) -> str:
"""Generate cache key for search request."""
# Create a normalized representation of the request
key_data = {
"departure_city": request.departure_city,
"arrival_city": request.arrival_city,
"departure_date": request.departure_date.isoformat(),
"return_date": request.return_date.isoformat() if request.return_date else None,
"passengers": request.passengers,
"travel_class": request.travel_class,
"strategy": request.strategy.value,
"trip_type": request.trip_type.value,
"max_stops": request.max_stops
}
# Create hash of the normalized data
key_string = json.dumps(key_data, sort_keys=True)
return hashlib.md5(key_string.encode()).hexdigest()
def get_cached_results(self, request: FlightSearchRequest) -> Optional[List[FlightOption]]:
"""Get cached results if available and valid."""
cache_key = self.get_cache_key(request)
if cache_key in self.cache:
metadata = self.cache_metadata[cache_key]
expiration_time = metadata["timestamp"] + timedelta(
minutes=self.expiration_times["flight_prices"]
)
if datetime.now() < expiration_time:
return self.cache[cache_key]
else:
# Remove expired entry
del self.cache[cache_key]
del self.cache_metadata[cache_key]
return None
def cache_results(self, request: FlightSearchRequest, results: List[FlightOption]):
"""Cache search results."""
cache_key = self.get_cache_key(request)
self.cache[cache_key] = results
self.cache_metadata[cache_key] = {
"timestamp": datetime.now(),
"request_hash": cache_key
}
def get_cache_stats(self) -> Dict[str, Any]:
"""Get cache statistics."""
total_entries = len(self.cache)
expired_entries = 0
for metadata in self.cache_metadata.values():
expiration_time = metadata["timestamp"] + timedelta(
minutes=self.expiration_times["flight_prices"]
)
if datetime.now() >= expiration_time:
expired_entries += 1
return {
"total_entries": total_entries,
"expired_entries": expired_entries,
"active_entries": total_entries - expired_entries
}
class IntelligentFlightAgent:
"""
Intelligent Flight Agent with sophisticated decision-making logic.
This agent goes beyond simple data retrieval to provide intelligent
flight recommendations based on multiple factors and user preferences.
"""
def __init__(self, api_key: Optional[str] = None):
"""Initialize the intelligent flight agent."""
self.settings = get_settings()
self.api_key = api_key or self.settings.anthropic_api_key
# Initialize components
self.date_generator = FlexibleDateGenerator()
self.ranking_engine = FlightRankingEngine()
self.filter_engine = FlightFilterEngine()
self.cache_manager = FlightCacheManager()
# Initialize logging
self.logger = logging.getLogger(__name__)
# Search engines (would integrate with your existing clients)
self.search_engines = {
"serpapi": None, # Would initialize SerpAPI client
"tavily": None # Would initialize Tavily client
}
async def search_flights(
self,
request: FlightSearchRequest,
preferences: Dict[str, Any] = None
) -> FlightSearchResult:
"""
Perform intelligent flight search with advanced decision-making.
Strategy:
1. Check cache for recent results
2. Generate flexible date ranges if requested
3. Execute searches across multiple engines
4. Filter unrealistic options
5. Rank results based on strategy and preferences
6. Cache results for future use
"""
start_time = time.time()
if preferences is None:
preferences = {}
# Check cache first
cached_results = self.cache_manager.get_cached_results(request)
if cached_results:
self.logger.info(f"Cache hit for flight search: {request.departure_city} β†’ {request.arrival_city}")
return FlightSearchResult(
flights=cached_results,
search_request=request,
search_metadata={"cache_hit": True},
search_duration_ms=(time.time() - start_time) * 1000,
cache_hit=True,
total_results_found=len(cached_results)
)
# Generate search dates if flexible
search_dates = self._generate_search_dates(request)
# Execute searches
all_flights = []
search_metadata = {
"searches_performed": 0,
"engines_used": [],
"date_ranges_searched": len(search_dates)
}
for search_date in search_dates:
# Create modified request for this date
date_request = self._create_date_request(request, search_date)
# Search across engines (would integrate with your existing clients)
flights = await self._execute_search(date_request)
all_flights.extend(flights)
search_metadata["searches_performed"] += 1
# Filter flights
filtered_flights, filter_reasons = self.filter_engine.filter_flights(all_flights, request)
# Rank flights
ranked_flights = self._rank_flights(filtered_flights, request, preferences)
# Cache results
self.cache_manager.cache_results(request, ranked_flights)
# Calculate search duration
search_duration_ms = (time.time() - start_time) * 1000
# Create result
result = FlightSearchResult(
flights=ranked_flights,
search_request=request,
search_metadata=search_metadata,
search_duration_ms=search_duration_ms,
cache_hit=False,
total_results_found=len(all_flights),
filtered_out=len(all_flights) - len(filtered_flights)
)
self.logger.info(
f"Flight search completed: {len(ranked_flights)} results from {len(all_flights)} total "
f"({len(filtered_flights)} after filtering) in {search_duration_ms:.0f}ms"
)
return result
def _generate_search_dates(self, request: FlightSearchRequest) -> List[datetime]:
"""Generate dates to search based on flexibility settings."""
if not request.flexible_dates:
return [request.departure_date]
dates = self.date_generator.generate_date_range(
request.departure_date,
request.date_flexibility_days,
include_weekends=True, # Could be configurable
prefer_weekdays=False # Could be configurable
)
return dates
def _create_date_request(self, request: FlightSearchRequest, search_date: datetime) -> FlightSearchRequest:
"""Create a modified request for a specific search date."""
return FlightSearchRequest(
departure_city=request.departure_city,
arrival_city=request.arrival_city,
departure_date=search_date,
return_date=request.return_date,
passengers=request.passengers,
travel_class=request.travel_class,
strategy=request.strategy,
trip_type=request.trip_type,
max_stops=request.max_stops,
max_duration_hours=request.max_duration_hours,
budget_max=request.budget_max,
flexible_dates=False, # Already handled
date_flexibility_days=0,
multi_city_legs=request.multi_city_legs
)
async def _execute_search(self, request: FlightSearchRequest) -> List[FlightOption]:
"""
Execute search across multiple engines.
This would integrate with your existing SerpAPI and Tavily clients.
"""
# Placeholder implementation - would integrate with your existing clients
flights = []
# Simulate search results for demonstration
sample_flights = [
FlightOption(
airline="Delta Air Lines",
flight_number="DL1234",
departure_city=request.departure_city,
arrival_city=request.arrival_city,
departure_time=request.departure_date.replace(hour=8, minute=30),
arrival_time=request.departure_date.replace(hour=11, minute=45),
price=Decimal("299.99"),
duration_minutes=195,
stops=0
),
FlightOption(
airline="American Airlines",
flight_number="AA5678",
departure_city=request.departure_city,
arrival_city=request.arrival_city,
departure_time=request.departure_date.replace(hour=14, minute=15),
arrival_time=request.departure_date.replace(hour=17, minute=30),
price=Decimal("279.99"),
duration_minutes=195,
stops=0
)
]
return sample_flights
def _rank_flights(
self,
flights: List[FlightOption],
request: FlightSearchRequest,
preferences: Dict[str, Any]
) -> List[FlightOption]:
"""Rank flights based on strategy and preferences."""
# Calculate scores for all flights
scored_flights = []
for flight in flights:
score = self.ranking_engine.calculate_composite_score(
flight, request.strategy, preferences
)
scored_flights.append((flight, score))
# Sort by score (descending)
scored_flights.sort(key=lambda x: x[1], reverse=True)
# Return sorted flights
return [flight for flight, score in scored_flights]
async def search_multi_city_flights(
self,
legs: List[Dict[str, Any]],
strategy: SearchStrategy = SearchStrategy.BEST_VALUE
) -> Dict[str, FlightSearchResult]:
"""
Search for multi-city flight itineraries.
Strategy:
1. Search each leg independently
2. Combine results intelligently
3. Apply multi-city specific ranking
"""
results = {}
for i, leg in enumerate(legs):
request = FlightSearchRequest(
departure_city=leg["departure_city"],
arrival_city=leg["arrival_city"],
departure_date=leg["departure_date"],
passengers=leg.get("passengers", 1),
travel_class=leg.get("travel_class", "Economy"),
strategy=strategy,
trip_type=TripType.ONE_WAY
)
result = await self.search_flights(request)
results[f"leg_{i+1}"] = result
return results
async def search_round_trip_flights(
self,
departure_city: str,
arrival_city: str,
departure_date: datetime,
return_date: datetime,
strategy: SearchStrategy = SearchStrategy.BEST_VALUE,
**kwargs
) -> Dict[str, FlightSearchResult]:
"""
Search for round-trip flights.
Strategy:
1. Search outbound and return legs separately
2. Combine results to find best round-trip combinations
3. Apply round-trip specific logic (e.g., same airline discounts)
"""
# Search outbound
outbound_request = FlightSearchRequest(
departure_city=departure_city,
arrival_city=arrival_city,
departure_date=departure_date,
return_date=None,
strategy=strategy,
trip_type=TripType.ONE_WAY,
**kwargs
)
outbound_result = await self.search_flights(outbound_request)
# Search return
return_request = FlightSearchRequest(
departure_city=arrival_city,
arrival_city=departure_city,
departure_date=return_date,
return_date=None,
strategy=strategy,
trip_type=TripType.ONE_WAY,
**kwargs
)
return_result = await self.search_flights(return_request)
return {
"outbound": outbound_result,
"return": return_result
}
def get_agent_stats(self) -> Dict[str, Any]:
"""Get comprehensive agent statistics."""
cache_stats = self.cache_manager.get_cache_stats()
return {
"cache_stats": cache_stats,
"ranking_weights": self.ranking_engine.weights,
"filter_settings": {
"min_connection_time_minutes": self.filter_engine.min_connection_time_minutes,
"max_connection_time_hours": self.filter_engine.max_connection_time_hours,
"min_flight_duration_minutes": self.filter_engine.min_flight_duration_minutes
}
}
# Example usage and demonstration
async def demonstrate_intelligent_flight_agent():
"""Demonstrate the intelligent flight agent capabilities."""
print("πŸš€ Intelligent Flight Agent Demonstration")
print("=" * 60)
# Initialize agent
agent = IntelligentFlightAgent()
# Example search requests
departure_date = datetime(2024, 1, 15, 8, 0, 0, tzinfo=timezone.utc)
# 1. Basic search
print("\nπŸ“‹ 1. Basic Flight Search:")
basic_request = FlightSearchRequest(
departure_city="New York",
arrival_city="Los Angeles",
departure_date=departure_date,
passengers=1,
strategy=SearchStrategy.BEST_VALUE
)
basic_result = await agent.search_flights(basic_request)
print(f"Found {len(basic_result.flights)} flights")
print(f"Search took {basic_result.search_duration_ms:.0f}ms")
print(f"Cache hit: {basic_result.cache_hit}")
# 2. Flexible date search
print("\nπŸ“… 2. Flexible Date Search:")
flexible_request = FlightSearchRequest(
departure_city="New York",
arrival_city="Los Angeles",
departure_date=departure_date,
passengers=2,
strategy=SearchStrategy.CHEAPEST,
flexible_dates=True,
date_flexibility_days=3
)
flexible_result = await agent.search_flights(flexible_request)
print(f"Found {len(flexible_result.flights)} flights with flexible dates")
print(f"Searched {flexible_result.search_metadata['date_ranges_searched']} date ranges")
# 3. Round-trip search
print("\nπŸ”„ 3. Round-Trip Search:")
return_date = datetime(2024, 1, 22, 18, 0, 0, tzinfo=timezone.utc)
round_trip_results = await agent.search_round_trip_flights(
"New York", "Los Angeles", departure_date, return_date
)
print(f"Outbound: {len(round_trip_results['outbound'].flights)} options")
print(f"Return: {len(round_trip_results['return'].flights)} options")
# 4. Agent statistics
print("\nπŸ“Š 4. Agent Statistics:")
stats = agent.get_agent_stats()
print(f"Cache entries: {stats['cache_stats']['active_entries']}")
print(f"Ranking weights: {stats['ranking_weights']}")
print("\nπŸŽ‰ Intelligent Flight Agent demonstration complete!")
if __name__ == "__main__":
asyncio.run(demonstrate_intelligent_flight_agent())