event_integration.py 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842
  1. """
  2. Event System Integration for Trixy ML Components.
  3. This module provides comprehensive event system integration for the complete
  4. ML workflow in the Trixy voice assistant. It handles event routing, workflow
  5. orchestration, and integration with the main application event system.
  6. Key Features:
  7. - Complete workflow event integration (wakeword -> voice recognition -> response)
  8. - Event routing and filtering
  9. - Workflow state management
  10. - Event analytics and monitoring
  11. - Custom event handlers and middleware
  12. - Integration with application container
  13. - Network protocol event bridging
  14. - Performance tracking and metrics
  15. Usage:
  16. from trixy_core.ml import MLEventManager, EventWorkflow
  17. # Create event manager
  18. event_manager = MLEventManager(app_container)
  19. # Register ML components
  20. event_manager.register_ml_manager(ml_manager)
  21. event_manager.register_audio_pipeline(audio_pipeline)
  22. # Start event processing
  23. await event_manager.start()
  24. """
  25. import asyncio
  26. import logging
  27. import time
  28. import threading
  29. from typing import Dict, Any, Optional, List, Callable, Union, Type
  30. from dataclasses import dataclass, field
  31. from enum import Enum
  32. import uuid
  33. from datetime import datetime
  34. import json
  35. from collections import defaultdict, deque
  36. # ML component imports
  37. from .ml_manager import MLManager, WakewordEvent, AudioProcessingResult, MLProcessingState
  38. from .audio_pipeline import AudioPipeline, AudioChunk, AudioQualityMetrics
  39. from .wakeword import WakewordType, DetectionResult
  40. from .voice_recognition import IdentificationResult
  41. class EventType(Enum):
  42. """Event types in the ML workflow."""
  43. # System events
  44. SYSTEM_STARTED = "system_started"
  45. SYSTEM_STOPPED = "system_stopped"
  46. SYSTEM_ERROR = "system_error"
  47. # Audio events
  48. AUDIO_INPUT_RECEIVED = "audio_input_received"
  49. AUDIO_QUALITY_ALERT = "audio_quality_alert"
  50. AUDIO_PIPELINE_ERROR = "audio_pipeline_error"
  51. SILENCE_DETECTED = "silence_detected"
  52. SILENCE_TIMEOUT = "silence_timeout"
  53. # Wakeword events
  54. WAKEWORD_RECEIVED = "wakeword_received"
  55. WAKEWORD_VALIDATED = "wakeword_validated"
  56. WAKEWORD_REJECTED = "wakeword_rejected"
  57. # Voice recognition events
  58. SPEAKER_IDENTIFIED = "speaker_identified"
  59. SPEAKER_UNKNOWN = "speaker_unknown"
  60. RAW_AUDIO_INPUT_RECEIVED = "raw_audio_input_received"
  61. # Workflow events
  62. CONVERSATION_STARTED = "conversation_started"
  63. CONVERSATION_ENDED = "conversation_ended"
  64. RECORDING_STARTED = "recording_started"
  65. RECORDING_COMPLETED = "recording_completed"
  66. # Network events
  67. SERVER_CONNECTED = "server_connected"
  68. SERVER_DISCONNECTED = "server_disconnected"
  69. AUDIO_STREAM_STARTED = "audio_stream_started"
  70. AUDIO_STREAM_STOPPED = "audio_stream_stopped"
  71. # Performance events
  72. PERFORMANCE_ALERT = "performance_alert"
  73. MEMORY_THRESHOLD_EXCEEDED = "memory_threshold_exceeded"
  74. PROCESSING_LATENCY_HIGH = "processing_latency_high"
  75. class EventPriority(Enum):
  76. """Event priority levels."""
  77. LOW = 1
  78. NORMAL = 2
  79. HIGH = 3
  80. CRITICAL = 4
  81. class WorkflowState(Enum):
  82. """Workflow state enumeration."""
  83. IDLE = "idle"
  84. LISTENING = "listening"
  85. WAKEWORD_DETECTED = "wakeword_detected"
  86. RECORDING = "recording"
  87. PROCESSING = "processing"
  88. WAITING_FOR_RESPONSE = "waiting_for_response"
  89. ERROR = "error"
  90. @dataclass
  91. class MLEvent:
  92. """ML event data structure."""
  93. event_id: str
  94. event_type: EventType
  95. timestamp: float
  96. source: str
  97. priority: EventPriority = EventPriority.NORMAL
  98. data: Dict[str, Any] = field(default_factory=dict)
  99. metadata: Dict[str, Any] = field(default_factory=dict)
  100. conversation_id: Optional[str] = None
  101. workflow_state: Optional[WorkflowState] = None
  102. def to_dict(self) -> Dict[str, Any]:
  103. """Convert event to dictionary."""
  104. return {
  105. "event_id": self.event_id,
  106. "event_type": self.event_type.value,
  107. "timestamp": self.timestamp,
  108. "source": self.source,
  109. "priority": self.priority.value,
  110. "data": self.data,
  111. "metadata": self.metadata,
  112. "conversation_id": self.conversation_id,
  113. "workflow_state": self.workflow_state.value if self.workflow_state else None
  114. }
  115. @classmethod
  116. def from_dict(cls, data: Dict[str, Any]) -> 'MLEvent':
  117. """Create event from dictionary."""
  118. return cls(
  119. event_id=data["event_id"],
  120. event_type=EventType(data["event_type"]),
  121. timestamp=data["timestamp"],
  122. source=data["source"],
  123. priority=EventPriority(data["priority"]),
  124. data=data.get("data", {}),
  125. metadata=data.get("metadata", {}),
  126. conversation_id=data.get("conversation_id"),
  127. workflow_state=WorkflowState(data["workflow_state"]) if data.get("workflow_state") else None
  128. )
  129. @dataclass
  130. class ConversationContext:
  131. """Context for a conversation session."""
  132. conversation_id: str
  133. start_time: float
  134. end_time: Optional[float] = None
  135. wakeword_event: Optional[WakewordEvent] = None
  136. audio_result: Optional[AudioProcessingResult] = None
  137. workflow_state: WorkflowState = WorkflowState.IDLE
  138. events: List[MLEvent] = field(default_factory=list)
  139. metadata: Dict[str, Any] = field(default_factory=dict)
  140. def duration(self) -> float:
  141. """Get conversation duration."""
  142. end = self.end_time or time.time()
  143. return end - self.start_time
  144. def add_event(self, event: MLEvent) -> None:
  145. """Add event to conversation."""
  146. event.conversation_id = self.conversation_id
  147. event.workflow_state = self.workflow_state
  148. self.events.append(event)
  149. class EventHandler:
  150. """Base event handler interface."""
  151. def __init__(self, name: str):
  152. self.name = name
  153. self.enabled = True
  154. async def handle_event(self, event: MLEvent) -> bool:
  155. """
  156. Handle an event.
  157. Args:
  158. event: Event to handle
  159. Returns:
  160. True if event was handled successfully
  161. """
  162. raise NotImplementedError
  163. class EventFilter:
  164. """Event filter for selective processing."""
  165. def __init__(self, name: str):
  166. self.name = name
  167. self.enabled = True
  168. def should_process(self, event: MLEvent) -> bool:
  169. """
  170. Check if event should be processed.
  171. Args:
  172. event: Event to check
  173. Returns:
  174. True if event should be processed
  175. """
  176. raise NotImplementedError
  177. class WorkflowEventHandler(EventHandler):
  178. """Handler for workflow state transitions."""
  179. def __init__(self, event_manager: 'MLEventManager'):
  180. super().__init__("workflow_handler")
  181. self.event_manager = event_manager
  182. async def handle_event(self, event: MLEvent) -> bool:
  183. """Handle workflow events."""
  184. if event.event_type == EventType.WAKEWORD_RECEIVED:
  185. await self._handle_wakeword_event(event)
  186. elif event.event_type == EventType.RAW_AUDIO_INPUT_RECEIVED:
  187. await self._handle_audio_input_event(event)
  188. elif event.event_type == EventType.SILENCE_TIMEOUT:
  189. await self._handle_silence_timeout(event)
  190. elif event.event_type == EventType.SYSTEM_ERROR:
  191. await self._handle_system_error(event)
  192. return True
  193. async def _handle_wakeword_event(self, event: MLEvent) -> None:
  194. """Handle wakeword detection event."""
  195. # Start new conversation
  196. conversation_id = str(uuid.uuid4())
  197. # Create conversation context
  198. context = ConversationContext(
  199. conversation_id=conversation_id,
  200. start_time=time.time(),
  201. workflow_state=WorkflowState.WAKEWORD_DETECTED
  202. )
  203. # Store conversation
  204. self.event_manager._conversations[conversation_id] = context
  205. self.event_manager._current_conversation = conversation_id
  206. # Trigger conversation started event
  207. await self.event_manager.emit_event(
  208. EventType.CONVERSATION_STARTED,
  209. source="workflow_handler",
  210. data={
  211. "conversation_id": conversation_id,
  212. "wakeword_type": event.data.get("wakeword_type"),
  213. "confidence": event.data.get("confidence")
  214. },
  215. conversation_id=conversation_id
  216. )
  217. # Update workflow state
  218. context.workflow_state = WorkflowState.RECORDING
  219. # Trigger recording started event
  220. await self.event_manager.emit_event(
  221. EventType.RECORDING_STARTED,
  222. source="workflow_handler",
  223. conversation_id=conversation_id
  224. )
  225. async def _handle_audio_input_event(self, event: MLEvent) -> None:
  226. """Handle raw audio input event."""
  227. conversation_id = event.conversation_id or self.event_manager._current_conversation
  228. if conversation_id and conversation_id in self.event_manager._conversations:
  229. context = self.event_manager._conversations[conversation_id]
  230. context.workflow_state = WorkflowState.PROCESSING
  231. # Trigger recording completed event
  232. await self.event_manager.emit_event(
  233. EventType.RECORDING_COMPLETED,
  234. source="workflow_handler",
  235. data={
  236. "speaker_id": event.data.get("speaker_id"),
  237. "speaker_name": event.data.get("speaker_name"),
  238. "confidence": event.data.get("confidence"),
  239. "duration": event.data.get("duration")
  240. },
  241. conversation_id=conversation_id
  242. )
  243. async def _handle_silence_timeout(self, event: MLEvent) -> None:
  244. """Handle silence timeout event."""
  245. conversation_id = event.conversation_id or self.event_manager._current_conversation
  246. if conversation_id and conversation_id in self.event_manager._conversations:
  247. await self._end_conversation(conversation_id, "silence_timeout")
  248. async def _handle_system_error(self, event: MLEvent) -> None:
  249. """Handle system error event."""
  250. conversation_id = event.conversation_id or self.event_manager._current_conversation
  251. if conversation_id and conversation_id in self.event_manager._conversations:
  252. context = self.event_manager._conversations[conversation_id]
  253. context.workflow_state = WorkflowState.ERROR
  254. await self._end_conversation(conversation_id, "system_error")
  255. async def _end_conversation(self, conversation_id: str, reason: str) -> None:
  256. """End a conversation."""
  257. if conversation_id in self.event_manager._conversations:
  258. context = self.event_manager._conversations[conversation_id]
  259. context.end_time = time.time()
  260. context.workflow_state = WorkflowState.IDLE
  261. # Trigger conversation ended event
  262. await self.event_manager.emit_event(
  263. EventType.CONVERSATION_ENDED,
  264. source="workflow_handler",
  265. data={
  266. "conversation_id": conversation_id,
  267. "reason": reason,
  268. "duration": context.duration()
  269. },
  270. conversation_id=conversation_id
  271. )
  272. # Clear current conversation if this was it
  273. if self.event_manager._current_conversation == conversation_id:
  274. self.event_manager._current_conversation = None
  275. class NetworkEventHandler(EventHandler):
  276. """Handler for network protocol events."""
  277. def __init__(self, network_client: Optional[Any] = None):
  278. super().__init__("network_handler")
  279. self.network_client = network_client
  280. async def handle_event(self, event: MLEvent) -> bool:
  281. """Handle network events."""
  282. if not self.network_client:
  283. return True
  284. try:
  285. if event.event_type in [
  286. EventType.WAKEWORD_RECEIVED,
  287. EventType.RAW_AUDIO_INPUT_RECEIVED,
  288. EventType.CONVERSATION_STARTED,
  289. EventType.CONVERSATION_ENDED
  290. ]:
  291. await self._send_event_to_server(event)
  292. return True
  293. except Exception as e:
  294. logging.error(f"Error handling network event: {e}")
  295. return False
  296. async def _send_event_to_server(self, event: MLEvent) -> None:
  297. """Send event to server."""
  298. # This would integrate with the actual network client
  299. logging.debug(f"Sending event to server: {event.event_type.value}")
  300. class PerformanceEventHandler(EventHandler):
  301. """Handler for performance monitoring events."""
  302. def __init__(self):
  303. super().__init__("performance_handler")
  304. self.metrics = {
  305. "events_processed": 0,
  306. "average_processing_time": 0.0,
  307. "error_count": 0,
  308. "conversation_count": 0
  309. }
  310. async def handle_event(self, event: MLEvent) -> bool:
  311. """Handle performance events."""
  312. start_time = time.time()
  313. try:
  314. # Update metrics
  315. self.metrics["events_processed"] += 1
  316. if event.event_type == EventType.SYSTEM_ERROR:
  317. self.metrics["error_count"] += 1
  318. elif event.event_type == EventType.CONVERSATION_STARTED:
  319. self.metrics["conversation_count"] += 1
  320. # Track processing time
  321. processing_time = time.time() - start_time
  322. self.metrics["average_processing_time"] = (
  323. (self.metrics["average_processing_time"] * (self.metrics["events_processed"] - 1) +
  324. processing_time) / self.metrics["events_processed"]
  325. )
  326. return True
  327. except Exception as e:
  328. logging.error(f"Error in performance handler: {e}")
  329. self.metrics["error_count"] += 1
  330. return False
  331. class PriorityEventFilter(EventFilter):
  332. """Filter events by priority."""
  333. def __init__(self, min_priority: EventPriority):
  334. super().__init__("priority_filter")
  335. self.min_priority = min_priority
  336. def should_process(self, event: MLEvent) -> bool:
  337. """Check if event priority meets minimum threshold."""
  338. return event.priority.value >= self.min_priority.value
  339. class EventTypeFilter(EventFilter):
  340. """Filter events by type."""
  341. def __init__(self, allowed_types: List[EventType]):
  342. super().__init__("type_filter")
  343. self.allowed_types = set(allowed_types)
  344. def should_process(self, event: MLEvent) -> bool:
  345. """Check if event type is allowed."""
  346. return event.event_type in self.allowed_types
  347. class MLEventManagerError(Exception):
  348. """ML event manager exception."""
  349. pass
  350. class MLEventManager:
  351. """
  352. ML Event Manager for comprehensive event system integration.
  353. This class manages the complete event workflow for ML components,
  354. handling event routing, filtering, and integration with the application
  355. event system.
  356. """
  357. def __init__(
  358. self,
  359. app_container: Optional[Any] = None,
  360. logger: Optional[logging.Logger] = None
  361. ):
  362. """
  363. Initialize ML event manager.
  364. Args:
  365. app_container: Application container for dependency injection
  366. logger: Logger instance
  367. """
  368. self.app_container = app_container
  369. self.logger = logger or logging.getLogger(__name__)
  370. # Event management
  371. self._event_queue = asyncio.Queue()
  372. self._event_handlers: List[EventHandler] = []
  373. self._event_filters: List[EventFilter] = []
  374. self._running = False
  375. self._processing_task: Optional[asyncio.Task] = None
  376. # Workflow management
  377. self._conversations: Dict[str, ConversationContext] = {}
  378. self._current_conversation: Optional[str] = None
  379. self._workflow_state = WorkflowState.IDLE
  380. # Component references
  381. self._ml_manager: Optional[MLManager] = None
  382. self._audio_pipeline: Optional[AudioPipeline] = None
  383. # Event analytics
  384. self._event_history = deque(maxlen=1000)
  385. self._event_counts = defaultdict(int)
  386. # Initialize default handlers
  387. self._init_default_handlers()
  388. self.logger.info("ML Event Manager initialized")
  389. def _init_default_handlers(self) -> None:
  390. """Initialize default event handlers."""
  391. # Add workflow handler
  392. workflow_handler = WorkflowEventHandler(self)
  393. self.add_event_handler(workflow_handler)
  394. # Add performance handler
  395. performance_handler = PerformanceEventHandler()
  396. self.add_event_handler(performance_handler)
  397. # Add network handler if network client is available
  398. network_client = getattr(self.app_container, 'network_client', None) if self.app_container else None
  399. if network_client:
  400. network_handler = NetworkEventHandler(network_client)
  401. self.add_event_handler(network_handler)
  402. async def start(self) -> None:
  403. """Start event processing."""
  404. if self._running:
  405. self.logger.warning("Event manager already running")
  406. return
  407. self.logger.info("Starting ML event manager...")
  408. self._running = True
  409. # Start event processing task
  410. self._processing_task = asyncio.create_task(self._event_processing_loop())
  411. # Emit system started event
  412. await self.emit_event(
  413. EventType.SYSTEM_STARTED,
  414. source="event_manager",
  415. data={"timestamp": time.time()}
  416. )
  417. self.logger.info("ML event manager started")
  418. async def stop(self) -> None:
  419. """Stop event processing."""
  420. if not self._running:
  421. return
  422. self.logger.info("Stopping ML event manager...")
  423. # Emit system stopped event
  424. await self.emit_event(
  425. EventType.SYSTEM_STOPPED,
  426. source="event_manager",
  427. data={"timestamp": time.time()}
  428. )
  429. self._running = False
  430. # Cancel processing task
  431. if self._processing_task:
  432. self._processing_task.cancel()
  433. try:
  434. await self._processing_task
  435. except asyncio.CancelledError:
  436. pass
  437. # End any active conversations
  438. for conversation_id in list(self._conversations.keys()):
  439. if self._conversations[conversation_id].end_time is None:
  440. await self._end_conversation(conversation_id, "system_shutdown")
  441. self.logger.info("ML event manager stopped")
  442. def register_ml_manager(self, ml_manager: MLManager) -> None:
  443. """Register ML manager for event integration."""
  444. self._ml_manager = ml_manager
  445. # Set up ML manager event callbacks
  446. ml_manager.event_handler = self
  447. self.logger.info("ML manager registered with event system")
  448. def register_audio_pipeline(self, audio_pipeline: AudioPipeline) -> None:
  449. """Register audio pipeline for event integration."""
  450. self._audio_pipeline = audio_pipeline
  451. # Set up audio pipeline callbacks
  452. audio_pipeline.on_audio_processed = self._handle_audio_processed
  453. audio_pipeline.on_silence_detected = self._handle_silence_detected
  454. audio_pipeline.on_quality_alert = self._handle_quality_alert
  455. audio_pipeline.on_error = self._handle_audio_error
  456. self.logger.info("Audio pipeline registered with event system")
  457. def add_event_handler(self, handler: EventHandler) -> None:
  458. """Add event handler."""
  459. self._event_handlers.append(handler)
  460. self.logger.info(f"Added event handler: {handler.name}")
  461. def remove_event_handler(self, handler: EventHandler) -> None:
  462. """Remove event handler."""
  463. if handler in self._event_handlers:
  464. self._event_handlers.remove(handler)
  465. self.logger.info(f"Removed event handler: {handler.name}")
  466. def add_event_filter(self, event_filter: EventFilter) -> None:
  467. """Add event filter."""
  468. self._event_filters.append(event_filter)
  469. self.logger.info(f"Added event filter: {event_filter.name}")
  470. def remove_event_filter(self, event_filter: EventFilter) -> None:
  471. """Remove event filter."""
  472. if event_filter in self._event_filters:
  473. self._event_filters.remove(event_filter)
  474. self.logger.info(f"Removed event filter: {event_filter.name}")
  475. async def emit_event(
  476. self,
  477. event_type: EventType,
  478. source: str,
  479. data: Optional[Dict[str, Any]] = None,
  480. priority: EventPriority = EventPriority.NORMAL,
  481. conversation_id: Optional[str] = None,
  482. metadata: Optional[Dict[str, Any]] = None
  483. ) -> str:
  484. """
  485. Emit an event.
  486. Args:
  487. event_type: Type of event
  488. source: Event source identifier
  489. data: Event data
  490. priority: Event priority
  491. conversation_id: Associated conversation ID
  492. metadata: Event metadata
  493. Returns:
  494. Event ID
  495. """
  496. event = MLEvent(
  497. event_id=str(uuid.uuid4()),
  498. event_type=event_type,
  499. timestamp=time.time(),
  500. source=source,
  501. priority=priority,
  502. data=data or {},
  503. metadata=metadata or {},
  504. conversation_id=conversation_id,
  505. workflow_state=self._workflow_state
  506. )
  507. # Add to queue for processing
  508. await self._event_queue.put(event)
  509. return event.event_id
  510. async def _event_processing_loop(self) -> None:
  511. """Main event processing loop."""
  512. self.logger.info("Event processing loop started")
  513. while self._running:
  514. try:
  515. # Get next event
  516. event = await asyncio.wait_for(self._event_queue.get(), timeout=1.0)
  517. # Process event
  518. await self._process_event(event)
  519. except asyncio.TimeoutError:
  520. continue
  521. except Exception as e:
  522. self.logger.error(f"Error in event processing loop: {e}")
  523. await asyncio.sleep(0.1) # Brief pause to prevent tight error loop
  524. self.logger.info("Event processing loop stopped")
  525. async def _process_event(self, event: MLEvent) -> None:
  526. """Process a single event."""
  527. try:
  528. # Apply filters
  529. for event_filter in self._event_filters:
  530. if event_filter.enabled and not event_filter.should_process(event):
  531. return
  532. # Add to history and update counts
  533. self._event_history.append(event)
  534. self._event_counts[event.event_type] += 1
  535. # Add to conversation if applicable
  536. if event.conversation_id and event.conversation_id in self._conversations:
  537. self._conversations[event.conversation_id].add_event(event)
  538. # Process with handlers
  539. for handler in self._event_handlers:
  540. if handler.enabled:
  541. try:
  542. await handler.handle_event(event)
  543. except Exception as e:
  544. self.logger.error(f"Error in event handler {handler.name}: {e}")
  545. # Integration with application event system
  546. if self.app_container and hasattr(self.app_container, 'event_system'):
  547. await self._integrate_with_app_events(event)
  548. except Exception as e:
  549. self.logger.error(f"Error processing event {event.event_id}: {e}")
  550. # Emit error event
  551. await self.emit_event(
  552. EventType.SYSTEM_ERROR,
  553. source="event_manager",
  554. data={"error": str(e), "original_event": event.event_id},
  555. priority=EventPriority.HIGH
  556. )
  557. async def _integrate_with_app_events(self, event: MLEvent) -> None:
  558. """Integrate with application event system."""
  559. try:
  560. # Convert ML event to application event format
  561. app_event_data = {
  562. "type": f"ml_{event.event_type.value}",
  563. "source": "ml_system",
  564. "data": event.to_dict()
  565. }
  566. # Emit to application event system
  567. # This would integrate with the actual application event system
  568. self.logger.debug(f"Integrating event with app system: {event.event_type.value}")
  569. except Exception as e:
  570. self.logger.error(f"Error integrating with app events: {e}")
  571. # Event callback methods for ML components
  572. async def _handle_audio_processed(self, audio_chunk: AudioChunk) -> None:
  573. """Handle audio processed callback."""
  574. await self.emit_event(
  575. EventType.AUDIO_INPUT_RECEIVED,
  576. source="audio_pipeline",
  577. data={
  578. "timestamp": audio_chunk.timestamp,
  579. "sequence_number": audio_chunk.sequence_number,
  580. "volume": audio_chunk.volume,
  581. "is_silence": audio_chunk.is_silence
  582. }
  583. )
  584. async def _handle_silence_detected(self, audio_chunk: AudioChunk) -> None:
  585. """Handle silence detected callback."""
  586. await self.emit_event(
  587. EventType.SILENCE_DETECTED,
  588. source="audio_pipeline",
  589. data={
  590. "timestamp": audio_chunk.timestamp,
  591. "sequence_number": audio_chunk.sequence_number
  592. }
  593. )
  594. async def _handle_quality_alert(self, metrics: AudioQualityMetrics, alerts: List[str]) -> None:
  595. """Handle audio quality alert callback."""
  596. await self.emit_event(
  597. EventType.AUDIO_QUALITY_ALERT,
  598. source="audio_pipeline",
  599. data={
  600. "alerts": alerts,
  601. "snr_db": metrics.snr_estimate,
  602. "peak_level": metrics.peak_level,
  603. "silence_ratio": metrics.silence_ratio
  604. },
  605. priority=EventPriority.HIGH
  606. )
  607. async def _handle_audio_error(self, error_message: str) -> None:
  608. """Handle audio pipeline error callback."""
  609. await self.emit_event(
  610. EventType.AUDIO_PIPELINE_ERROR,
  611. source="audio_pipeline",
  612. data={"error": error_message},
  613. priority=EventPriority.HIGH
  614. )
  615. def get_conversation_context(self, conversation_id: str) -> Optional[ConversationContext]:
  616. """Get conversation context by ID."""
  617. return self._conversations.get(conversation_id)
  618. def get_current_conversation(self) -> Optional[ConversationContext]:
  619. """Get current active conversation."""
  620. if self._current_conversation:
  621. return self._conversations.get(self._current_conversation)
  622. return None
  623. def get_event_statistics(self) -> Dict[str, Any]:
  624. """Get event processing statistics."""
  625. total_events = sum(self._event_counts.values())
  626. return {
  627. "total_events": total_events,
  628. "event_counts": dict(self._event_counts),
  629. "active_conversations": len([c for c in self._conversations.values() if c.end_time is None]),
  630. "total_conversations": len(self._conversations),
  631. "handlers_count": len(self._event_handlers),
  632. "filters_count": len(self._event_filters),
  633. "queue_size": self._event_queue.qsize(),
  634. "workflow_state": self._workflow_state.value
  635. }
  636. async def _end_conversation(self, conversation_id: str, reason: str) -> None:
  637. """End a conversation (used by workflow handler)."""
  638. if conversation_id in self._conversations:
  639. context = self._conversations[conversation_id]
  640. context.end_time = time.time()
  641. context.workflow_state = WorkflowState.IDLE
  642. # Clear current conversation if this was it
  643. if self._current_conversation == conversation_id:
  644. self._current_conversation = None
  645. def create_ml_event_manager(
  646. app_container: Optional[Any] = None,
  647. logger: Optional[logging.Logger] = None
  648. ) -> MLEventManager:
  649. """
  650. Create ML event manager.
  651. Args:
  652. app_container: Application container
  653. logger: Logger instance
  654. Returns:
  655. ML event manager instance
  656. """
  657. return MLEventManager(app_container, logger)
  658. def create_priority_filter(min_priority: EventPriority) -> PriorityEventFilter:
  659. """
  660. Create priority event filter.
  661. Args:
  662. min_priority: Minimum priority level
  663. Returns:
  664. Priority event filter
  665. """
  666. return PriorityEventFilter(min_priority)
  667. def create_type_filter(allowed_types: List[EventType]) -> EventTypeFilter:
  668. """
  669. Create event type filter.
  670. Args:
  671. allowed_types: List of allowed event types
  672. Returns:
  673. Event type filter
  674. """
  675. return EventTypeFilter(allowed_types)