| """Advanced configuration management with validation and environment-specific settings."""
|
|
|
| import os
|
| import json
|
| from pathlib import Path
|
| from typing import Dict, Any, Optional
|
| from dataclasses import dataclass, field
|
| from .logging_config import logger
|
|
|
| @dataclass
|
| class APIConfig:
|
| """API configuration with validation."""
|
| nebius_api_key: str = ""
|
| nebius_base_url: str = "https://api.studio.nebius.ai/v1/"
|
| tavily_api_key: str = ""
|
|
|
|
|
| nebius_model: str = "meta-llama/Meta-Llama-3.1-8B-Instruct"
|
| nebius_max_tokens: int = 1000
|
| nebius_temperature: float = 0.7
|
|
|
| tavily_search_depth: str = "basic"
|
| tavily_max_results: int = 5
|
|
|
| def __post_init__(self):
|
| """Validate configuration after initialization."""
|
| if not self.nebius_api_key:
|
| raise ValueError("NEBIUS_API_KEY is required")
|
| if not self.tavily_api_key:
|
| raise ValueError("TAVILY_API_KEY is required")
|
|
|
|
|
| if not 0.0 <= self.nebius_temperature <= 2.0:
|
| raise ValueError("nebius_temperature must be between 0.0 and 2.0")
|
| if self.nebius_max_tokens <= 0:
|
| raise ValueError("nebius_max_tokens must be positive")
|
| if self.tavily_max_results <= 0:
|
| raise ValueError("tavily_max_results must be positive")
|
|
|
| @dataclass
|
| class AppConfig:
|
| """Application configuration."""
|
| environment: str = "development"
|
| debug: bool = True
|
| log_level: str = "INFO"
|
|
|
|
|
| gradio_server_name: str = "0.0.0.0"
|
| gradio_server_port: int = 7860
|
| gradio_share: bool = False
|
| gradio_auth: Optional[tuple] = None
|
|
|
|
|
| max_search_results: int = 10
|
| max_sub_questions: int = 5
|
| cache_ttl_seconds: int = 3600
|
| request_timeout_seconds: int = 30
|
|
|
|
|
| api_calls_per_second: float = 2.0
|
| api_burst_size: int = 5
|
|
|
|
|
| circuit_breaker_failure_threshold: int = 5
|
| circuit_breaker_timeout_seconds: int = 60
|
|
|
|
|
| metrics_retention_hours: int = 24
|
| health_check_interval_seconds: int = 300
|
|
|
| def __post_init__(self):
|
| """Validate application configuration."""
|
| valid_environments = ["development", "staging", "production"]
|
| if self.environment not in valid_environments:
|
| raise ValueError(f"environment must be one of: {valid_environments}")
|
|
|
| valid_log_levels = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
|
| if self.log_level not in valid_log_levels:
|
| raise ValueError(f"log_level must be one of: {valid_log_levels}")
|
|
|
| if self.gradio_server_port <= 0 or self.gradio_server_port > 65535:
|
| raise ValueError("gradio_server_port must be between 1 and 65535")
|
|
|
| @dataclass
|
| class SecurityConfig:
|
| """Security configuration."""
|
| enable_authentication: bool = False
|
| allowed_origins: list = field(default_factory=lambda: ["*"])
|
| api_key_header: str = "X-API-Key"
|
| rate_limit_per_ip: int = 100
|
| max_request_size_mb: int = 10
|
|
|
|
|
| enable_content_filtering: bool = True
|
| blocked_patterns: list = field(default_factory=list)
|
|
|
| def __post_init__(self):
|
| """Validate security configuration."""
|
| if self.rate_limit_per_ip <= 0:
|
| raise ValueError("rate_limit_per_ip must be positive")
|
| if self.max_request_size_mb <= 0:
|
| raise ValueError("max_request_size_mb must be positive")
|
|
|
| class ConfigManager:
|
| """Centralized configuration management with environment-specific overrides."""
|
|
|
| def __init__(self, config_dir: str = "config"):
|
| """
|
| Initialize configuration manager.
|
|
|
| Args:
|
| config_dir: Directory containing configuration files
|
| """
|
| self.config_dir = Path(config_dir)
|
| self.config_dir.mkdir(exist_ok=True)
|
|
|
|
|
| self._load_environment_variables()
|
|
|
|
|
| self.api_config = self._load_api_config()
|
| self.app_config = self._load_app_config()
|
| self.security_config = self._load_security_config()
|
|
|
| logger.info(f"Configuration loaded for environment: {self.app_config.environment}")
|
|
|
| def _load_environment_variables(self):
|
| """Load environment variables from .env file if it exists."""
|
| env_file = Path(".env")
|
| if env_file.exists():
|
| from dotenv import load_dotenv
|
| load_dotenv()
|
| logger.info("Loaded environment variables from .env file")
|
|
|
| def _load_api_config(self) -> APIConfig:
|
| """Load API configuration from environment and config files."""
|
|
|
| config_data = {
|
| "nebius_api_key": os.getenv("NEBIUS_API_KEY", ""),
|
| "nebius_base_url": os.getenv("NEBIUS_BASE_URL", "https://api.studio.nebius.ai/v1/"),
|
| "tavily_api_key": os.getenv("TAVILY_API_KEY", ""),
|
| "nebius_model": os.getenv("NEBIUS_MODEL", "meta-llama/Meta-Llama-3.1-8B-Instruct"),
|
| "nebius_max_tokens": int(os.getenv("NEBIUS_MAX_TOKENS", "1000")),
|
| "nebius_temperature": float(os.getenv("NEBIUS_TEMPERATURE", "0.7")),
|
| "tavily_search_depth": os.getenv("TAVILY_SEARCH_DEPTH", "basic"),
|
| "tavily_max_results": int(os.getenv("TAVILY_MAX_RESULTS", "5"))
|
| }
|
|
|
|
|
| config_file = self.config_dir / "api_config.json"
|
| if config_file.exists():
|
| try:
|
| with open(config_file, 'r') as f:
|
| file_config = json.load(f)
|
| config_data.update(file_config)
|
| logger.info("Loaded API configuration from config file")
|
| except Exception as e:
|
| logger.warning(f"Failed to load API config file: {e}")
|
|
|
| return APIConfig(**config_data)
|
|
|
| def _load_app_config(self) -> AppConfig:
|
| """Load application configuration."""
|
| environment = os.getenv("ENVIRONMENT", "development")
|
|
|
|
|
| config_data = {
|
| "environment": environment,
|
| "debug": environment == "development",
|
| "log_level": os.getenv("LOG_LEVEL", "INFO"),
|
| "gradio_server_name": os.getenv("GRADIO_SERVER_NAME", "0.0.0.0"),
|
| "gradio_server_port": int(os.getenv("GRADIO_SERVER_PORT", "7860")),
|
| "gradio_share": os.getenv("GRADIO_SHARE", "false").lower() == "true",
|
| "max_search_results": int(os.getenv("MAX_SEARCH_RESULTS", "10")),
|
| "max_sub_questions": int(os.getenv("MAX_SUB_QUESTIONS", "5")),
|
| "cache_ttl_seconds": int(os.getenv("CACHE_TTL_SECONDS", "3600")),
|
| "request_timeout_seconds": int(os.getenv("REQUEST_TIMEOUT_SECONDS", "30"))
|
| }
|
|
|
|
|
| env_config_file = self.config_dir / f"app_config_{environment}.json"
|
| if env_config_file.exists():
|
| try:
|
| with open(env_config_file, 'r') as f:
|
| env_config = json.load(f)
|
| config_data.update(env_config)
|
| logger.info(f"Loaded environment-specific config: {environment}")
|
| except Exception as e:
|
| logger.warning(f"Failed to load environment config: {e}")
|
|
|
| return AppConfig(**config_data)
|
|
|
| def _load_security_config(self) -> SecurityConfig:
|
| """Load security configuration."""
|
| config_data = {
|
| "enable_authentication": os.getenv("ENABLE_AUTH", "false").lower() == "true",
|
| "rate_limit_per_ip": int(os.getenv("RATE_LIMIT_PER_IP", "100")),
|
| "max_request_size_mb": int(os.getenv("MAX_REQUEST_SIZE_MB", "10")),
|
| "enable_content_filtering": os.getenv("ENABLE_CONTENT_FILTERING", "true").lower() == "true"
|
| }
|
|
|
|
|
| config_file = self.config_dir / "security_config.json"
|
| if config_file.exists():
|
| try:
|
| with open(config_file, 'r') as f:
|
| file_config = json.load(f)
|
| config_data.update(file_config)
|
| logger.info("Loaded security configuration from config file")
|
| except Exception as e:
|
| logger.warning(f"Failed to load security config: {e}")
|
|
|
| return SecurityConfig(**config_data)
|
|
|
| def save_config_template(self):
|
| """Save configuration templates for easy editing."""
|
| templates = {
|
| "api_config.json": {
|
| "nebius_model": "meta-llama/Meta-Llama-3.1-8B-Instruct",
|
| "nebius_max_tokens": 1000,
|
| "nebius_temperature": 0.7,
|
| "tavily_search_depth": "basic",
|
| "tavily_max_results": 5
|
| },
|
| "app_config_development.json": {
|
| "debug": True,
|
| "log_level": "DEBUG",
|
| "gradio_share": False,
|
| "max_search_results": 5
|
| },
|
| "app_config_production.json": {
|
| "debug": False,
|
| "log_level": "INFO",
|
| "gradio_share": False,
|
| "max_search_results": 10,
|
| "cache_ttl_seconds": 7200
|
| },
|
| "security_config.json": {
|
| "enable_authentication": False,
|
| "allowed_origins": ["*"],
|
| "rate_limit_per_ip": 100,
|
| "enable_content_filtering": True,
|
| "blocked_patterns": []
|
| }
|
| }
|
|
|
| for filename, template in templates.items():
|
| config_file = self.config_dir / filename
|
| if not config_file.exists():
|
| try:
|
| with open(config_file, 'w') as f:
|
| json.dump(template, f, indent=2)
|
| logger.info(f"Created config template: {filename}")
|
| except Exception as e:
|
| logger.error(f"Failed to create config template {filename}: {e}")
|
|
|
| def get_config_summary(self) -> Dict[str, Any]:
|
| """Get a summary of current configuration (without sensitive data)."""
|
| return {
|
| "environment": self.app_config.environment,
|
| "debug_mode": self.app_config.debug,
|
| "log_level": self.app_config.log_level,
|
| "gradio_port": self.app_config.gradio_server_port,
|
| "cache_ttl": self.app_config.cache_ttl_seconds,
|
| "max_search_results": self.app_config.max_search_results,
|
| "authentication_enabled": self.security_config.enable_authentication,
|
| "content_filtering_enabled": self.security_config.enable_content_filtering,
|
| "api_endpoints": {
|
| "nebius": bool(self.api_config.nebius_api_key),
|
| "tavily": bool(self.api_config.tavily_api_key)
|
| }
|
| }
|
|
|