Spaces:
Running
Running
| """ | |
| Centralized configuration for API keys, models, and application settings | |
| """ | |
| import os | |
| import logging | |
| from typing import Optional | |
| from pathlib import Path | |
| from pydantic_settings import BaseSettings | |
| from pydantic import Field | |
| # ============================================================================ | |
| # Settings Class | |
| # ============================================================================ | |
| class Settings(BaseSettings): | |
| """ | |
| Application settings loaded from environment variables | |
| Environment variables can be set in: | |
| 1. System environment | |
| 2. .env file in project root | |
| 3. Passed explicitly when creating Settings instance | |
| """ | |
| # ======================================================================== | |
| # API Keys | |
| # ======================================================================== | |
| # Anthropic (Required) | |
| anthropic_api_key: Optional[str] = Field( | |
| None, | |
| alias="ANTHROPIC_API_KEY", | |
| description="Anthropic API key for Claude models" | |
| ) | |
| gemini_api_key: Optional[str] = Field( | |
| None, | |
| alias="GOOGLE_API_KEY", | |
| description="Google API key for Gemini models" | |
| ) | |
| # Google Maps (Required for Scout and Traffic agents) | |
| google_maps_api_key: Optional[str] = Field( | |
| None, | |
| alias="GOOGLE_MAPS_API_KEY", | |
| description="Google Maps Platform API key" | |
| ) | |
| # OpenWeatherMap (Optional, for Weather agent) | |
| openweather_api_key: Optional[str] = Field( | |
| None, | |
| alias="OPENWEATHER_API_KEY", | |
| description="OpenWeatherMap API key (optional)" | |
| ) | |
| # Mapbox (Optional, for UI visualization) | |
| mapbox_token: Optional[str] = Field( | |
| None, | |
| alias="MAPBOX_TOKEN", | |
| description="Mapbox access token for map visualization" | |
| ) | |
| # ======================================================================== | |
| # Model Configuration | |
| # ======================================================================== | |
| # Claude model for agents and team | |
| model_name: str = Field( | |
| "claude-sonnet-4-20250514", | |
| alias="MODEL_NAME", | |
| description="Claude model name to use" | |
| ) | |
| # Alternative models for cost optimization (future) | |
| planner_model: Optional[str] = Field( | |
| None, | |
| alias="PLANNER_MODEL", | |
| description="Specific model for Planner agent (optional)" | |
| ) | |
| optimizer_model: Optional[str] = Field( | |
| None, | |
| alias="OPTIMIZER_MODEL", | |
| description="Specific model for Optimizer agent (optional)" | |
| ) | |
| # ======================================================================== | |
| # Application Settings | |
| # ======================================================================== | |
| # Logging | |
| log_level: str = Field( | |
| "INFO", | |
| alias="LOG_LEVEL", | |
| description="Logging level (DEBUG/INFO/WARNING/ERROR)" | |
| ) | |
| log_file: Optional[str] = Field( | |
| None, | |
| alias="LOG_FILE", | |
| description="Log file path (optional, logs to console if not set)" | |
| ) | |
| # Search parameters | |
| poi_search_radius: int = Field( | |
| 5000, | |
| alias="POI_SEARCH_RADIUS", | |
| description="Default POI search radius in meters" | |
| ) | |
| max_poi_results: int = Field( | |
| 5, | |
| alias="MAX_POI_RESULTS", | |
| description="Maximum number of POI candidates per task" | |
| ) | |
| # Optimization parameters | |
| tsptw_time_limit: int = Field( | |
| 10, | |
| alias="TSPTW_TIME_LIMIT", | |
| description="TSPTW solver time limit in seconds" | |
| ) | |
| tsptw_verbose: bool = Field( | |
| False, | |
| alias="TSPTW_VERBOSE", | |
| description="Enable verbose output for TSPTW solver" | |
| ) | |
| # Default locations | |
| default_start_lat: float = Field( | |
| 25.0330, | |
| alias="DEFAULT_START_LAT", | |
| description="Default start location latitude (Taipei)" | |
| ) | |
| default_start_lng: float = Field( | |
| 121.5654, | |
| alias="DEFAULT_START_LNG", | |
| description="Default start location longitude (Taipei)" | |
| ) | |
| # ======================================================================== | |
| # Feature Flags | |
| # ======================================================================== | |
| enable_weather_agent: bool = Field( | |
| True, | |
| alias="ENABLE_WEATHER_AGENT", | |
| description="Enable Weather agent (requires OpenWeatherMap API key)" | |
| ) | |
| enable_traffic_agent: bool = Field( | |
| True, | |
| alias="ENABLE_TRAFFIC_AGENT", | |
| description="Enable Traffic agent (requires Google Maps API key)" | |
| ) | |
| use_mock_data: bool = Field( | |
| False, | |
| alias="USE_MOCK_DATA", | |
| description="Use mock data instead of real APIs (for testing)" | |
| ) | |
| TASK_LIST_KEY: str = Field("task_list", description="Session state key(name) for task list") | |
| # ======================================================================== | |
| # Pydantic Config | |
| # ======================================================================== | |
| class Config: | |
| env_file = str(Path(__file__).resolve().parents[2] / ".env") | |
| env_file_encoding = "utf-8" | |
| case_sensitive = False | |
| # Allow extra fields (for future extensions) | |
| extra = "ignore" | |
| # ============================================================================ | |
| # Global Settings Instance | |
| # ============================================================================ | |
| _settings: Optional[Settings] = None | |
| def get_settings() -> Settings: | |
| """ | |
| Get application settings (singleton pattern) | |
| Returns: | |
| Settings: Application settings | |
| Raises: | |
| ValueError: If required environment variables are missing | |
| """ | |
| global _settings | |
| if _settings is None: | |
| try: | |
| _settings = Settings() | |
| except Exception as e: | |
| raise ValueError( | |
| f"Failed to load settings. Please check your .env file or environment variables.\n" | |
| f"Error: {str(e)}\n\n" | |
| f"Required variables:\n" | |
| f" - ANTHROPIC_API_KEY\n" | |
| f" - GOOGLE_MAPS_API_KEY\n\n" | |
| f"Optional variables:\n" | |
| f" - OPENWEATHER_API_KEY\n" | |
| f" - MAPBOX_TOKEN\n" | |
| ) from e | |
| return _settings | |
| def reload_settings(): | |
| """ | |
| Reload settings from environment | |
| Useful for testing or when environment variables change | |
| """ | |
| global _settings | |
| _settings = None | |
| return get_settings() | |
| # ============================================================================ | |
| # Logging Configuration | |
| # ============================================================================ | |
| def setup_logging(settings: Optional[Settings] = None): | |
| """ | |
| Setup application logging | |
| Args: | |
| settings: Settings instance (uses global if not provided) | |
| """ | |
| if settings is None: | |
| settings = get_settings() | |
| # Configure logging format | |
| log_format = ( | |
| "%(asctime)s - %(name)s - %(levelname)s - %(message)s" | |
| ) | |
| # Configure handlers | |
| handlers = [] | |
| # Console handler | |
| console_handler = logging.StreamHandler() | |
| console_handler.setFormatter(logging.Formatter(log_format)) | |
| handlers.append(console_handler) | |
| # File handler (if specified) | |
| if settings.log_file: | |
| log_path = Path(settings.log_file) | |
| log_path.parent.mkdir(parents=True, exist_ok=True) | |
| file_handler = logging.FileHandler(settings.log_file) | |
| file_handler.setFormatter(logging.Formatter(log_format)) | |
| handlers.append(file_handler) | |
| # Configure root logger | |
| logging.basicConfig( | |
| level=getattr(logging, settings.log_level.upper()), | |
| format=log_format, | |
| handlers=handlers | |
| ) | |
| # Set specific loggers | |
| logging.getLogger("agno").setLevel(logging.WARNING) | |
| logging.getLogger("anthropic").setLevel(logging.WARNING) | |
| logging.getLogger("httpx").setLevel(logging.WARNING) | |
| logger = logging.getLogger(__name__) | |
| logger.info(f"Logging configured: level={settings.log_level}") | |
| if settings.log_file: | |
| logger.info(f"Logging to file: {settings.log_file}") | |
| # ============================================================================ | |
| # Validation Helpers | |
| # ============================================================================ | |
| def validate_api_keys(settings: Optional[Settings] = None) -> dict: | |
| """ | |
| Validate that all required API keys are set | |
| Args: | |
| settings: Settings instance | |
| Returns: | |
| dict: Validation results | |
| """ | |
| if settings is None: | |
| settings = get_settings() | |
| results = { | |
| "valid": True, | |
| "missing": [], | |
| "optional_missing": [] | |
| } | |
| # Check required keys | |
| if not settings.anthropic_api_key: | |
| results["valid"] = False | |
| results["missing"].append("ANTHROPIC_API_KEY") | |
| if not settings.google_maps_api_key: | |
| results["valid"] = False | |
| results["missing"].append("GOOGLE_MAPS_API_KEY") | |
| # Check optional keys | |
| if settings.enable_weather_agent and not settings.openweather_api_key: | |
| results["optional_missing"].append("OPENWEATHER_API_KEY") | |
| if not settings.mapbox_token: | |
| results["optional_missing"].append("MAPBOX_TOKEN") | |
| return results | |
| def print_config_status(settings: Optional[Settings] = None): | |
| """ | |
| Print current configuration status | |
| Args: | |
| settings: Settings instance | |
| """ | |
| if settings is None: | |
| settings = get_settings() | |
| print("=" * 60) | |
| print("π LifeFlow AI - Configuration Status") | |
| print("=" * 60) | |
| # API Keys | |
| print("\nπ API Keys:") | |
| print(f" Anthropic: {'β Set' if settings.anthropic_api_key else 'β Missing'}") | |
| print(f" Google Maps: {'β Set' if settings.google_maps_api_key else 'β Missing'}") | |
| print(f" OpenWeatherMap: {'β Set' if settings.openweather_api_key else 'β οΈ Not set (optional)'}") | |
| print(f" Mapbox: {'β Set' if settings.mapbox_token else 'β οΈ Not set (optional)'}") | |
| # Model Configuration | |
| print("\nπ€ Model Configuration:") | |
| print(f" Default Model: {settings.model_name}") | |
| if settings.planner_model: | |
| print(f" Planner Model: {settings.planner_model}") | |
| if settings.optimizer_model: | |
| print(f" Optimizer Model: {settings.optimizer_model}") | |
| # Application Settings | |
| print("\nβοΈ Application Settings:") | |
| print(f" Log Level: {settings.log_level}") | |
| print(f" Log File: {settings.log_file or 'Console only'}") | |
| print(f" Search Radius: {settings.poi_search_radius}m") | |
| print(f" Max POI Results: {settings.max_poi_results}") | |
| print(f" TSPTW Time Limit: {settings.tsptw_time_limit}s") | |
| # Feature Flags | |
| print("\nποΈ Feature Flags:") | |
| print(f" Weather Agent: {'β Enabled' if settings.enable_weather_agent else 'β Disabled'}") | |
| print(f" Traffic Agent: {'β Enabled' if settings.enable_traffic_agent else 'β Disabled'}") | |
| print(f" Mock Data Mode: {'β οΈ Enabled' if settings.use_mock_data else 'β Disabled'}") | |
| # Validation | |
| validation = validate_api_keys(settings) | |
| print("\nβ Validation:") | |
| if validation["valid"]: | |
| print(" All required API keys are set") | |
| else: | |
| print(f" β Missing required keys: {', '.join(validation['missing'])}") | |
| if validation["optional_missing"]: | |
| print(f" β οΈ Missing optional keys: {', '.join(validation['optional_missing'])}") | |
| print("=" * 60) | |
| # ============================================================================ | |
| # Test | |
| # ============================================================================ | |
| if __name__ == "__main__": | |
| try: | |
| settings = get_settings() | |
| print_config_status(settings) | |
| # Test validation | |
| validation = validate_api_keys(settings) | |
| if not validation["valid"]: | |
| print("\nβ Configuration is invalid!") | |
| print("Please set the following environment variables:") | |
| for key in validation["missing"]: | |
| print(f" - {key}") | |
| else: | |
| print("\nβ Configuration is valid!") | |
| except Exception as e: | |
| print(f"\nβ Error loading configuration:") | |
| print(f" {str(e)}") | |
| print("\nPlease create a .env file with required variables.") | |
| raise e |