config.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757
  1. """
  2. Unified Configuration Management for Trixy ML Integration.
  3. This module provides comprehensive configuration management for all ML components
  4. in the Trixy voice assistant system, including wakeword detection, voice recognition,
  5. audio pipeline, and system integration.
  6. Key Features:
  7. - Unified configuration for all ML components
  8. - Environment-specific configurations (dev, prod, debug)
  9. - Configuration validation and compatibility checking
  10. - Dynamic configuration updates and hot-reloading
  11. - Configuration file management (JSON, YAML)
  12. - Profile-based configuration switching
  13. - Security features for model passwords and paths
  14. - Performance tuning presets
  15. - Integration with application container
  16. Usage:
  17. from trixy_core.ml import MLConfiguration, ConfigManager
  18. # Load configuration
  19. config_manager = ConfigManager()
  20. ml_config = config_manager.load_ml_config("production")
  21. # Create ML components with unified config
  22. ml_manager = MLManager(ml_config.ml_manager)
  23. audio_pipeline = AudioPipeline(ml_config.audio_pipeline)
  24. """
  25. import json
  26. import yaml
  27. import logging
  28. import os
  29. from pathlib import Path
  30. from typing import Dict, Any, Optional, List, Union, Type
  31. from dataclasses import dataclass, field, asdict
  32. from enum import Enum
  33. import hashlib
  34. from datetime import datetime
  35. # Import component configurations
  36. from .ml_manager import MLConfig as MLManagerConfig, MLSystemMode
  37. from .audio_pipeline import AudioPipelineConfig, AudioFormat, BufferType
  38. from .wakeword import WakewordConfig, ModelConfig as WakewordModelConfig
  39. from .voice_recognition import VoiceRecognitionConfig, ModelConfig as VoiceModelConfig
  40. class ConfigFormat(Enum):
  41. """Supported configuration file formats."""
  42. JSON = "json"
  43. YAML = "yaml"
  44. TOML = "toml"
  45. class ConfigProfile(Enum):
  46. """Configuration profiles for different environments."""
  47. DEVELOPMENT = "development"
  48. TESTING = "testing"
  49. PRODUCTION = "production"
  50. DEBUG = "debug"
  51. STANDALONE = "standalone"
  52. CLIENT = "client"
  53. SERVER = "server"
  54. class SecurityLevel(Enum):
  55. """Security levels for configuration."""
  56. NONE = "none"
  57. BASIC = "basic"
  58. ENHANCED = "enhanced"
  59. STRICT = "strict"
  60. @dataclass
  61. class SecurityConfig:
  62. """Security configuration for ML system."""
  63. level: SecurityLevel = SecurityLevel.BASIC
  64. encrypt_model_paths: bool = False
  65. require_model_passwords: bool = True
  66. validate_model_checksums: bool = True
  67. secure_config_storage: bool = False
  68. audit_configuration_changes: bool = True
  69. # Model security
  70. allowed_model_extensions: List[str] = field(default_factory=lambda: [".pth", ".pt", ".onnx"])
  71. model_path_whitelist: List[str] = field(default_factory=list)
  72. require_signed_models: bool = False
  73. # Runtime security
  74. sandbox_model_loading: bool = False
  75. memory_protection: bool = True
  76. secure_temp_files: bool = True
  77. @dataclass
  78. class PerformanceConfig:
  79. """Performance configuration for ML system."""
  80. # General performance
  81. optimization_level: str = "balanced" # "speed", "balanced", "quality"
  82. enable_gpu_acceleration: bool = True
  83. enable_model_quantization: bool = False
  84. enable_model_pruning: bool = False
  85. # Memory management
  86. max_memory_usage_mb: int = 1024
  87. enable_memory_pooling: bool = True
  88. garbage_collection_threshold: int = 100
  89. # Threading
  90. max_worker_threads: int = 4
  91. thread_priority: str = "normal" # "low", "normal", "high"
  92. enable_thread_affinity: bool = False
  93. # Caching
  94. enable_model_caching: bool = True
  95. cache_size_mb: int = 256
  96. cache_ttl_seconds: int = 3600
  97. # Monitoring
  98. enable_performance_monitoring: bool = True
  99. monitoring_interval_seconds: float = 30.0
  100. enable_profiling: bool = False
  101. @dataclass
  102. class NetworkConfig:
  103. """Network configuration for client-server communication."""
  104. # Server connection
  105. server_host: Optional[str] = None
  106. server_port: Optional[int] = None
  107. use_ssl: bool = False
  108. ssl_cert_path: Optional[str] = None
  109. ssl_key_path: Optional[str] = None
  110. # Connection settings
  111. connection_timeout_seconds: float = 10.0
  112. read_timeout_seconds: float = 30.0
  113. max_retry_attempts: int = 3
  114. retry_delay_seconds: float = 1.0
  115. keepalive_interval_seconds: float = 30.0
  116. # Audio streaming
  117. audio_buffer_size: int = 8192
  118. audio_compression: bool = False
  119. audio_encryption: bool = False
  120. # Protocol settings
  121. protocol_version: str = "1.0"
  122. enable_heartbeat: bool = True
  123. enable_message_validation: bool = True
  124. @dataclass
  125. class LoggingConfig:
  126. """Logging configuration for ML system."""
  127. # General logging
  128. level: str = "INFO"
  129. enable_file_logging: bool = True
  130. enable_console_logging: bool = True
  131. log_file_path: str = "/var/log/trixy/ml.log"
  132. # Log formatting
  133. log_format: str = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
  134. date_format: str = "%Y-%m-%d %H:%M:%S"
  135. # Log rotation
  136. max_file_size_mb: int = 100
  137. backup_count: int = 5
  138. # Component logging
  139. ml_manager_level: str = "INFO"
  140. audio_pipeline_level: str = "INFO"
  141. wakeword_level: str = "INFO"
  142. voice_recognition_level: str = "INFO"
  143. # Debug logging
  144. enable_debug_logging: bool = False
  145. debug_log_path: str = "/var/log/trixy/ml_debug.log"
  146. log_audio_metadata: bool = False
  147. log_model_operations: bool = False
  148. @dataclass
  149. class MLConfiguration:
  150. """Unified ML configuration containing all component configurations."""
  151. # Profile information
  152. profile: ConfigProfile = ConfigProfile.PRODUCTION
  153. version: str = "1.0.0"
  154. created_at: str = field(default_factory=lambda: datetime.now().isoformat())
  155. # Component configurations
  156. ml_manager: MLManagerConfig = field(default_factory=lambda: MLManagerConfig(
  157. wakeword_model_path="./models/wakeword/default/model.pth",
  158. wakeword_model_password="",
  159. voice_recognition_model_path="./models/voice_recognition/default/model.pth",
  160. voice_recognition_model_password=""
  161. ))
  162. audio_pipeline: AudioPipelineConfig = field(default_factory=AudioPipelineConfig)
  163. wakeword: WakewordConfig = field(default_factory=lambda: WakewordConfig(
  164. model_path="./models/wakeword/default/model.pth"
  165. ))
  166. voice_recognition: VoiceRecognitionConfig = field(default_factory=lambda: VoiceRecognitionConfig(
  167. model_path="./models/voice_recognition/default/model.pth"
  168. ))
  169. # System configurations
  170. security: SecurityConfig = field(default_factory=SecurityConfig)
  171. performance: PerformanceConfig = field(default_factory=PerformanceConfig)
  172. network: NetworkConfig = field(default_factory=NetworkConfig)
  173. logging: LoggingConfig = field(default_factory=LoggingConfig)
  174. # Metadata
  175. description: str = "Trixy ML Configuration"
  176. tags: List[str] = field(default_factory=list)
  177. custom_settings: Dict[str, Any] = field(default_factory=dict)
  178. def validate(self) -> bool:
  179. """
  180. Validate the configuration.
  181. Returns:
  182. True if configuration is valid
  183. """
  184. try:
  185. # Validate individual component configs
  186. if not self.ml_manager.validate():
  187. return False
  188. # Validate cross-component compatibility
  189. if self.ml_manager.audio_sample_rate != self.audio_pipeline.sample_rate:
  190. return False
  191. if self.ml_manager.audio_chunk_size != self.audio_pipeline.chunk_size:
  192. return False
  193. # Validate security settings
  194. if self.security.require_model_passwords:
  195. if not self.ml_manager.wakeword_model_password:
  196. return False
  197. if not self.ml_manager.voice_recognition_model_password:
  198. return False
  199. # Validate performance settings
  200. if self.performance.max_memory_usage_mb < 256:
  201. return False # Minimum memory requirement
  202. return True
  203. except Exception:
  204. return False
  205. def get_checksum(self) -> str:
  206. """
  207. Calculate configuration checksum for change detection.
  208. Returns:
  209. MD5 checksum of configuration
  210. """
  211. config_dict = asdict(self)
  212. # Remove timestamp fields for consistent checksums
  213. config_dict.pop('created_at', None)
  214. config_str = json.dumps(config_dict, sort_keys=True)
  215. return hashlib.md5(config_str.encode()).hexdigest()
  216. class ConfigValidationError(Exception):
  217. """Configuration validation error."""
  218. pass
  219. class ConfigManager:
  220. """
  221. Configuration manager for ML system.
  222. Handles loading, saving, validating, and managing ML configurations
  223. across different environments and profiles.
  224. """
  225. def __init__(
  226. self,
  227. config_dir: str = "./config",
  228. logger: Optional[logging.Logger] = None
  229. ):
  230. """
  231. Initialize configuration manager.
  232. Args:
  233. config_dir: Directory containing configuration files
  234. logger: Logger instance
  235. """
  236. self.config_dir = Path(config_dir)
  237. self.logger = logger or logging.getLogger(__name__)
  238. # Ensure config directory exists
  239. self.config_dir.mkdir(parents=True, exist_ok=True)
  240. # Configuration cache
  241. self._config_cache: Dict[str, MLConfiguration] = {}
  242. self._config_checksums: Dict[str, str] = {}
  243. # File watchers for hot reloading
  244. self._file_watchers: Dict[str, float] = {}
  245. self.logger.info(f"Configuration manager initialized: {config_dir}")
  246. def create_default_config(self, profile: ConfigProfile = ConfigProfile.PRODUCTION) -> MLConfiguration:
  247. """
  248. Create default configuration for a profile.
  249. Args:
  250. profile: Configuration profile
  251. Returns:
  252. Default ML configuration
  253. """
  254. config = MLConfiguration(profile=profile)
  255. if profile == ConfigProfile.DEVELOPMENT:
  256. config = self._apply_development_settings(config)
  257. elif profile == ConfigProfile.TESTING:
  258. config = self._apply_testing_settings(config)
  259. elif profile == ConfigProfile.PRODUCTION:
  260. config = self._apply_production_settings(config)
  261. elif profile == ConfigProfile.DEBUG:
  262. config = self._apply_debug_settings(config)
  263. elif profile == ConfigProfile.STANDALONE:
  264. config = self._apply_standalone_settings(config)
  265. elif profile == ConfigProfile.CLIENT:
  266. config = self._apply_client_settings(config)
  267. elif profile == ConfigProfile.SERVER:
  268. config = self._apply_server_settings(config)
  269. return config
  270. def load_config(
  271. self,
  272. config_name: str,
  273. config_format: ConfigFormat = ConfigFormat.JSON,
  274. use_cache: bool = True
  275. ) -> MLConfiguration:
  276. """
  277. Load configuration from file.
  278. Args:
  279. config_name: Name of configuration file (without extension)
  280. config_format: Configuration file format
  281. use_cache: Whether to use cached configuration
  282. Returns:
  283. Loaded ML configuration
  284. """
  285. # Check cache first
  286. if use_cache and config_name in self._config_cache:
  287. if not self._is_config_file_modified(config_name, config_format):
  288. return self._config_cache[config_name]
  289. # Load from file
  290. config_file = self._get_config_file_path(config_name, config_format)
  291. if not config_file.exists():
  292. raise FileNotFoundError(f"Configuration file not found: {config_file}")
  293. try:
  294. with open(config_file, 'r') as f:
  295. if config_format == ConfigFormat.JSON:
  296. config_data = json.load(f)
  297. elif config_format == ConfigFormat.YAML:
  298. config_data = yaml.safe_load(f)
  299. else:
  300. raise ValueError(f"Unsupported config format: {config_format}")
  301. # Convert to MLConfiguration
  302. config = self._dict_to_config(config_data)
  303. # Validate configuration
  304. if not config.validate():
  305. raise ConfigValidationError(f"Invalid configuration: {config_name}")
  306. # Cache configuration
  307. if use_cache:
  308. self._config_cache[config_name] = config
  309. self._config_checksums[config_name] = config.get_checksum()
  310. self._file_watchers[config_name] = config_file.stat().st_mtime
  311. self.logger.info(f"Configuration loaded successfully: {config_name}")
  312. return config
  313. except Exception as e:
  314. self.logger.error(f"Failed to load configuration {config_name}: {e}")
  315. raise
  316. def save_config(
  317. self,
  318. config: MLConfiguration,
  319. config_name: str,
  320. config_format: ConfigFormat = ConfigFormat.JSON,
  321. create_backup: bool = True
  322. ) -> None:
  323. """
  324. Save configuration to file.
  325. Args:
  326. config: ML configuration to save
  327. config_name: Name of configuration file
  328. config_format: Configuration file format
  329. create_backup: Whether to create backup of existing file
  330. """
  331. config_file = self._get_config_file_path(config_name, config_format)
  332. # Create backup if requested and file exists
  333. if create_backup and config_file.exists():
  334. backup_file = config_file.with_suffix(f".backup.{config_format.value}")
  335. config_file.rename(backup_file)
  336. self.logger.info(f"Configuration backup created: {backup_file}")
  337. try:
  338. # Convert configuration to dictionary
  339. config_data = asdict(config)
  340. # Save to file
  341. with open(config_file, 'w') as f:
  342. if config_format == ConfigFormat.JSON:
  343. json.dump(config_data, f, indent=2, default=str)
  344. elif config_format == ConfigFormat.YAML:
  345. yaml.dump(config_data, f, default_flow_style=False)
  346. else:
  347. raise ValueError(f"Unsupported config format: {config_format}")
  348. # Update cache
  349. self._config_cache[config_name] = config
  350. self._config_checksums[config_name] = config.get_checksum()
  351. self._file_watchers[config_name] = config_file.stat().st_mtime
  352. self.logger.info(f"Configuration saved successfully: {config_name}")
  353. except Exception as e:
  354. self.logger.error(f"Failed to save configuration {config_name}: {e}")
  355. raise
  356. def validate_config(self, config: MLConfiguration) -> List[str]:
  357. """
  358. Validate configuration and return list of issues.
  359. Args:
  360. config: Configuration to validate
  361. Returns:
  362. List of validation issues (empty if valid)
  363. """
  364. issues = []
  365. try:
  366. # Basic validation
  367. if not config.validate():
  368. issues.append("Basic validation failed")
  369. # Check model file paths
  370. if config.ml_manager.wakeword_model_path and not Path(config.ml_manager.wakeword_model_path).exists():
  371. issues.append(f"Wakeword model file not found: {config.ml_manager.wakeword_model_path}")
  372. if config.ml_manager.voice_recognition_model_path and not Path(config.ml_manager.voice_recognition_model_path).exists():
  373. issues.append(f"Voice recognition model file not found: {config.ml_manager.voice_recognition_model_path}")
  374. # Check security requirements
  375. if config.security.require_model_passwords:
  376. if not config.ml_manager.wakeword_model_password:
  377. issues.append("Wakeword model password required but not provided")
  378. if not config.ml_manager.voice_recognition_model_password:
  379. issues.append("Voice recognition model password required but not provided")
  380. # Check performance constraints
  381. total_threads = (
  382. config.performance.max_worker_threads +
  383. config.audio_pipeline.processing_threads
  384. )
  385. if total_threads > 16: # Reasonable limit
  386. issues.append(f"Too many threads configured: {total_threads}")
  387. # Check memory requirements
  388. estimated_memory = (
  389. config.performance.max_memory_usage_mb +
  390. config.audio_pipeline.max_buffer_size_mb +
  391. config.performance.cache_size_mb
  392. )
  393. if estimated_memory > 4096: # 4GB limit
  394. issues.append(f"Memory usage too high: {estimated_memory}MB")
  395. except Exception as e:
  396. issues.append(f"Validation error: {e}")
  397. return issues
  398. def get_config_profiles(self) -> List[str]:
  399. """
  400. Get list of available configuration profiles.
  401. Returns:
  402. List of configuration profile names
  403. """
  404. profiles = []
  405. for config_file in self.config_dir.glob("*.json"):
  406. profiles.append(config_file.stem)
  407. for config_file in self.config_dir.glob("*.yaml"):
  408. if config_file.stem not in profiles:
  409. profiles.append(config_file.stem)
  410. return sorted(profiles)
  411. def create_profile_configs(self) -> None:
  412. """Create default configuration files for all profiles."""
  413. for profile in ConfigProfile:
  414. config = self.create_default_config(profile)
  415. self.save_config(config, profile.value, ConfigFormat.JSON)
  416. self.logger.info(f"Created default configuration for profile: {profile.value}")
  417. def update_config(
  418. self,
  419. config_name: str,
  420. updates: Dict[str, Any],
  421. config_format: ConfigFormat = ConfigFormat.JSON
  422. ) -> MLConfiguration:
  423. """
  424. Update specific configuration values.
  425. Args:
  426. config_name: Name of configuration to update
  427. updates: Dictionary of updates to apply
  428. config_format: Configuration file format
  429. Returns:
  430. Updated configuration
  431. """
  432. # Load current configuration
  433. config = self.load_config(config_name, config_format)
  434. # Apply updates
  435. config_dict = asdict(config)
  436. self._deep_update(config_dict, updates)
  437. # Convert back to configuration object
  438. updated_config = self._dict_to_config(config_dict)
  439. # Validate updated configuration
  440. issues = self.validate_config(updated_config)
  441. if issues:
  442. raise ConfigValidationError(f"Updated configuration is invalid: {issues}")
  443. # Save updated configuration
  444. self.save_config(updated_config, config_name, config_format)
  445. return updated_config
  446. def _apply_development_settings(self, config: MLConfiguration) -> MLConfiguration:
  447. """Apply development-specific settings."""
  448. config.logging.level = "DEBUG"
  449. config.logging.enable_debug_logging = True
  450. config.performance.enable_profiling = True
  451. config.security.level = SecurityLevel.BASIC
  452. config.tags.append("development")
  453. return config
  454. def _apply_testing_settings(self, config: MLConfiguration) -> MLConfiguration:
  455. """Apply testing-specific settings."""
  456. config.logging.level = "INFO"
  457. config.performance.optimization_level = "speed"
  458. config.security.level = SecurityLevel.BASIC
  459. config.tags.append("testing")
  460. return config
  461. def _apply_production_settings(self, config: MLConfiguration) -> MLConfiguration:
  462. """Apply production-specific settings."""
  463. config.logging.level = "WARNING"
  464. config.logging.enable_debug_logging = False
  465. config.performance.optimization_level = "balanced"
  466. config.security.level = SecurityLevel.ENHANCED
  467. config.security.audit_configuration_changes = True
  468. config.performance.enable_performance_monitoring = True
  469. config.tags.append("production")
  470. return config
  471. def _apply_debug_settings(self, config: MLConfiguration) -> MLConfiguration:
  472. """Apply debug-specific settings."""
  473. config.logging.level = "DEBUG"
  474. config.logging.enable_debug_logging = True
  475. config.logging.log_audio_metadata = True
  476. config.logging.log_model_operations = True
  477. config.performance.enable_profiling = True
  478. config.audio_pipeline.enable_debug_logging = True
  479. config.audio_pipeline.save_debug_audio = True
  480. config.tags.append("debug")
  481. return config
  482. def _apply_standalone_settings(self, config: MLConfiguration) -> MLConfiguration:
  483. """Apply standalone-specific settings."""
  484. config.ml_manager.system_mode = MLSystemMode.STANDALONE
  485. config.network.server_host = None
  486. config.network.server_port = None
  487. config.tags.append("standalone")
  488. return config
  489. def _apply_client_settings(self, config: MLConfiguration) -> MLConfiguration:
  490. """Apply client-specific settings."""
  491. config.ml_manager.system_mode = MLSystemMode.CLIENT
  492. config.network.server_host = "localhost"
  493. config.network.server_port = 2101
  494. config.tags.append("client")
  495. return config
  496. def _apply_server_settings(self, config: MLConfiguration) -> MLConfiguration:
  497. """Apply server-specific settings."""
  498. # Server typically doesn't run ML components directly
  499. config.performance.max_memory_usage_mb = 2048
  500. config.network.server_host = "0.0.0.0"
  501. config.network.server_port = 2101
  502. config.tags.append("server")
  503. return config
  504. def _get_config_file_path(self, config_name: str, config_format: ConfigFormat) -> Path:
  505. """Get full path to configuration file."""
  506. return self.config_dir / f"{config_name}.{config_format.value}"
  507. def _is_config_file_modified(self, config_name: str, config_format: ConfigFormat) -> bool:
  508. """Check if configuration file has been modified."""
  509. config_file = self._get_config_file_path(config_name, config_format)
  510. if not config_file.exists():
  511. return True
  512. if config_name not in self._file_watchers:
  513. return True
  514. return config_file.stat().st_mtime > self._file_watchers[config_name]
  515. def _dict_to_config(self, config_dict: Dict[str, Any]) -> MLConfiguration:
  516. """Convert dictionary to MLConfiguration object."""
  517. # This is a simplified conversion - in practice, you'd want more robust handling
  518. try:
  519. # Handle enum conversions
  520. if 'profile' in config_dict and isinstance(config_dict['profile'], str):
  521. config_dict['profile'] = ConfigProfile(config_dict['profile'])
  522. # Extract component configurations
  523. ml_manager_data = config_dict.get('ml_manager', {})
  524. audio_pipeline_data = config_dict.get('audio_pipeline', {})
  525. wakeword_data = config_dict.get('wakeword', {})
  526. voice_recognition_data = config_dict.get('voice_recognition', {})
  527. security_data = config_dict.get('security', {})
  528. performance_data = config_dict.get('performance', {})
  529. network_data = config_dict.get('network', {})
  530. logging_data = config_dict.get('logging', {})
  531. # Create component configurations with basic handling
  532. ml_manager_config = MLManagerConfig(
  533. wakeword_model_path=ml_manager_data.get('wakeword_model_path', ''),
  534. wakeword_model_password=ml_manager_data.get('wakeword_model_password', ''),
  535. voice_recognition_model_path=ml_manager_data.get('voice_recognition_model_path', ''),
  536. voice_recognition_model_password=ml_manager_data.get('voice_recognition_model_password', ''),
  537. **{k: v for k, v in ml_manager_data.items() if k not in [
  538. 'wakeword_model_path', 'wakeword_model_password',
  539. 'voice_recognition_model_path', 'voice_recognition_model_password'
  540. ]}
  541. )
  542. audio_pipeline_config = AudioPipelineConfig(**audio_pipeline_data)
  543. wakeword_config = WakewordConfig(
  544. model_path=wakeword_data.get('model_path', ''),
  545. **{k: v for k, v in wakeword_data.items() if k != 'model_path'}
  546. )
  547. voice_recognition_config = VoiceRecognitionConfig(
  548. model_path=voice_recognition_data.get('model_path', ''),
  549. **{k: v for k, v in voice_recognition_data.items() if k != 'model_path'}
  550. )
  551. # Handle security config enums
  552. if 'level' in security_data and isinstance(security_data['level'], str):
  553. security_data['level'] = SecurityLevel(security_data['level'])
  554. security_config = SecurityConfig(**security_data)
  555. performance_config = PerformanceConfig(**performance_data)
  556. network_config = NetworkConfig(**network_data)
  557. logging_config = LoggingConfig(**logging_data)
  558. # Create main configuration
  559. config = MLConfiguration(
  560. profile=config_dict.get('profile', ConfigProfile.PRODUCTION),
  561. version=config_dict.get('version', '1.0.0'),
  562. created_at=config_dict.get('created_at', datetime.now().isoformat()),
  563. ml_manager=ml_manager_config,
  564. audio_pipeline=audio_pipeline_config,
  565. wakeword=wakeword_config,
  566. voice_recognition=voice_recognition_config,
  567. security=security_config,
  568. performance=performance_config,
  569. network=network_config,
  570. logging=logging_config,
  571. description=config_dict.get('description', 'Trixy ML Configuration'),
  572. tags=config_dict.get('tags', []),
  573. custom_settings=config_dict.get('custom_settings', {})
  574. )
  575. return config
  576. except Exception as e:
  577. raise ConfigValidationError(f"Failed to convert dictionary to configuration: {e}")
  578. def _deep_update(self, base_dict: Dict[str, Any], update_dict: Dict[str, Any]) -> None:
  579. """Deep update dictionary with nested values."""
  580. for key, value in update_dict.items():
  581. if key in base_dict and isinstance(base_dict[key], dict) and isinstance(value, dict):
  582. self._deep_update(base_dict[key], value)
  583. else:
  584. base_dict[key] = value
  585. def create_config_manager(config_dir: str = "./config") -> ConfigManager:
  586. """
  587. Create configuration manager.
  588. Args:
  589. config_dir: Configuration directory path
  590. Returns:
  591. Configuration manager instance
  592. """
  593. return ConfigManager(config_dir)
  594. def load_ml_config(
  595. config_name: str = "production",
  596. config_dir: str = "./config"
  597. ) -> MLConfiguration:
  598. """
  599. Load ML configuration.
  600. Args:
  601. config_name: Configuration name
  602. config_dir: Configuration directory
  603. Returns:
  604. ML configuration
  605. """
  606. config_manager = ConfigManager(config_dir)
  607. return config_manager.load_config(config_name)
  608. def create_default_configs(config_dir: str = "./config") -> None:
  609. """
  610. Create default configuration files for all profiles.
  611. Args:
  612. config_dir: Configuration directory
  613. """
  614. config_manager = ConfigManager(config_dir)
  615. config_manager.create_profile_configs()