""" ML Manager for Client-side ML Integration in Trixy Voice Assistant. This module coordinates wakeword detection and voice recognition systems, providing a unified interface for client-side AI processing. It manages the complete workflow from audio input to event generation. Key Features: - Unified coordination of wakeword and voice recognition - Real-time audio pipeline management - Event system integration - Performance monitoring and optimization - Error handling and fallback mechanisms - Configuration management for ML models - Network protocol integration - Application container support Usage: from trixy_core.ml import MLManager, MLConfig # Create configuration config = MLConfig( wakeword_model_path="/path/to/wakeword.pth", voice_recognition_model_path="/path/to/voice.pth", audio_sample_rate=16000 ) # Initialize ML manager ml_manager = MLManager(config, event_handler, app_container) # Start ML processing await ml_manager.start() # Process audio chunk await ml_manager.process_audio_chunk(audio_data) """ import asyncio import logging import time import threading from typing import Optional, Dict, Any, List, Callable, Union from dataclasses import dataclass from enum import Enum import numpy as np from .wakeword import ( WakewordDetector, WakewordConfig, create_wakeword_detector, WakewordDetectorError, DetectionResult, WakewordType ) from .wakeword.config import ( ModelConfig as WakewordModelConfig, AudioConfig as WakewordAudioConfig, PerformanceConfig as WakewordPerformanceConfig ) from .wakeword.detection_engine import DetectionConfig as WakewordDetectionConfig from .wakeword.audio_features import SpectrogramConfig from .performance_monitor import PerformanceMonitor, MetricType from .voice_recognition import ( VoiceRecognitionDetector, VoiceRecognitionConfig, create_voice_recognition_detector, VoiceRecognitionDetectorError, IdentificationResult ) from .voice_recognition.config import ( ModelConfig as VoiceModelConfig, AudioConfig as VoiceAudioConfig, RecognitionConfig, PerformanceConfig as VoicePerformanceConfig ) class MLProcessingState(Enum): """ML processing state enumeration.""" IDLE = "idle" LISTENING = "listening" WAKEWORD_DETECTED = "wakeword_detected" RECORDING = "recording" PROCESSING = "processing" ERROR = "error" class MLSystemMode(Enum): """ML system operating mode.""" CLIENT = "client" STANDALONE = "standalone" DEBUG = "debug" @dataclass class MLConfig: """Configuration for ML Manager.""" # Wakeword configuration wakeword_model_path: str = "" wakeword_model_password: str = "" wakeword_confidence_threshold: float = 0.7 # Voice recognition configuration voice_recognition_model_path: str = "" voice_recognition_model_password: str = "" voice_recognition_confidence_threshold: float = 0.8 # Audio configuration audio_sample_rate: int = 16000 audio_chunk_size: int = 1024 audio_channels: int = 1 audio_format: str = "int16" # Processing configuration buffer_duration_seconds: float = 10.0 silence_timeout_seconds: float = 3.0 max_recording_seconds: float = 60.0 # Performance configuration enable_optimization: bool = True enable_monitoring: bool = True memory_limit_mb: int = 512 processing_timeout_seconds: float = 30.0 # System configuration system_mode: MLSystemMode = MLSystemMode.CLIENT debug_mode: bool = False # Network configuration server_host: Optional[str] = None server_port: Optional[int] = None def validate(self) -> bool: """Validate configuration parameters.""" # Wakeword model is required, voice recognition is optional if not self.wakeword_model_path: return False if self.audio_sample_rate <= 0 or self.audio_chunk_size <= 0: return False if self.buffer_duration_seconds <= 0 or self.max_recording_seconds <= 0: return False if self.wakeword_confidence_threshold < 0 or self.wakeword_confidence_threshold > 1: return False if self.voice_recognition_confidence_threshold < 0 or self.voice_recognition_confidence_threshold > 1: return False return True @dataclass class WakewordEvent: """Wakeword detection event data.""" wakeword_type: WakewordType confidence: float speaker_id: Optional[str] speaker_name: Optional[str] timestamp: float audio_buffer: Optional[np.ndarray] = None volume: float = 0.0 @dataclass class AudioProcessingResult: """Audio processing result data.""" conversation_id: Optional[str] audio_data: np.ndarray speaker_id: Optional[str] speaker_name: Optional[str] confidence: float timestamp: float duration: float class MLManagerError(Exception): """Base exception for ML Manager errors.""" pass def build_wakeword_config_from_flat_json(flat_config: Dict[str, Any]) -> WakewordConfig: """ Build complete WakewordConfig from flat JSON configuration. This helper function constructs a fully-nested WakewordConfig with all required sub-configurations from a flat dictionary structure (like from client_config.json's "ml" section). Args: flat_config: Flat configuration dictionary with keys like: - wakeword_model_path: str - wakeword_model_password: str (optional) - wakeword_threshold: float (optional) - audio_sample_rate: int (optional) - enable_gpu: bool (optional) etc. Returns: WakewordConfig: Complete configuration with all sub-configs Example: >>> flat_config = { ... "wakeword_model_path": "models/wakeword/model.pth", ... "wakeword_threshold": 0.8, ... "audio_sample_rate": 16000 ... } >>> config = build_wakeword_config_from_flat_json(flat_config) """ from pathlib import Path # Convert relative model path to absolute if needed model_path = flat_config.get("wakeword_model_path", "") if model_path and not Path(model_path).is_absolute(): # Assume relative to project root (where main.py is) model_path = str(Path.cwd() / model_path) # Build ModelConfig model_config = WakewordModelConfig( model_path=model_path, model_password=flat_config.get("wakeword_model_password", ""), device="cuda" if flat_config.get("enable_gpu", True) else "cpu", use_model_optimization=flat_config.get("enable_optimization", True), verify_model_hash=True, use_half_precision=False, enable_model_compilation=True, fallback_on_error=True ) # Build SpectrogramConfig spectrogram_config = SpectrogramConfig( sample_rate=flat_config.get("audio_sample_rate", 16000), n_fft=512, hop_length=160, # 10ms at 16kHz win_length=400, # 25ms at 16kHz window_fn="hann", n_mels=40, f_min=80.0, f_max=None, # Will default to sample_rate/2 power=2.0, normalized=True, center=True, pad_mode="constant", log_offset=1e-8, time_frames=151 ) # Build AudioConfig audio_config = WakewordAudioConfig( sample_rate=flat_config.get("audio_sample_rate", 16000), channels=flat_config.get("audio_channels", 1), bit_depth=16, buffer_duration=2.0, chunk_duration=1.0, overlap_ratio=0.5, spectrogram_config=spectrogram_config, enable_noise_reduction=flat_config.get("enable_noise_reduction", False), enable_gain_control=True, enable_voice_activity_detection=False, max_chunks_in_memory=10, use_circular_buffer=True ) # Build DetectionConfig detection_config = WakewordDetectionConfig( custom_threshold=flat_config.get("wakeword_threshold", 0.7), system_command_threshold=flat_config.get("wakeword_threshold", 0.8), negative_threshold=0.3, use_temporal_smoothing=True, smoothing_window_size=5, min_detections_in_window=3, use_half_precision=False, enable_model_optimization=flat_config.get("enable_optimization", True), batch_inference=False, max_batch_size=flat_config.get("batch_size", 1), log_all_predictions=False, save_detection_features=False ) # Build PerformanceConfig performance_config = WakewordPerformanceConfig( enable_threading=True, max_worker_threads=2, thread_priority="normal", max_memory_usage_mb=512.0, enable_memory_pool=True, gc_frequency=100, enable_batch_inference=False, max_batch_size=flat_config.get("batch_size", 1), inference_timeout_ms=100.0, enable_performance_monitoring=True, log_performance_stats=False, stats_update_interval=10 ) # Build complete WakewordConfig wakeword_config = WakewordConfig( model_config=model_config, audio_config=audio_config, detection_config=detection_config, performance_config=performance_config, enable_event_system=True, event_handler=None, # Will be set later satellite_id="", debug_mode=flat_config.get("debug_mode", False), save_debug_audio=False, debug_output_path="/tmp/trixy_wakeword_debug", enable_voice_recognition_integration=True, enable_conversation_context=True, enable_adaptive_thresholds=False ) return wakeword_config def build_voice_recognition_config_from_flat_json(flat_config: Dict[str, Any]) -> VoiceRecognitionConfig: """ Build complete VoiceRecognitionConfig from flat JSON configuration. This helper function constructs a fully-nested VoiceRecognitionConfig with all required sub-configurations from a flat dictionary structure. Args: flat_config: Flat configuration dictionary with keys like: - voice_recognition_model_path: str - voice_recognition_model_password: str (optional) - voice_recognition_threshold: float (optional) - audio_sample_rate: int (optional) - enable_gpu: bool (optional) etc. Returns: VoiceRecognitionConfig: Complete configuration with all sub-configs Example: >>> flat_config = { ... "voice_recognition_model_path": "models/voice/model.pth", ... "voice_recognition_threshold": 0.7, ... "audio_sample_rate": 16000 ... } >>> config = build_voice_recognition_config_from_flat_json(flat_config) """ from pathlib import Path # Convert relative model path to absolute if needed model_path = flat_config.get("voice_recognition_model_path", "") if model_path and not Path(model_path).is_absolute(): # Assume relative to project root (where main.py is) model_path = str(Path.cwd() / model_path) # Build ModelConfig model_config = VoiceModelConfig( model_path=model_path, model_password=flat_config.get("voice_recognition_model_password", None), architecture_type="ecapa_tdnn", # Default architecture embedding_dim=192, num_speakers=4, # Default, will be updated from model metadata speaker_mapping={}, ecapa_channels=512, use_attention_pooling=True, dropout_rate=0.1, use_batch_norm=True ) # Build AudioConfig audio_config = VoiceAudioConfig( sample_rate=flat_config.get("audio_sample_rate", 16000), audio_length=3.0, feature_type="log_mel", n_mels=40, n_fft=512, hop_length=160, # 10ms at 16kHz win_length=400, # 25ms at 16kHz f_min=80.0, f_max=None, # Will default to sample_rate/2 n_mfcc=40, dct_type=2, norm="ortho", log_mels=False, use_vad=flat_config.get("vad_enabled", True), vad_type="webrtc", vad_aggressiveness=flat_config.get("vad_aggressiveness", 2), vad_frame_duration=flat_config.get("vad_frame_duration", 30), energy_threshold=0.01, use_augmentation=False, # Don't augment during inference noise_factor=0.05, speed_factor=0.05, pitch_factor=0.02, volume_factor=0.1, reverb_factor=0.03 ) # Build RecognitionConfig recognition_config = RecognitionConfig( confidence_threshold=flat_config.get("voice_recognition_threshold", 0.7), verification_threshold=0.6, identification_threshold=0.5, loss_type="angular_margin", # ArcFace angular_margin=0.5, angular_scale=64.0, ge2e_init_w=10.0, ge2e_init_b=-5.0, contrastive_margin=2.0, triplet_margin=0.3, similarity_metric="cosine", use_score_normalization=True, use_temporal_smoothing=True, smoothing_window_size=5, confidence_decay_factor=0.9, use_anti_spoofing=False, spoofing_threshold=0.5 ) # Build PerformanceConfig performance_config = VoicePerformanceConfig( device="cuda" if flat_config.get("enable_gpu", True) else "cpu", batch_size=flat_config.get("batch_size", 1), use_half_precision=False, use_torch_compile=False, max_memory_usage_mb=512, clear_cache_interval=100, target_latency_ms=100.0, use_streaming_inference=True, chunk_size_ms=30.0, num_workers=2, pin_memory=True, use_quantization=False, quantization_backend="fbgemm", use_tensorrt=False ) # Build complete VoiceRecognitionConfig voice_config = VoiceRecognitionConfig( model_config=model_config, audio_config=audio_config, recognition_config=recognition_config, performance_config=performance_config, debug_mode=flat_config.get("debug_mode", False), log_level="INFO", save_debug_audio=False, debug_audio_dir="./debug/voice_recognition" ) return voice_config class MLManager: """ ML Manager for coordinating wakeword detection and voice recognition. This class provides a unified interface for client-side ML processing, coordinating between wakeword detection and voice recognition systems. It handles the complete workflow from audio input to event generation. """ def __init__( self, config: MLConfig, event_handler: Optional[Any] = None, app_container: Optional[Any] = None, logger: Optional[logging.Logger] = None ): """ Initialize ML Manager. Args: config: ML configuration event_handler: Event handler for triggering events app_container: Application container for dependency injection logger: Logger instance """ self.config = config self.event_handler = event_handler self.app_container = app_container self.logger = logger or logging.getLogger(__name__) # Validate configuration if not self.config.validate(): raise MLManagerError("Invalid ML configuration") # Initialize state self._state = MLProcessingState.IDLE self._running = False self._audio_buffer = [] self._processing_lock = threading.Lock() # Initialize components self._wakeword_detector: Optional[WakewordDetector] = None self._voice_recognition_detector: Optional[VoiceRecognitionDetector] = None # Performance monitoring self._start_time = 0.0 self._processing_stats = { "wakeword_detections": 0, "voice_recognitions": 0, "processing_errors": 0, "average_processing_time": 0.0 } self._performance_monitor = PerformanceMonitor( config.get('performance_config', {}) if hasattr(config, 'get') else {} ) # Audio processing self._recording_start_time = 0.0 self._silence_start_time = 0.0 self._last_audio_activity = 0.0 # Network integration self._network_client = None if self.config.server_host and self.config.server_port: self._initialize_network_client() async def initialize(self) -> None: """Initialize ML components.""" try: self.logger.info("Initializing ML Manager...") # DEBUG: Log the actual config values self.logger.info(f"DEBUG - self.config.wakeword_model_path = {self.config.wakeword_model_path}") self.logger.info(f"DEBUG - self.config.voice_recognition_model_path = {self.config.voice_recognition_model_path}") # Build flat config dict from MLConfig for helper functions flat_config = { "wakeword_model_path": self.config.wakeword_model_path, "wakeword_model_password": self.config.wakeword_model_password, "wakeword_threshold": self.config.wakeword_confidence_threshold, "voice_recognition_model_path": self.config.voice_recognition_model_path, "voice_recognition_model_password": self.config.voice_recognition_model_password, "voice_recognition_threshold": self.config.voice_recognition_confidence_threshold, "audio_sample_rate": self.config.audio_sample_rate, "audio_channels": self.config.audio_channels, "batch_size": 1, "enable_gpu": True, "enable_optimization": self.config.enable_optimization, "debug_mode": self.config.debug_mode, "vad_enabled": True, "vad_aggressiveness": 2, "vad_frame_duration": 30, "enable_noise_reduction": False } # Build complete wakeword configuration from flat JSON wakeword_config = build_wakeword_config_from_flat_json(flat_config) wakeword_config.event_handler = self.event_handler # Check if voice recognition is enabled (has model path specified) voice_recognition_enabled = ( self.config.voice_recognition_model_path and self.config.voice_recognition_model_path.strip() ) # Build complete voice recognition configuration only if enabled voice_config = None if voice_recognition_enabled: voice_config = build_voice_recognition_config_from_flat_json(flat_config) self.logger.info(f"Voice recognition enabled - model path: {voice_config.model_config.model_path}") else: self.logger.info("Voice recognition disabled - no model path specified") # Debug: Log the model paths to verify they're correct self.logger.info(f"Wakeword model path: {wakeword_config.model_config.model_path}") # Check if model files exist from pathlib import Path if not Path(wakeword_config.model_config.model_path).exists(): raise MLManagerError(f"Wakeword model file not found: {wakeword_config.model_config.model_path}") if voice_recognition_enabled and not Path(voice_config.model_config.model_path).exists(): raise MLManagerError(f"Voice recognition model file not found: {voice_config.model_config.model_path}") # Validate configurations - validate sub-configs individually since overall # WakewordConfig.validate() has a design issue with AudioConfig validation if not wakeword_config.model_config.validate(): raise MLManagerError(f"Invalid wakeword model configuration - model_path validation failed") if not wakeword_config.detection_config.validate(): raise MLManagerError("Invalid wakeword detection configuration") if not wakeword_config.performance_config.validate(): raise MLManagerError("Invalid wakeword performance configuration") self.logger.info(f"Wakeword config built successfully: threshold={wakeword_config.detection_config.custom_threshold}") if voice_recognition_enabled: self.logger.info(f"Voice config built successfully: threshold={voice_config.recognition_config.confidence_threshold}") # Initialize detectors # For wakeword detector, use direct constructor with config object self._wakeword_detector = WakewordDetector( config=wakeword_config, event_handler=self.event_handler, logger=self.logger ) # For voice recognition detector, only initialize if enabled if voice_recognition_enabled: self._voice_recognition_detector = create_voice_recognition_detector( config=voice_config, event_handler=self.event_handler, audio_callback=None ) else: self._voice_recognition_detector = None self.logger.info("Voice recognition detector not initialized (disabled)") # Apply optimizations if enabled if self.config.enable_optimization: await self._apply_optimizations() self.logger.info("ML Manager initialized successfully") except Exception as e: self.logger.error(f"Failed to initialize ML Manager: {e}") import traceback self.logger.error(f"Traceback: {traceback.format_exc()}") raise MLManagerError(f"Initialization failed: {e}") async def start(self) -> None: """Start ML processing.""" if self._running: self.logger.warning("ML Manager is already running") return try: self.logger.info("Starting ML Manager...") # Initialize if not already done if not self._wakeword_detector: await self.initialize() # Start detectors self._wakeword_detector.start() if self._voice_recognition_detector: self._voice_recognition_detector.start() else: self.logger.info("Voice recognition detector is disabled, skipping start") # Set state self._running = True self._state = MLProcessingState.LISTENING self._start_time = time.time() # Start performance monitoring if self.config.enable_monitoring: await self._performance_monitor.start_monitoring() # Trigger system event if self.event_handler: await self._trigger_event("ml_system_started", { "timestamp": time.time(), "mode": self.config.system_mode.value }) self.logger.info("ML Manager started successfully") except Exception as e: self.logger.error(f"Failed to start ML Manager: {e}") self._state = MLProcessingState.ERROR raise MLManagerError(f"Start failed: {e}") async def stop(self) -> None: """Stop ML processing.""" if not self._running: return try: self.logger.info("Stopping ML Manager...") # Stop detectors if self._wakeword_detector: self._wakeword_detector.stop() if self._voice_recognition_detector: self._voice_recognition_detector.stop() # Stop performance monitoring if self.config.enable_monitoring: await self._performance_monitor.stop_monitoring() # Clear state self._running = False self._state = MLProcessingState.IDLE self._audio_buffer.clear() # Trigger system event if self.event_handler: uptime = time.time() - self._start_time await self._trigger_event("ml_system_stopped", { "timestamp": time.time(), "uptime": uptime, "stats": self._processing_stats.copy() }) self.logger.info("ML Manager stopped successfully") except Exception as e: self.logger.error(f"Error stopping ML Manager: {e}") async def process_audio_chunk(self, audio_data: np.ndarray) -> None: """ Process audio chunk through ML pipeline. Args: audio_data: Audio data as numpy array """ if not self._running: return # Start performance timer timer_key = self._performance_monitor.start_timer("ml_manager", "process_audio_chunk") try: with self._processing_lock: # Process based on current state if self._state == MLProcessingState.LISTENING: await self._process_listening_state(audio_data) elif self._state == MLProcessingState.RECORDING: await self._process_recording_state(audio_data) # Record throughput metric self._performance_monitor.record_metric( "ml_manager", MetricType.THROUGHPUT, len(audio_data), "samples" ) except Exception as e: self.logger.error(f"Error processing audio chunk: {e}") self._processing_stats["processing_errors"] += 1 self._performance_monitor.record_metric( "ml_manager", MetricType.ERROR_RATE, 1, "errors" ) finally: # End performance timer processing_time = self._performance_monitor.end_timer(timer_key) if processing_time > 0: self._performance_monitor.record_metric( "ml_manager", MetricType.LATENCY, processing_time, "milliseconds" ) async def _process_listening_state(self, audio_data: np.ndarray) -> None: """Process audio in listening state (wakeword detection).""" # Feed audio to wakeword detector if self._wakeword_detector: self._wakeword_detector.process_audio_chunk(audio_data) async def _process_recording_state(self, audio_data: np.ndarray) -> None: """Process audio in recording state (after wakeword detected).""" # Add audio to buffer self._audio_buffer.append(audio_data) # Check for voice activity has_voice = self._detect_voice_activity(audio_data) current_time = time.time() if has_voice: self._last_audio_activity = current_time self._silence_start_time = 0.0 else: if self._last_audio_activity > 0 and self._silence_start_time == 0.0: self._silence_start_time = current_time # Check stopping conditions recording_duration = current_time - self._recording_start_time silence_duration = (current_time - self._silence_start_time) if self._silence_start_time > 0 else 0 if (recording_duration >= self.config.max_recording_seconds or silence_duration >= self.config.silence_timeout_seconds): await self._complete_recording() def _detect_voice_activity(self, audio_data: np.ndarray) -> bool: """Simple voice activity detection based on energy.""" energy = np.mean(audio_data ** 2) threshold = 0.01 # Adjust based on requirements return energy > threshold async def _complete_recording(self) -> None: """Complete audio recording and process result.""" try: # Combine audio buffer if not self._audio_buffer: self.logger.warning("No audio data recorded") self._reset_recording_state() return combined_audio = np.concatenate(self._audio_buffer) recording_duration = time.time() - self._recording_start_time # Process with voice recognition speaker_result = None if self._voice_recognition_detector: speaker_result = self._voice_recognition_detector.identify_speaker(combined_audio) # Create processing result result = AudioProcessingResult( conversation_id=None, # Will be set by server audio_data=combined_audio, speaker_id=speaker_result.speaker_id if speaker_result else None, speaker_name=speaker_result.speaker_name if speaker_result else None, confidence=speaker_result.confidence if speaker_result else 0.0, timestamp=time.time(), duration=recording_duration ) # Trigger audio processing event if self.event_handler: await self._trigger_event("raw_audio_input_received", { "audio_data": result.audio_data, "speaker_id": result.speaker_id, "speaker_name": result.speaker_name, "confidence": result.confidence, "timestamp": result.timestamp, "duration": result.duration }) # Send to server if in client mode if self.config.system_mode == MLSystemMode.CLIENT: await self._send_audio_to_server(result) # Update statistics self._processing_stats["voice_recognitions"] += 1 self.logger.info(f"Audio processing completed: speaker={result.speaker_name}, confidence={result.confidence:.2f}") except Exception as e: self.logger.error(f"Error completing recording: {e}") self._processing_stats["processing_errors"] += 1 finally: self._reset_recording_state() def _reset_recording_state(self) -> None: """Reset recording state back to listening.""" self._audio_buffer.clear() self._recording_start_time = 0.0 self._silence_start_time = 0.0 self._last_audio_activity = 0.0 self._state = MLProcessingState.LISTENING # Resume wakeword detection if self._wakeword_detector: self._wakeword_detector.resume() async def _handle_wakeword_event(self, detection_result: DetectionResult) -> None: """Handle wakeword detection event.""" try: self.logger.info(f"Wakeword detected: {detection_result.wakeword_type}, confidence: {detection_result.confidence:.2f}") # Pause wakeword detection if self._wakeword_detector: self._wakeword_detector.pause() # Start recording self._state = MLProcessingState.RECORDING self._recording_start_time = time.time() self._last_audio_activity = self._recording_start_time # Create wakeword event wakeword_event = WakewordEvent( wakeword_type=detection_result.wakeword_type, confidence=detection_result.confidence, speaker_id=None, # Will be determined during recording speaker_name=None, timestamp=detection_result.timestamp, volume=getattr(detection_result, 'volume', 0.0) ) # Trigger wakeword event if self.event_handler: await self._trigger_event("wakeword_received", { "wakeword_id": detection_result.wakeword_type.value, "wakeword_type": detection_result.wakeword_type.value, "confidence": detection_result.confidence, "timestamp": detection_result.timestamp, "volume": wakeword_event.volume }) # Send to server if in client mode if self.config.system_mode == MLSystemMode.CLIENT: await self._send_wakeword_to_server(wakeword_event) # Update statistics self._processing_stats["wakeword_detections"] += 1 except Exception as e: self.logger.error(f"Error handling wakeword event: {e}") self._processing_stats["processing_errors"] += 1 self._reset_recording_state() async def _handle_voice_recognition_event(self, result: IdentificationResult) -> None: """Handle voice recognition event.""" self.logger.info(f"Speaker identified: {result.speaker_name}, confidence: {result.confidence:.2f}") async def _send_wakeword_to_server(self, wakeword_event: WakewordEvent) -> None: """Send wakeword detection to server.""" if not self._network_client: return try: # Create wakeword command for server # This would integrate with the network protocol self.logger.debug(f"Sending wakeword to server: {wakeword_event.wakeword_type}") except Exception as e: self.logger.error(f"Error sending wakeword to server: {e}") async def _send_audio_to_server(self, result: AudioProcessingResult) -> None: """Send audio recording to server.""" if not self._network_client: return try: # Send audio data through raw audio input stream self.logger.debug(f"Sending audio to server: {result.duration:.2f}s") except Exception as e: self.logger.error(f"Error sending audio to server: {e}") def _initialize_network_client(self) -> None: """Initialize network client for server communication.""" # This would initialize the network client for command socket # and audio streaming connections self.logger.info("Network client initialization placeholder") async def _apply_optimizations(self) -> None: """Apply performance optimizations.""" try: # Model optimization if self._wakeword_detector and hasattr(self._wakeword_detector, 'optimize'): self._wakeword_detector.optimize() if self._voice_recognition_detector and hasattr(self._voice_recognition_detector, 'optimize'): self._voice_recognition_detector.optimize() self.logger.info("ML optimizations applied") except Exception as e: self.logger.error(f"Error applying optimizations: {e}") async def _trigger_event(self, event_name: str, event_data: Dict[str, Any]) -> None: """Trigger event through event handler.""" if self.event_handler: try: # This would integrate with the actual event handler self.logger.debug(f"Triggering event: {event_name}") except Exception as e: self.logger.error(f"Error triggering event {event_name}: {e}") def get_state(self) -> MLProcessingState: """Get current processing state.""" return self._state def get_statistics(self) -> Dict[str, Any]: """Get processing statistics.""" stats = self._processing_stats.copy() if self._running: stats["uptime"] = time.time() - self._start_time stats["current_state"] = self._state.value return stats def is_running(self) -> bool: """Check if ML manager is running.""" return self._running async def reload_models(self) -> None: """Reload ML models.""" try: self.logger.info("Reloading ML models...") # Stop current processing was_running = self._running if was_running: await self.stop() # Reinitialize await self.initialize() # Restart if it was running if was_running: await self.start() self.logger.info("ML models reloaded successfully") except Exception as e: self.logger.error(f"Error reloading models: {e}") raise MLManagerError(f"Model reload failed: {e}") async def update_configuration(self, new_config: MLConfig) -> None: """Update ML configuration.""" try: self.logger.info("Updating ML configuration...") # Validate new configuration if not new_config.validate(): raise MLManagerError("Invalid configuration") # Stop current processing was_running = self._running if was_running: await self.stop() # Update configuration self.config = new_config # Reinitialize with new config await self.initialize() # Restart if it was running if was_running: await self.start() self.logger.info("ML configuration updated successfully") except Exception as e: self.logger.error(f"Error updating configuration: {e}") raise MLManagerError(f"Configuration update failed: {e}") def get_performance_report(self, window_hours: int = 1) -> Dict[str, Any]: """ Get comprehensive performance report. Args: window_hours: Time window in hours for the report Returns: Performance report dictionary """ from datetime import timedelta window = timedelta(hours=window_hours) report = self._performance_monitor.get_performance_report(window) return { 'timestamp': report.timestamp.isoformat(), 'window_hours': window_hours, 'system_stats': report.system_stats, 'component_stats': report.component_stats, 'active_alerts': report.alerts, 'recommendations': report.recommendations, 'ml_manager_stats': self._processing_stats.copy(), 'uptime_hours': (time.time() - self._start_time) / 3600 if self._start_time > 0 else 0 } def export_performance_metrics(self, file_path: str, window_hours: int = 24) -> None: """ Export performance metrics to file. Args: file_path: Path to export file window_hours: Time window in hours """ from datetime import timedelta window = timedelta(hours=window_hours) self._performance_monitor.export_metrics(file_path, window) self.logger.info(f"Performance metrics exported to {file_path}") def get_current_metrics(self) -> Dict[str, Any]: """ Get current real-time performance metrics. Returns: Current metrics dictionary """ return { 'state': self._state.value, 'uptime_seconds': time.time() - self._start_time if self._start_time > 0 else 0, 'processing_stats': self._processing_stats.copy(), 'active_alerts': len(self._performance_monitor.alert_manager.get_active_alerts()), 'system_cpu': self._performance_monitor.system_monitor.get_cpu_usage(), 'system_memory': self._performance_monitor.system_monitor.get_memory_usage(), 'audio_buffer_size': len(self._audio_buffer), 'is_monitoring': self._performance_monitor.monitoring_active } def create_ml_manager( config: MLConfig, event_handler: Optional[Any] = None, app_container: Optional[Any] = None, logger: Optional[logging.Logger] = None ) -> MLManager: """ Factory function to create ML Manager. Args: config: ML configuration event_handler: Event handler for triggering events app_container: Application container for dependency injection logger: Logger instance Returns: MLManager: Configured ML manager instance """ return MLManager(config, event_handler, app_container, logger) def create_default_ml_config() -> MLConfig: """ Create default ML configuration. Returns: MLConfig: Default configuration """ return MLConfig( wakeword_model_path="./models/wakeword/default/model.pth", wakeword_model_password="", voice_recognition_model_path="./models/voice_recognition/default/model.pth", voice_recognition_model_password="" ) def create_optimized_ml_config() -> MLConfig: """ Create optimized ML configuration for production. Returns: MLConfig: Optimized configuration """ config = create_default_ml_config() config.enable_optimization = True config.enable_monitoring = True config.memory_limit_mb = 256 config.processing_timeout_seconds = 15.0 return config def create_debug_ml_config() -> MLConfig: """ Create debug ML configuration for development. Returns: MLConfig: Debug configuration """ config = create_default_ml_config() config.debug_mode = True config.system_mode = MLSystemMode.DEBUG config.enable_monitoring = True return config