events.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790
  1. """
  2. Event System Integration for Trixy ML Components.
  3. This module provides comprehensive event system integration for all ML components
  4. including wakeword detection, voice recognition, and workflow management. It defines
  5. ML-specific event data structures and provides event handlers that integrate with
  6. the main Trixy event system.
  7. Key Features:
  8. - ML-specific event data structures
  9. - Event handler registration and management
  10. - Integration with main Trixy event system
  11. - Performance event monitoring
  12. - Debug and diagnostic events
  13. - Event correlation and tracking
  14. - Real-time event streaming
  15. - Event persistence and replay
  16. Usage:
  17. from trixy_core.ml.events import MLEventManager, MLEventData
  18. # Create ML event manager
  19. ml_events = MLEventManager(main_event_handler)
  20. # Register ML component
  21. ml_events.register_ml_component(ml_manager)
  22. # Trigger ML events
  23. ml_events.trigger_wakeword_detected(detection_data)
  24. """
  25. import logging
  26. import threading
  27. import time
  28. import queue
  29. from typing import Dict, List, Optional, Any, Callable, Union, Set
  30. from dataclasses import dataclass, field
  31. from datetime import datetime
  32. from enum import Enum
  33. import uuid
  34. from collections import defaultdict, deque
  35. # Import event system components
  36. try:
  37. from ..events import (
  38. EventHandler, TrixyEvent, EventType, TrixyEventData,
  39. WakewordReceivedEventData, RawAudioInputReceivedEventData,
  40. SpeakerInfo, EventDataFactory
  41. )
  42. EVENTS_AVAILABLE = True
  43. except ImportError:
  44. # Create mock classes if event system not available
  45. EVENTS_AVAILABLE = False
  46. EventHandler = object
  47. TrixyEvent = lambda events: lambda func: func
  48. EventType = object
  49. TrixyEventData = object
  50. WakewordReceivedEventData = object
  51. RawAudioInputReceivedEventData = object
  52. SpeakerInfo = object
  53. EventDataFactory = object
  54. # Configure logging
  55. logger = logging.getLogger(__name__)
  56. class MLEventType(Enum):
  57. """ML-specific event types."""
  58. # Core ML events
  59. ML_SYSTEM_STARTED = "ml_system_started"
  60. ML_SYSTEM_STOPPED = "ml_system_stopped"
  61. ML_SYSTEM_ERROR = "ml_system_error"
  62. # Wakeword events
  63. WAKEWORD_DETECTED = "wakeword_detected"
  64. WAKEWORD_ENHANCED = "wakeword_enhanced"
  65. WAKEWORD_FALSE_POSITIVE = "wakeword_false_positive"
  66. # Speaker recognition events
  67. SPEAKER_IDENTIFIED = "speaker_identified"
  68. SPEAKER_VERIFIED = "speaker_verified"
  69. SPEAKER_ENROLLED = "speaker_enrolled"
  70. SPEAKER_REMOVED = "speaker_removed"
  71. SPEAKER_UPDATED = "speaker_updated"
  72. # Audio processing events
  73. AUDIO_PROCESSING_STARTED = "audio_processing_started"
  74. AUDIO_PROCESSING_STOPPED = "audio_processing_stopped"
  75. AUDIO_QUALITY_WARNING = "audio_quality_warning"
  76. AUDIO_BUFFER_OVERRUN = "audio_buffer_overrun"
  77. SILENCE_DETECTED = "silence_detected"
  78. VOICE_ACTIVITY_DETECTED = "voice_activity_detected"
  79. # Workflow events
  80. WORKFLOW_STARTED = "workflow_started"
  81. WORKFLOW_STEP_COMPLETED = "workflow_step_completed"
  82. WORKFLOW_COMPLETED = "workflow_completed"
  83. WORKFLOW_FAILED = "workflow_failed"
  84. WORKFLOW_TIMEOUT = "workflow_timeout"
  85. # Performance events
  86. PERFORMANCE_METRICS_UPDATED = "performance_metrics_updated"
  87. LATENCY_WARNING = "latency_warning"
  88. MEMORY_WARNING = "memory_warning"
  89. GPU_ERROR = "gpu_error"
  90. # Model events
  91. MODEL_LOADED = "model_loaded"
  92. MODEL_UNLOADED = "model_unloaded"
  93. MODEL_ERROR = "model_error"
  94. MODEL_UPDATED = "model_updated"
  95. # Network events
  96. SERVER_SELECTION_RECEIVED = "server_selection_received"
  97. AUDIO_STREAMING_STARTED = "audio_streaming_started"
  98. AUDIO_STREAMING_STOPPED = "audio_streaming_stopped"
  99. NETWORK_ERROR = "network_error"
  100. class EventPriority(Enum):
  101. """Event priority levels."""
  102. LOW = "low"
  103. NORMAL = "normal"
  104. HIGH = "high"
  105. CRITICAL = "critical"
  106. @dataclass
  107. class MLEventData(TrixyEventData if EVENTS_AVAILABLE else object):
  108. """Base class for ML event data."""
  109. event_type: str = ""
  110. correlation_id: Optional[str] = None
  111. session_id: Optional[str] = None
  112. timestamp: str = field(default_factory=lambda: datetime.now().isoformat())
  113. event_id: str = field(default_factory=lambda: str(uuid.uuid4()))
  114. source_component: str = "ml_system"
  115. priority: EventPriority = EventPriority.NORMAL
  116. processing_time_ms: float = 0.0
  117. metadata: Dict[str, Any] = field(default_factory=dict)
  118. @dataclass
  119. class WakewordEventData(MLEventData):
  120. """Event data for wakeword detection events."""
  121. wakeword_type: str = ""
  122. confidence: float = 0.0
  123. audio_chunk_position: int = 0
  124. speaker_id: Optional[str] = None
  125. speaker_name: Optional[str] = None
  126. speaker_confidence: float = 0.0
  127. enhancement_data: Dict[str, Any] = field(default_factory=dict)
  128. @dataclass
  129. class SpeakerEventData(MLEventData):
  130. """Event data for speaker recognition events."""
  131. speaker_id: str = ""
  132. speaker_name: str = ""
  133. confidence: float = 0.0
  134. similarity_score: float = 0.0
  135. embedding_quality: float = 0.0
  136. verification_result: Optional[bool] = None
  137. enrollment_status: Optional[str] = None
  138. @dataclass
  139. class AudioEventData(MLEventData):
  140. """Event data for audio processing events."""
  141. audio_metrics: Dict[str, float] = field(default_factory=dict)
  142. buffer_status: Dict[str, Any] = field(default_factory=dict)
  143. quality_indicators: Dict[str, float] = field(default_factory=dict)
  144. vad_result: Optional[Dict[str, Any]] = None
  145. silence_duration: float = 0.0
  146. voice_duration: float = 0.0
  147. @dataclass
  148. class WorkflowEventData(MLEventData):
  149. """Event data for workflow events."""
  150. workflow_state: str = ""
  151. workflow_step: str = ""
  152. step_duration_ms: float = 0.0
  153. workflow_duration_ms: float = 0.0
  154. success: bool = True
  155. error_message: Optional[str] = None
  156. conversation_session_id: Optional[str] = None
  157. @dataclass
  158. class PerformanceEventData(MLEventData):
  159. """Event data for performance monitoring events."""
  160. cpu_usage_percent: float = 0.0
  161. memory_usage_mb: float = 0.0
  162. gpu_usage_percent: float = 0.0
  163. gpu_memory_usage_mb: float = 0.0
  164. latency_ms: float = 0.0
  165. throughput_samples_per_second: float = 0.0
  166. error_rate: float = 0.0
  167. queue_depth: int = 0
  168. @dataclass
  169. class ModelEventData(MLEventData):
  170. """Event data for model events."""
  171. model_type: str = ""
  172. model_path: str = ""
  173. model_name: str = ""
  174. model_version: str = ""
  175. model_size_mb: float = 0.0
  176. load_time_ms: float = 0.0
  177. validation_result: bool = True
  178. error_details: Optional[str] = None
  179. @dataclass
  180. class NetworkEventData(MLEventData):
  181. """Event data for network events."""
  182. command_type: str = ""
  183. server_response: str = ""
  184. bytes_transmitted: int = 0
  185. transmission_time_ms: float = 0.0
  186. connection_status: str = ""
  187. error_details: Optional[str] = None
  188. class EventCorrelation:
  189. """Event correlation and tracking."""
  190. def __init__(self, max_correlation_time: float = 30.0):
  191. """
  192. Initialize event correlation.
  193. Args:
  194. max_correlation_time: Maximum time to keep correlations (seconds)
  195. """
  196. self.max_correlation_time = max_correlation_time
  197. self._correlations: Dict[str, List[MLEventData]] = defaultdict(list)
  198. self._correlation_times: Dict[str, float] = {}
  199. self._lock = threading.RLock()
  200. def add_event(self, event_data: MLEventData) -> None:
  201. """Add event to correlation tracking."""
  202. if not event_data.correlation_id:
  203. return
  204. with self._lock:
  205. current_time = time.time()
  206. # Clean up old correlations
  207. self._cleanup_old_correlations(current_time)
  208. # Add event to correlation
  209. self._correlations[event_data.correlation_id].append(event_data)
  210. self._correlation_times[event_data.correlation_id] = current_time
  211. def get_correlated_events(self, correlation_id: str) -> List[MLEventData]:
  212. """Get all events with the same correlation ID."""
  213. with self._lock:
  214. return self._correlations.get(correlation_id, []).copy()
  215. def _cleanup_old_correlations(self, current_time: float) -> None:
  216. """Clean up old correlation data."""
  217. expired_ids = [
  218. correlation_id for correlation_id, timestamp in self._correlation_times.items()
  219. if current_time - timestamp > self.max_correlation_time
  220. ]
  221. for correlation_id in expired_ids:
  222. del self._correlations[correlation_id]
  223. del self._correlation_times[correlation_id]
  224. class MLEventManager:
  225. """
  226. Comprehensive event manager for ML components.
  227. This class provides centralized event management for all ML components,
  228. integrating with the main Trixy event system while providing ML-specific
  229. functionality.
  230. """
  231. def __init__(
  232. self,
  233. main_event_handler: Optional[EventHandler] = None,
  234. enable_correlation: bool = True,
  235. enable_persistence: bool = False,
  236. max_event_history: int = 1000
  237. ):
  238. """
  239. Initialize ML event manager.
  240. Args:
  241. main_event_handler: Main Trixy event handler
  242. enable_correlation: Enable event correlation tracking
  243. enable_persistence: Enable event persistence
  244. max_event_history: Maximum events to keep in history
  245. """
  246. self.main_event_handler = main_event_handler
  247. self.enable_correlation = enable_correlation
  248. self.enable_persistence = enable_persistence
  249. self.max_event_history = max_event_history
  250. # Event processing
  251. self._event_queue = queue.Queue()
  252. self._processing_thread: Optional[threading.Thread] = None
  253. self._stop_event = threading.Event()
  254. self._is_running = False
  255. # Event handlers
  256. self._ml_event_handlers: Dict[MLEventType, List[Callable]] = defaultdict(list)
  257. self._registered_components: Set[Any] = set()
  258. # Event history and correlation
  259. self._event_history = deque(maxlen=max_event_history)
  260. self._correlation = EventCorrelation() if enable_correlation else None
  261. # Performance tracking
  262. self._event_counts: Dict[str, int] = defaultdict(int)
  263. self._last_event_time: Dict[str, float] = {}
  264. self._processing_times: Dict[str, List[float]] = defaultdict(list)
  265. # Event filtering
  266. self._event_filters: List[Callable[[MLEventData], bool]] = []
  267. self._debug_mode = False
  268. logger.info("ML Event Manager initialized")
  269. def start(self) -> None:
  270. """Start the ML event manager."""
  271. if self._is_running:
  272. logger.warning("ML event manager already running")
  273. return
  274. logger.info("Starting ML event manager...")
  275. self._is_running = True
  276. self._stop_event.clear()
  277. # Start event processing thread
  278. self._processing_thread = threading.Thread(
  279. target=self._event_processing_loop,
  280. daemon=True,
  281. name="MLEventManager"
  282. )
  283. self._processing_thread.start()
  284. logger.info("ML event manager started")
  285. def stop(self) -> None:
  286. """Stop the ML event manager."""
  287. if not self._is_running:
  288. return
  289. logger.info("Stopping ML event manager...")
  290. self._is_running = False
  291. self._stop_event.set()
  292. # Stop processing thread
  293. if self._processing_thread and self._processing_thread.is_alive():
  294. self._processing_thread.join(timeout=5.0)
  295. logger.info("ML event manager stopped")
  296. def register_ml_component(self, component: Any) -> None:
  297. """
  298. Register an ML component for event handling.
  299. Args:
  300. component: ML component to register
  301. """
  302. if component in self._registered_components:
  303. logger.warning(f"Component {component} already registered")
  304. return
  305. self._registered_components.add(component)
  306. # Auto-register event handlers if the component has them
  307. self._auto_register_handlers(component)
  308. logger.info(f"Registered ML component: {type(component).__name__}")
  309. def unregister_ml_component(self, component: Any) -> None:
  310. """
  311. Unregister an ML component.
  312. Args:
  313. component: ML component to unregister
  314. """
  315. if component in self._registered_components:
  316. self._registered_components.remove(component)
  317. logger.info(f"Unregistered ML component: {type(component).__name__}")
  318. def add_event_handler(self, event_type: MLEventType, handler: Callable) -> None:
  319. """
  320. Add an event handler for a specific ML event type.
  321. Args:
  322. event_type: ML event type
  323. handler: Event handler function
  324. """
  325. self._ml_event_handlers[event_type].append(handler)
  326. logger.debug(f"Added event handler for {event_type.value}")
  327. def remove_event_handler(self, event_type: MLEventType, handler: Callable) -> None:
  328. """
  329. Remove an event handler.
  330. Args:
  331. event_type: ML event type
  332. handler: Event handler function to remove
  333. """
  334. if handler in self._ml_event_handlers[event_type]:
  335. self._ml_event_handlers[event_type].remove(handler)
  336. logger.debug(f"Removed event handler for {event_type.value}")
  337. def trigger_event(self, event_data: MLEventData) -> None:
  338. """
  339. Trigger an ML event.
  340. Args:
  341. event_data: Event data to trigger
  342. """
  343. if not self._is_running:
  344. logger.warning("Cannot trigger event - event manager not running")
  345. return
  346. # Apply event filters
  347. if not self._should_process_event(event_data):
  348. return
  349. # Add to processing queue
  350. try:
  351. if not self._event_queue.full():
  352. self._event_queue.put(event_data, block=False)
  353. else:
  354. logger.warning("Event queue full - dropping event")
  355. except Exception as e:
  356. logger.error(f"Error queuing event: {e}")
  357. def trigger_wakeword_detected(
  358. self,
  359. wakeword_type: str,
  360. confidence: float,
  361. speaker_id: Optional[str] = None,
  362. speaker_name: Optional[str] = None,
  363. speaker_confidence: float = 0.0,
  364. **kwargs
  365. ) -> None:
  366. """Trigger wakeword detected event."""
  367. event_data = WakewordEventData(
  368. event_type=MLEventType.WAKEWORD_DETECTED.value,
  369. wakeword_type=wakeword_type,
  370. confidence=confidence,
  371. speaker_id=speaker_id,
  372. speaker_name=speaker_name,
  373. speaker_confidence=speaker_confidence,
  374. **kwargs
  375. )
  376. self.trigger_event(event_data)
  377. def trigger_speaker_identified(
  378. self,
  379. speaker_id: str,
  380. speaker_name: str,
  381. confidence: float,
  382. similarity_score: float = 0.0,
  383. **kwargs
  384. ) -> None:
  385. """Trigger speaker identified event."""
  386. event_data = SpeakerEventData(
  387. event_type=MLEventType.SPEAKER_IDENTIFIED.value,
  388. speaker_id=speaker_id,
  389. speaker_name=speaker_name,
  390. confidence=confidence,
  391. similarity_score=similarity_score,
  392. **kwargs
  393. )
  394. self.trigger_event(event_data)
  395. def trigger_audio_quality_warning(
  396. self,
  397. audio_metrics: Dict[str, float],
  398. warning_details: str = "",
  399. **kwargs
  400. ) -> None:
  401. """Trigger audio quality warning event."""
  402. event_data = AudioEventData(
  403. event_type=MLEventType.AUDIO_QUALITY_WARNING.value,
  404. audio_metrics=audio_metrics,
  405. metadata={'warning_details': warning_details},
  406. priority=EventPriority.HIGH,
  407. **kwargs
  408. )
  409. self.trigger_event(event_data)
  410. def trigger_workflow_completed(
  411. self,
  412. workflow_state: str,
  413. success: bool = True,
  414. duration_ms: float = 0.0,
  415. session_id: Optional[str] = None,
  416. **kwargs
  417. ) -> None:
  418. """Trigger workflow completed event."""
  419. event_data = WorkflowEventData(
  420. event_type=MLEventType.WORKFLOW_COMPLETED.value,
  421. workflow_state=workflow_state,
  422. success=success,
  423. workflow_duration_ms=duration_ms,
  424. conversation_session_id=session_id,
  425. **kwargs
  426. )
  427. self.trigger_event(event_data)
  428. def trigger_performance_update(
  429. self,
  430. cpu_usage: float = 0.0,
  431. memory_usage: float = 0.0,
  432. latency_ms: float = 0.0,
  433. **kwargs
  434. ) -> None:
  435. """Trigger performance metrics update event."""
  436. event_data = PerformanceEventData(
  437. event_type=MLEventType.PERFORMANCE_METRICS_UPDATED.value,
  438. cpu_usage_percent=cpu_usage,
  439. memory_usage_mb=memory_usage,
  440. latency_ms=latency_ms,
  441. **kwargs
  442. )
  443. self.trigger_event(event_data)
  444. def add_event_filter(self, filter_func: Callable[[MLEventData], bool]) -> None:
  445. """
  446. Add an event filter function.
  447. Args:
  448. filter_func: Function that returns True if event should be processed
  449. """
  450. self._event_filters.append(filter_func)
  451. logger.debug("Added event filter")
  452. def enable_debug_mode(self, enabled: bool = True) -> None:
  453. """Enable or disable debug mode."""
  454. self._debug_mode = enabled
  455. logger.info(f"Debug mode {'enabled' if enabled else 'disabled'}")
  456. def get_event_statistics(self) -> Dict[str, Any]:
  457. """Get event processing statistics."""
  458. total_events = sum(self._event_counts.values())
  459. return {
  460. 'total_events_processed': total_events,
  461. 'events_by_type': dict(self._event_counts),
  462. 'average_processing_times': {
  463. event_type: sum(times) / len(times) if times else 0.0
  464. for event_type, times in self._processing_times.items()
  465. },
  466. 'event_history_size': len(self._event_history),
  467. 'registered_components': len(self._registered_components),
  468. 'active_handlers': sum(len(handlers) for handlers in self._ml_event_handlers.values()),
  469. 'is_running': self._is_running
  470. }
  471. def get_event_history(self, event_type: Optional[str] = None, limit: int = 100) -> List[MLEventData]:
  472. """
  473. Get event history.
  474. Args:
  475. event_type: Filter by event type (optional)
  476. limit: Maximum number of events to return
  477. Returns:
  478. List of historical events
  479. """
  480. history = list(self._event_history)
  481. if event_type:
  482. history = [event for event in history if event.event_type == event_type]
  483. return history[-limit:] if limit > 0 else history
  484. def get_correlated_events(self, correlation_id: str) -> List[MLEventData]:
  485. """Get events with the same correlation ID."""
  486. if self._correlation:
  487. return self._correlation.get_correlated_events(correlation_id)
  488. return []
  489. def _event_processing_loop(self) -> None:
  490. """Main event processing loop."""
  491. logger.debug("ML event processing loop started")
  492. while not self._stop_event.is_set():
  493. try:
  494. # Get event from queue
  495. try:
  496. event_data = self._event_queue.get(timeout=0.1)
  497. except queue.Empty:
  498. continue
  499. # Process the event
  500. self._process_event(event_data)
  501. # Mark task done
  502. self._event_queue.task_done()
  503. except Exception as e:
  504. logger.error(f"Error in event processing loop: {e}")
  505. time.sleep(0.1)
  506. logger.debug("ML event processing loop stopped")
  507. def _process_event(self, event_data: MLEventData) -> None:
  508. """Process a single event."""
  509. start_time = time.time()
  510. try:
  511. # Add to history
  512. self._event_history.append(event_data)
  513. # Add to correlation tracking
  514. if self._correlation and event_data.correlation_id:
  515. self._correlation.add_event(event_data)
  516. # Update statistics
  517. self._event_counts[event_data.event_type] += 1
  518. self._last_event_time[event_data.event_type] = start_time
  519. # Debug logging
  520. if self._debug_mode:
  521. logger.debug(f"Processing ML event: {event_data.event_type}")
  522. # Call ML-specific handlers
  523. event_type = MLEventType(event_data.event_type) if hasattr(MLEventType, event_data.event_type.upper()) else None
  524. if event_type and event_type in self._ml_event_handlers:
  525. for handler in self._ml_event_handlers[event_type]:
  526. try:
  527. handler(event_data)
  528. except Exception as e:
  529. logger.error(f"Error in ML event handler: {e}")
  530. # Forward to main event system if available
  531. if EVENTS_AVAILABLE and self.main_event_handler:
  532. self._forward_to_main_event_system(event_data)
  533. # Update processing time statistics
  534. processing_time = (time.time() - start_time) * 1000
  535. self._processing_times[event_data.event_type].append(processing_time)
  536. # Keep only recent processing times
  537. if len(self._processing_times[event_data.event_type]) > 100:
  538. self._processing_times[event_data.event_type] = self._processing_times[event_data.event_type][-50:]
  539. except Exception as e:
  540. logger.error(f"Error processing event {event_data.event_type}: {e}")
  541. def _forward_to_main_event_system(self, event_data: MLEventData) -> None:
  542. """Forward ML event to main Trixy event system."""
  543. try:
  544. # Convert ML event to main event system format
  545. if event_data.event_type == MLEventType.WAKEWORD_DETECTED.value:
  546. self._forward_wakeword_event(event_data)
  547. elif event_data.event_type == MLEventType.SPEAKER_IDENTIFIED.value:
  548. self._forward_speaker_event(event_data)
  549. elif hasattr(event_data, 'priority') and event_data.priority == EventPriority.CRITICAL:
  550. # Forward critical events as system events
  551. self._forward_system_event(event_data)
  552. except Exception as e:
  553. logger.error(f"Error forwarding event to main system: {e}")
  554. def _forward_wakeword_event(self, event_data: MLEventData) -> None:
  555. """Forward wakeword event to main event system."""
  556. if not isinstance(event_data, WakewordEventData):
  557. return
  558. # Create speaker info if available
  559. speaker_info = None
  560. if event_data.speaker_id:
  561. speaker_info = SpeakerInfo(
  562. speaker_id=event_data.speaker_id,
  563. speaker_name=event_data.speaker_name or "Unknown",
  564. confidence=event_data.speaker_confidence
  565. )
  566. # Create main event data
  567. main_event_data = WakewordReceivedEventData(
  568. wakeword_type=event_data.wakeword_type,
  569. confidence=event_data.confidence,
  570. processing_time_ms=event_data.processing_time_ms,
  571. audio_chunk_position=event_data.audio_chunk_position,
  572. timestamp=event_data.timestamp,
  573. speaker_info=speaker_info
  574. )
  575. # Trigger in main event system
  576. self.main_event_handler.trigger_event("wakeword_received", main_event_data)
  577. def _forward_speaker_event(self, event_data: MLEventData) -> None:
  578. """Forward speaker event to main event system."""
  579. # Create generic event for speaker identification
  580. self.main_event_handler.trigger_event(
  581. "speaker_identified",
  582. event_data.metadata
  583. )
  584. def _forward_system_event(self, event_data: MLEventData) -> None:
  585. """Forward critical event as system event."""
  586. self.main_event_handler.trigger_event(
  587. "ml_system_event",
  588. {
  589. 'ml_event_type': event_data.event_type,
  590. 'priority': event_data.priority.value,
  591. 'timestamp': event_data.timestamp,
  592. 'metadata': event_data.metadata
  593. }
  594. )
  595. def _should_process_event(self, event_data: MLEventData) -> bool:
  596. """Check if event should be processed based on filters."""
  597. for filter_func in self._event_filters:
  598. try:
  599. if not filter_func(event_data):
  600. return False
  601. except Exception as e:
  602. logger.error(f"Error in event filter: {e}")
  603. return True
  604. def _auto_register_handlers(self, component: Any) -> None:
  605. """Auto-register event handlers from component."""
  606. # Look for methods with ML event decorators
  607. for attr_name in dir(component):
  608. attr = getattr(component, attr_name)
  609. if callable(attr) and hasattr(attr, '_ml_events'):
  610. # Register handler for each event type
  611. for event_type in attr._ml_events:
  612. if isinstance(event_type, MLEventType):
  613. self.add_event_handler(event_type, attr)
  614. logger.debug(f"Auto-registered handler {attr_name} for {event_type.value}")
  615. def MLEvent(event_types: List[MLEventType]) -> Callable:
  616. """
  617. Decorator for ML event handlers.
  618. Args:
  619. event_types: List of ML event types to handle
  620. Returns:
  621. Decorated function
  622. """
  623. def decorator(func: Callable) -> Callable:
  624. func._ml_events = event_types
  625. return func
  626. return decorator
  627. def create_ml_event_manager(
  628. main_event_handler: Optional[EventHandler] = None,
  629. **kwargs
  630. ) -> MLEventManager:
  631. """
  632. Factory function to create an ML event manager.
  633. Args:
  634. main_event_handler: Main Trixy event handler
  635. **kwargs: Additional configuration options
  636. Returns:
  637. Configured ML event manager
  638. """
  639. return MLEventManager(main_event_handler=main_event_handler, **kwargs)
  640. # Export main classes and functions
  641. __all__ = [
  642. 'MLEventManager',
  643. 'MLEventType',
  644. 'EventPriority',
  645. 'MLEventData',
  646. 'WakewordEventData',
  647. 'SpeakerEventData',
  648. 'AudioEventData',
  649. 'WorkflowEventData',
  650. 'PerformanceEventData',
  651. 'ModelEventData',
  652. 'NetworkEventData',
  653. 'EventCorrelation',
  654. 'MLEvent',
  655. 'create_ml_event_manager'
  656. ]