|
"""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)
|
|
}
|
|
}
|
|
|