omnimancer-cli 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. omnimancer/__init__.py +42 -0
  2. omnimancer/__main__.py +9 -0
  3. omnimancer/cli/__init__.py +8 -0
  4. omnimancer/cli/agent_manager.py +826 -0
  5. omnimancer/cli/agent_wizard.py +944 -0
  6. omnimancer/cli/approval_formatter.py +734 -0
  7. omnimancer/cli/approval_integration.py +734 -0
  8. omnimancer/cli/approval_prompt.py +1286 -0
  9. omnimancer/cli/batch_approval_display.py +617 -0
  10. omnimancer/cli/batch_approval_filters.py +729 -0
  11. omnimancer/cli/batch_operation_monitor.py +775 -0
  12. omnimancer/cli/commands/performance_cmd.py +624 -0
  13. omnimancer/cli/commands.py +757 -0
  14. omnimancer/cli/completion_handler.py +281 -0
  15. omnimancer/cli/config_setup_wizard.py +783 -0
  16. omnimancer/cli/dynamic_commands.py +371 -0
  17. omnimancer/cli/handlers/__init__.py +18 -0
  18. omnimancer/cli/handlers/agent_handler.py +865 -0
  19. omnimancer/cli/handlers/agent_persona_handler.py +1048 -0
  20. omnimancer/cli/handlers/custom_agent_handler.py +352 -0
  21. omnimancer/cli/handlers/permissions_handler.py +1609 -0
  22. omnimancer/cli/interface.py +6695 -0
  23. omnimancer/cli/performance_dashboard.py +612 -0
  24. omnimancer/core/__init__.py +34 -0
  25. omnimancer/core/agent/__init__.py +256 -0
  26. omnimancer/core/agent/agent_config.py +715 -0
  27. omnimancer/core/agent/agent_switcher.py +668 -0
  28. omnimancer/core/agent/approval_context.py +460 -0
  29. omnimancer/core/agent/approval_dialog.py +902 -0
  30. omnimancer/core/agent/approval_interface.py +628 -0
  31. omnimancer/core/agent/approval_manager.py +760 -0
  32. omnimancer/core/agent/cli_integration.py +470 -0
  33. omnimancer/core/agent/config.py +845 -0
  34. omnimancer/core/agent/custom_agent_integration.py +654 -0
  35. omnimancer/core/agent/diff_renderer.py +706 -0
  36. omnimancer/core/agent/file_content_display.py +503 -0
  37. omnimancer/core/agent/file_modification_workflow.py +658 -0
  38. omnimancer/core/agent/file_system_manager.py +1790 -0
  39. omnimancer/core/agent/input_handler.py +614 -0
  40. omnimancer/core/agent/metrics_collector.py +771 -0
  41. omnimancer/core/agent/optimization_engine.py +729 -0
  42. omnimancer/core/agent/performance_monitor.py +717 -0
  43. omnimancer/core/agent/persona.py +870 -0
  44. omnimancer/core/agent/persona_validator.py +672 -0
  45. omnimancer/core/agent/program_executor.py +748 -0
  46. omnimancer/core/agent/proposed_changes_integration.py +663 -0
  47. omnimancer/core/agent/read_before_write_errors.py +492 -0
  48. omnimancer/core/agent/read_before_write_ui.py +647 -0
  49. omnimancer/core/agent/rich_renderer.py +763 -0
  50. omnimancer/core/agent/status_config.py +238 -0
  51. omnimancer/core/agent/status_core.py +446 -0
  52. omnimancer/core/agent/status_integrated_engine.py +540 -0
  53. omnimancer/core/agent/status_manager.py +878 -0
  54. omnimancer/core/agent/status_ui_components.py +535 -0
  55. omnimancer/core/agent/template_applicator.py +548 -0
  56. omnimancer/core/agent/terminal_status_listener.py +394 -0
  57. omnimancer/core/agent/token_tracker.py +679 -0
  58. omnimancer/core/agent/types.py +60 -0
  59. omnimancer/core/agent/web_client.py +745 -0
  60. omnimancer/core/agent/workflow_orchestrator.py +826 -0
  61. omnimancer/core/agent_engine.py +1625 -0
  62. omnimancer/core/agent_mode_manager.py +706 -0
  63. omnimancer/core/agent_progress_ui.py +545 -0
  64. omnimancer/core/chat_manager.py +141 -0
  65. omnimancer/core/config_manager.py +2199 -0
  66. omnimancer/core/config_migration.py +772 -0
  67. omnimancer/core/config_provider.py +1974 -0
  68. omnimancer/core/config_security_validator.py +393 -0
  69. omnimancer/core/config_validator.py +768 -0
  70. omnimancer/core/conversation_manager.py +352 -0
  71. omnimancer/core/engine.py +787 -0
  72. omnimancer/core/enhanced_input.py +523 -0
  73. omnimancer/core/env_loader.py +83 -0
  74. omnimancer/core/fallback_manager.py +568 -0
  75. omnimancer/core/health_monitor.py +462 -0
  76. omnimancer/core/history_manager.py +462 -0
  77. omnimancer/core/mcp_integration_layer.py +878 -0
  78. omnimancer/core/models.py +1889 -0
  79. omnimancer/core/provider_capabilities.py +403 -0
  80. omnimancer/core/provider_initializer.py +450 -0
  81. omnimancer/core/provider_registry.py +432 -0
  82. omnimancer/core/security/__init__.py +16 -0
  83. omnimancer/core/security/approval_workflow.py +406 -0
  84. omnimancer/core/security/audit_logger.py +511 -0
  85. omnimancer/core/security/permission_controller.py +511 -0
  86. omnimancer/core/security/sandbox_manager.py +379 -0
  87. omnimancer/core/security/security_manager.py +530 -0
  88. omnimancer/core/setup_wizard.py +345 -0
  89. omnimancer/core/setup_wizard_core.py +449 -0
  90. omnimancer/core/setup_wizard_guide_generator.py +376 -0
  91. omnimancer/core/setup_wizard_provider_setup.py +454 -0
  92. omnimancer/core/setup_wizard_ui.py +317 -0
  93. omnimancer/core/setup_wizard_validation.py +371 -0
  94. omnimancer/core/signal_handler.py +174 -0
  95. omnimancer/mcp/__init__.py +11 -0
  96. omnimancer/mcp/client.py +725 -0
  97. omnimancer/mcp/manager.py +649 -0
  98. omnimancer/providers/__init__.py +24 -0
  99. omnimancer/providers/azure.py +608 -0
  100. omnimancer/providers/base.py +262 -0
  101. omnimancer/providers/bedrock.py +711 -0
  102. omnimancer/providers/claude.py +426 -0
  103. omnimancer/providers/claude_code.py +406 -0
  104. omnimancer/providers/cohere.py +412 -0
  105. omnimancer/providers/factory.py +988 -0
  106. omnimancer/providers/gemini.py +708 -0
  107. omnimancer/providers/mistral.py +554 -0
  108. omnimancer/providers/ollama.py +510 -0
  109. omnimancer/providers/openai.py +362 -0
  110. omnimancer/providers/openrouter.py +709 -0
  111. omnimancer/providers/perplexity.py +476 -0
  112. omnimancer/providers/vertex.py +617 -0
  113. omnimancer/providers/xai.py +484 -0
  114. omnimancer/ui/__init__.py +6 -0
  115. omnimancer/ui/cancellation_handler.py +235 -0
  116. omnimancer/ui/progress_indicator.py +318 -0
  117. omnimancer/utils/__init__.py +22 -0
  118. omnimancer/utils/error_handler.py +589 -0
  119. omnimancer/utils/errors.py +667 -0
  120. omnimancer/utils/retry.py +238 -0
  121. omnimancer_cli-0.1.0.dist-info/METADATA +413 -0
  122. omnimancer_cli-0.1.0.dist-info/RECORD +126 -0
  123. omnimancer_cli-0.1.0.dist-info/WHEEL +5 -0
  124. omnimancer_cli-0.1.0.dist-info/entry_points.txt +4 -0
  125. omnimancer_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
  126. omnimancer_cli-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,717 @@
1
+ """
2
+ Agent Performance Monitoring System for Omnimancer.
3
+
4
+ This module provides comprehensive performance monitoring for agent operations,
5
+ including token usage tracking, context efficiency measurement, operation timing,
6
+ and resource utilization metrics with optimization suggestions.
7
+ """
8
+
9
+ import logging
10
+ import statistics
11
+ import threading
12
+ import time
13
+ from collections import defaultdict, deque
14
+ from dataclasses import asdict, dataclass, field
15
+ from datetime import datetime, timedelta
16
+ from enum import Enum
17
+ from pathlib import Path
18
+ from typing import Any, Callable, Dict, List, Optional
19
+
20
+
21
+ from .persona import PersonaManager, get_persona_manager
22
+ from .status_core import (
23
+ AgentEvent,
24
+ EventType,
25
+ OperationType,
26
+ )
27
+ from .status_manager import UnifiedStatusManager as AgentStatusManager
28
+ from .status_manager import (
29
+ get_status_manager,
30
+ )
31
+
32
+ logger = logging.getLogger(__name__)
33
+
34
+
35
+ class MetricType(Enum):
36
+ """Types of performance metrics."""
37
+
38
+ TOKEN_USAGE = "token_usage"
39
+ OPERATION_TIMING = "operation_timing"
40
+ CONTEXT_EFFICIENCY = "context_efficiency"
41
+ RESOURCE_UTILIZATION = "resource_utilization"
42
+ SUCCESS_RATE = "success_rate"
43
+ COST_TRACKING = "cost_tracking"
44
+ PERSONA_PERFORMANCE = "persona_performance"
45
+
46
+
47
+ class AlertLevel(Enum):
48
+ """Alert severity levels."""
49
+
50
+ INFO = "info"
51
+ WARNING = "warning"
52
+ CRITICAL = "critical"
53
+ EMERGENCY = "emergency"
54
+
55
+
56
+ class OptimizationType(Enum):
57
+ """Types of optimization suggestions."""
58
+
59
+ PROVIDER_SWITCHING = "provider_switching"
60
+ CONTEXT_REDUCTION = "context_reduction"
61
+ PERSONA_OPTIMIZATION = "persona_optimization"
62
+ RESOURCE_TUNING = "resource_tuning"
63
+ COST_OPTIMIZATION = "cost_optimization"
64
+
65
+
66
+ @dataclass
67
+ class TokenUsageMetrics:
68
+ """Token usage metrics for a specific operation or time period."""
69
+
70
+ total_tokens: int = 0
71
+ input_tokens: int = 0
72
+ output_tokens: int = 0
73
+ cached_tokens: int = 0
74
+ provider_name: str = ""
75
+ model_name: str = ""
76
+ operation_type: OperationType = OperationType.API_CALL
77
+ timestamp: datetime = field(default_factory=datetime.now)
78
+ cost_estimate: float = 0.0
79
+
80
+ @property
81
+ def efficiency_ratio(self) -> float:
82
+ """Calculate token efficiency ratio (output/total)."""
83
+ return (
84
+ (self.output_tokens / self.total_tokens) if self.total_tokens > 0 else 0.0
85
+ )
86
+
87
+
88
+ @dataclass
89
+ class OperationTiming:
90
+ """Timing metrics for agent operations."""
91
+
92
+ operation_id: str
93
+ operation_type: OperationType
94
+ start_time: datetime
95
+ end_time: Optional[datetime] = None
96
+ duration_ms: float = 0.0
97
+ context_load_time_ms: float = 0.0
98
+ api_call_time_ms: float = 0.0
99
+ processing_time_ms: float = 0.0
100
+ queue_wait_time_ms: float = 0.0
101
+
102
+ @property
103
+ def is_complete(self) -> bool:
104
+ """Check if timing is complete."""
105
+ return self.end_time is not None
106
+
107
+ def complete_timing(self, end_time: Optional[datetime] = None) -> None:
108
+ """Complete the timing measurement."""
109
+ if not end_time:
110
+ end_time = datetime.now()
111
+ self.end_time = end_time
112
+ self.duration_ms = (end_time - self.start_time).total_seconds() * 1000
113
+
114
+
115
+ @dataclass
116
+ class ContextMetrics:
117
+ """Context efficiency metrics."""
118
+
119
+ context_size_chars: int = 0
120
+ context_size_tokens: int = 0
121
+ useful_context_ratio: float = 0.0 # % of context that was actually used
122
+ context_compression_ratio: float = 0.0
123
+ context_load_time_ms: float = 0.0
124
+ context_type: str = "" # "file", "conversation", "documentation", etc.
125
+
126
+ @property
127
+ def efficiency_score(self) -> float:
128
+ """Calculate overall context efficiency score (0-100)."""
129
+ base_score = self.useful_context_ratio * 100
130
+ # Penalize slow loading
131
+ time_penalty = min(10, self.context_load_time_ms / 100)
132
+ return max(0, base_score - time_penalty)
133
+
134
+
135
+ @dataclass
136
+ class ResourceUtilization:
137
+ """Resource utilization metrics."""
138
+
139
+ memory_mb: float = 0.0
140
+ cpu_percent: float = 0.0
141
+ network_requests: int = 0
142
+ disk_reads: int = 0
143
+ disk_writes: int = 0
144
+ timestamp: datetime = field(default_factory=datetime.now)
145
+
146
+ @property
147
+ def overall_utilization(self) -> float:
148
+ """Calculate overall resource utilization score."""
149
+ return (self.memory_mb / 1024) * 0.3 + self.cpu_percent * 0.7
150
+
151
+
152
+ @dataclass
153
+ class PerformanceAlert:
154
+ """Performance monitoring alert."""
155
+
156
+ alert_id: str
157
+ level: AlertLevel
158
+ title: str
159
+ description: str
160
+ metric_type: MetricType
161
+ threshold_value: float
162
+ current_value: float
163
+ timestamp: datetime = field(default_factory=datetime.now)
164
+ persona_name: Optional[str] = None
165
+ provider_name: Optional[str] = None
166
+ suggested_actions: List[str] = field(default_factory=list)
167
+ auto_resolvable: bool = False
168
+
169
+
170
+ @dataclass
171
+ class OptimizationSuggestion:
172
+ """Performance optimization suggestion."""
173
+
174
+ suggestion_id: str
175
+ optimization_type: OptimizationType
176
+ title: str
177
+ description: str
178
+ potential_improvement: str # "20% faster", "30% cost reduction", etc.
179
+ confidence: float # 0-1
180
+ implementation_difficulty: str # "easy", "medium", "hard"
181
+ estimated_impact: Dict[str, float] = field(default_factory=dict)
182
+ required_actions: List[str] = field(default_factory=list)
183
+ timestamp: datetime = field(default_factory=datetime.now)
184
+
185
+
186
+ @dataclass
187
+ class PerformanceSnapshot:
188
+ """Point-in-time performance snapshot."""
189
+
190
+ timestamp: datetime = field(default_factory=datetime.now)
191
+ token_usage: TokenUsageMetrics = field(default_factory=TokenUsageMetrics)
192
+ context_metrics: ContextMetrics = field(default_factory=ContextMetrics)
193
+ resource_utilization: ResourceUtilization = field(
194
+ default_factory=ResourceUtilization
195
+ )
196
+ active_operations: int = 0
197
+ success_rate: float = 100.0
198
+ average_response_time_ms: float = 0.0
199
+ persona_name: Optional[str] = None
200
+ provider_name: Optional[str] = None
201
+
202
+
203
+ class PerformanceMetricsCollector:
204
+ """Collects and aggregates performance metrics."""
205
+
206
+ def __init__(self, max_history_size: int = 10000):
207
+ """Initialize the metrics collector."""
208
+ self.max_history_size = max_history_size
209
+ self.token_usage_history: deque = deque(maxlen=max_history_size)
210
+ self.timing_history: deque = deque(maxlen=max_history_size)
211
+ self.context_history: deque = deque(maxlen=max_history_size)
212
+ self.resource_history: deque = deque(maxlen=max_history_size)
213
+ self.active_timings: Dict[str, OperationTiming] = {}
214
+ self._lock = threading.RLock()
215
+
216
+ def record_token_usage(self, tokens: TokenUsageMetrics) -> None:
217
+ """Record token usage metrics."""
218
+ with self._lock:
219
+ self.token_usage_history.append(tokens)
220
+ logger.debug(
221
+ f"Recorded token usage: {tokens.total_tokens} tokens for {tokens.provider_name}"
222
+ )
223
+
224
+ def start_operation_timing(
225
+ self, operation_id: str, operation_type: OperationType
226
+ ) -> OperationTiming:
227
+ """Start timing an operation."""
228
+ with self._lock:
229
+ timing = OperationTiming(
230
+ operation_id=operation_id,
231
+ operation_type=operation_type,
232
+ start_time=datetime.now(),
233
+ )
234
+ self.active_timings[operation_id] = timing
235
+ return timing
236
+
237
+ def complete_operation_timing(self, operation_id: str) -> Optional[OperationTiming]:
238
+ """Complete timing measurement for an operation."""
239
+ with self._lock:
240
+ if operation_id in self.active_timings:
241
+ timing = self.active_timings.pop(operation_id)
242
+ timing.complete_timing()
243
+ self.timing_history.append(timing)
244
+ logger.debug(
245
+ f"Completed timing for {operation_id}: {timing.duration_ms}ms"
246
+ )
247
+ return timing
248
+ return None
249
+
250
+ def record_context_metrics(self, context: ContextMetrics) -> None:
251
+ """Record context efficiency metrics."""
252
+ with self._lock:
253
+ self.context_history.append(context)
254
+ logger.debug(
255
+ f"Recorded context metrics: {context.efficiency_score:.1f} efficiency score"
256
+ )
257
+
258
+ def record_resource_utilization(self, resources: ResourceUtilization) -> None:
259
+ """Record resource utilization metrics."""
260
+ with self._lock:
261
+ self.resource_history.append(resources)
262
+
263
+ def get_token_usage_stats(
264
+ self, time_window: Optional[timedelta] = None
265
+ ) -> Dict[str, Any]:
266
+ """Get aggregated token usage statistics."""
267
+ with self._lock:
268
+ if not self.token_usage_history:
269
+ return {"total_tokens": 0, "providers": {}, "operations": {}}
270
+
271
+ # Filter by time window if provided
272
+ cutoff_time = datetime.now() - time_window if time_window else None
273
+ relevant_usage = [
274
+ usage
275
+ for usage in self.token_usage_history
276
+ if not cutoff_time or usage.timestamp >= cutoff_time
277
+ ]
278
+
279
+ if not relevant_usage:
280
+ return {"total_tokens": 0, "providers": {}, "operations": {}}
281
+
282
+ # Aggregate statistics
283
+ total_tokens = sum(usage.total_tokens for usage in relevant_usage)
284
+ total_cost = sum(usage.cost_estimate for usage in relevant_usage)
285
+
286
+ # Group by provider
287
+ provider_stats = defaultdict(
288
+ lambda: {"tokens": 0, "cost": 0.0, "requests": 0}
289
+ )
290
+ for usage in relevant_usage:
291
+ provider_stats[usage.provider_name]["tokens"] += usage.total_tokens
292
+ provider_stats[usage.provider_name]["cost"] += usage.cost_estimate
293
+ provider_stats[usage.provider_name]["requests"] += 1
294
+
295
+ # Group by operation type
296
+ operation_stats = defaultdict(lambda: {"tokens": 0, "requests": 0})
297
+ for usage in relevant_usage:
298
+ operation_stats[usage.operation_type.value][
299
+ "tokens"
300
+ ] += usage.total_tokens
301
+ operation_stats[usage.operation_type.value]["requests"] += 1
302
+
303
+ return {
304
+ "total_tokens": total_tokens,
305
+ "total_cost": total_cost,
306
+ "total_requests": len(relevant_usage),
307
+ "providers": dict(provider_stats),
308
+ "operations": dict(operation_stats),
309
+ "time_period": (
310
+ f"{time_window.total_seconds():.0f} seconds"
311
+ if time_window
312
+ else "all time"
313
+ ),
314
+ }
315
+
316
+ def get_performance_snapshot(self) -> PerformanceSnapshot:
317
+ """Get current performance snapshot."""
318
+ with self._lock:
319
+ # Calculate recent metrics (last 10 minutes)
320
+ recent_window = timedelta(minutes=10)
321
+ recent_tokens = [
322
+ usage
323
+ for usage in self.token_usage_history
324
+ if (datetime.now() - usage.timestamp) <= recent_window
325
+ ]
326
+ recent_timings = [
327
+ timing
328
+ for timing in self.timing_history
329
+ if timing.is_complete
330
+ and (datetime.now() - timing.start_time) <= recent_window
331
+ ]
332
+
333
+ # Aggregate token usage
334
+ token_metrics = TokenUsageMetrics()
335
+ if recent_tokens:
336
+ token_metrics.total_tokens = sum(t.total_tokens for t in recent_tokens)
337
+ token_metrics.input_tokens = sum(t.input_tokens for t in recent_tokens)
338
+ token_metrics.output_tokens = sum(
339
+ t.output_tokens for t in recent_tokens
340
+ )
341
+ token_metrics.cost_estimate = sum(
342
+ t.cost_estimate for t in recent_tokens
343
+ )
344
+
345
+ # Calculate average response time
346
+ avg_response_time = 0.0
347
+ if recent_timings:
348
+ avg_response_time = statistics.mean(
349
+ t.duration_ms for t in recent_timings
350
+ )
351
+
352
+ # Calculate success rate
353
+ total_ops = len(recent_timings)
354
+ successful_ops = len(
355
+ [t for t in recent_timings if t.duration_ms > 0]
356
+ ) # Completed ops
357
+ success_rate = (
358
+ (successful_ops / total_ops * 100) if total_ops > 0 else 100.0
359
+ )
360
+
361
+ return PerformanceSnapshot(
362
+ token_usage=token_metrics,
363
+ active_operations=len(self.active_timings),
364
+ success_rate=success_rate,
365
+ average_response_time_ms=avg_response_time,
366
+ )
367
+
368
+
369
+ class PerformanceMonitor:
370
+ """Main performance monitoring system."""
371
+
372
+ def __init__(
373
+ self,
374
+ status_manager: Optional[AgentStatusManager] = None,
375
+ persona_manager: Optional[PersonaManager] = None,
376
+ storage_path: Optional[Path] = None,
377
+ ):
378
+ """Initialize the performance monitor."""
379
+ self.status_manager = status_manager or get_status_manager()
380
+ self.persona_manager = persona_manager or get_persona_manager()
381
+ self.storage_path = storage_path or Path.home() / ".omnimancer" / "performance"
382
+ self.storage_path.mkdir(parents=True, exist_ok=True)
383
+
384
+ self.metrics_collector = PerformanceMetricsCollector()
385
+ self.alerts: List[PerformanceAlert] = []
386
+ self.suggestions: List[OptimizationSuggestion] = []
387
+ self.alert_thresholds = self._initialize_thresholds()
388
+ self.is_monitoring = False
389
+ self._lock = threading.RLock()
390
+
391
+ # Event listeners
392
+ self._event_listeners: List[Callable] = []
393
+
394
+ logger.info("Performance monitor initialized")
395
+
396
+ def _initialize_thresholds(self) -> Dict[str, Dict[str, float]]:
397
+ """Initialize default alert thresholds."""
398
+ return {
399
+ "token_usage": {
400
+ "high_usage_per_minute": 10000,
401
+ "excessive_usage_per_minute": 50000,
402
+ "cost_threshold_per_hour": 10.0,
403
+ },
404
+ "timing": {
405
+ "slow_response_ms": 5000,
406
+ "very_slow_response_ms": 15000,
407
+ "context_load_slow_ms": 2000,
408
+ },
409
+ "context": {
410
+ "low_efficiency_threshold": 30.0,
411
+ "very_low_efficiency_threshold": 10.0,
412
+ },
413
+ "resources": {"high_memory_mb": 1024, "high_cpu_percent": 80.0},
414
+ }
415
+
416
+ def start_monitoring(self) -> None:
417
+ """Start performance monitoring."""
418
+ if self.is_monitoring:
419
+ logger.warning("Performance monitoring is already active")
420
+ return
421
+
422
+ self.is_monitoring = True
423
+
424
+ # Register with status manager for events
425
+ if self.status_manager:
426
+ self.status_manager.add_event_listener(self._handle_agent_event)
427
+
428
+ logger.info("Performance monitoring started")
429
+
430
+ def stop_monitoring(self) -> None:
431
+ """Stop performance monitoring."""
432
+ if not self.is_monitoring:
433
+ return
434
+
435
+ self.is_monitoring = False
436
+
437
+ # Unregister from status manager
438
+ if self.status_manager:
439
+ self.status_manager.remove_event_listener(self._handle_agent_event)
440
+
441
+ logger.info("Performance monitoring stopped")
442
+
443
+ def record_api_call_metrics(
444
+ self,
445
+ provider_name: str,
446
+ model_name: str,
447
+ input_tokens: int,
448
+ output_tokens: int,
449
+ cached_tokens: int = 0,
450
+ cost_estimate: float = 0.0,
451
+ operation_type: OperationType = OperationType.API_CALL,
452
+ ) -> None:
453
+ """Record API call metrics."""
454
+ metrics = TokenUsageMetrics(
455
+ total_tokens=input_tokens + output_tokens,
456
+ input_tokens=input_tokens,
457
+ output_tokens=output_tokens,
458
+ cached_tokens=cached_tokens,
459
+ provider_name=provider_name,
460
+ model_name=model_name,
461
+ operation_type=operation_type,
462
+ cost_estimate=cost_estimate,
463
+ )
464
+
465
+ self.metrics_collector.record_token_usage(metrics)
466
+ self._check_token_usage_alerts(metrics)
467
+
468
+ def start_operation_tracking(
469
+ self, operation_id: str, operation_type: OperationType
470
+ ) -> str:
471
+ """Start tracking an operation."""
472
+ timing = self.metrics_collector.start_operation_timing(
473
+ operation_id, operation_type
474
+ )
475
+ return timing.operation_id
476
+
477
+ def complete_operation_tracking(
478
+ self, operation_id: str
479
+ ) -> Optional[OperationTiming]:
480
+ """Complete tracking an operation."""
481
+ timing = self.metrics_collector.complete_operation_timing(operation_id)
482
+ if timing:
483
+ self._check_timing_alerts(timing)
484
+ return timing
485
+
486
+ def record_context_usage(
487
+ self,
488
+ context_size_chars: int,
489
+ context_size_tokens: int,
490
+ useful_ratio: float,
491
+ load_time_ms: float,
492
+ context_type: str = "",
493
+ ) -> None:
494
+ """Record context usage metrics."""
495
+ context_metrics = ContextMetrics(
496
+ context_size_chars=context_size_chars,
497
+ context_size_tokens=context_size_tokens,
498
+ useful_context_ratio=useful_ratio,
499
+ context_load_time_ms=load_time_ms,
500
+ context_type=context_type,
501
+ )
502
+
503
+ self.metrics_collector.record_context_metrics(context_metrics)
504
+ self._check_context_alerts(context_metrics)
505
+
506
+ def get_performance_dashboard_data(self) -> Dict[str, Any]:
507
+ """Get data for performance dashboard."""
508
+ snapshot = self.metrics_collector.get_performance_snapshot()
509
+ recent_stats = self.metrics_collector.get_token_usage_stats(timedelta(hours=1))
510
+
511
+ return {
512
+ "current_snapshot": asdict(snapshot),
513
+ "recent_stats": recent_stats,
514
+ "active_alerts": [
515
+ asdict(alert) for alert in self.alerts[-10:]
516
+ ], # Last 10 alerts
517
+ "suggestions": [
518
+ asdict(suggestion) for suggestion in self.suggestions[-5:]
519
+ ], # Last 5 suggestions
520
+ "monitoring_status": ("active" if self.is_monitoring else "inactive"),
521
+ }
522
+
523
+ def _handle_agent_event(self, event: AgentEvent) -> None:
524
+ """Handle agent events for performance monitoring."""
525
+ if not self.is_monitoring:
526
+ return
527
+
528
+ try:
529
+ if event.event_type == EventType.OPERATION_STARTED:
530
+ # Start tracking operation
531
+ if event.operation:
532
+ self.start_operation_tracking(
533
+ event.operation.operation_id,
534
+ event.operation.operation_type,
535
+ )
536
+
537
+ elif event.event_type == EventType.OPERATION_COMPLETED:
538
+ # Complete tracking operation
539
+ if event.operation:
540
+ self.complete_operation_tracking(event.operation.operation_id)
541
+
542
+ elif event.event_type == EventType.OPERATION_FAILED:
543
+ # Record failed operation
544
+ if event.operation:
545
+ self.complete_operation_tracking(event.operation.operation_id)
546
+
547
+ except Exception as e:
548
+ logger.error(f"Error handling agent event: {e}")
549
+
550
+ def _check_token_usage_alerts(self, metrics: TokenUsageMetrics) -> None:
551
+ """Check for token usage alerts."""
552
+ with self._lock:
553
+ thresholds = self.alert_thresholds["token_usage"]
554
+
555
+ # Check recent usage (last minute)
556
+ recent_usage = self.metrics_collector.get_token_usage_stats(
557
+ timedelta(minutes=1)
558
+ )
559
+ tokens_per_minute = recent_usage["total_tokens"]
560
+
561
+ if tokens_per_minute > thresholds["excessive_usage_per_minute"]:
562
+ self._create_alert(
563
+ AlertLevel.CRITICAL,
564
+ "Excessive Token Usage",
565
+ f"Using {tokens_per_minute:,} tokens per minute (threshold: {thresholds['excessive_usage_per_minute']:,})",
566
+ MetricType.TOKEN_USAGE,
567
+ thresholds["excessive_usage_per_minute"],
568
+ tokens_per_minute,
569
+ provider_name=metrics.provider_name,
570
+ suggested_actions=[
571
+ "Consider switching to a more efficient model",
572
+ "Reduce context size",
573
+ "Implement response caching",
574
+ ],
575
+ )
576
+ elif tokens_per_minute > thresholds["high_usage_per_minute"]:
577
+ self._create_alert(
578
+ AlertLevel.WARNING,
579
+ "High Token Usage",
580
+ f"Using {tokens_per_minute:,} tokens per minute (threshold: {thresholds['high_usage_per_minute']:,})",
581
+ MetricType.TOKEN_USAGE,
582
+ thresholds["high_usage_per_minute"],
583
+ tokens_per_minute,
584
+ provider_name=metrics.provider_name,
585
+ suggested_actions=[
586
+ "Monitor usage patterns",
587
+ "Consider optimizing context",
588
+ ],
589
+ )
590
+
591
+ def _check_timing_alerts(self, timing: OperationTiming) -> None:
592
+ """Check for timing-based alerts."""
593
+ with self._lock:
594
+ thresholds = self.alert_thresholds["timing"]
595
+
596
+ if timing.duration_ms > thresholds["very_slow_response_ms"]:
597
+ self._create_alert(
598
+ AlertLevel.WARNING,
599
+ "Very Slow Response",
600
+ f"Operation took {timing.duration_ms:.0f}ms (threshold: {thresholds['very_slow_response_ms']:.0f}ms)",
601
+ MetricType.OPERATION_TIMING,
602
+ thresholds["very_slow_response_ms"],
603
+ timing.duration_ms,
604
+ suggested_actions=[
605
+ "Check network connectivity",
606
+ "Consider switching providers",
607
+ ],
608
+ )
609
+
610
+ def _check_context_alerts(self, context: ContextMetrics) -> None:
611
+ """Check for context efficiency alerts."""
612
+ with self._lock:
613
+ thresholds = self.alert_thresholds["context"]
614
+ efficiency_score = context.efficiency_score
615
+
616
+ if efficiency_score < thresholds["very_low_efficiency_threshold"]:
617
+ self._create_alert(
618
+ AlertLevel.WARNING,
619
+ "Very Low Context Efficiency",
620
+ f"Context efficiency is {efficiency_score:.1f}% (threshold: {thresholds['very_low_efficiency_threshold']:.1f}%)",
621
+ MetricType.CONTEXT_EFFICIENCY,
622
+ thresholds["very_low_efficiency_threshold"],
623
+ efficiency_score,
624
+ suggested_actions=[
625
+ "Reduce context size",
626
+ "Improve context relevance",
627
+ "Consider context compression techniques",
628
+ ],
629
+ )
630
+
631
+ def _create_alert(
632
+ self,
633
+ level: AlertLevel,
634
+ title: str,
635
+ description: str,
636
+ metric_type: MetricType,
637
+ threshold_value: float,
638
+ current_value: float,
639
+ provider_name: Optional[str] = None,
640
+ persona_name: Optional[str] = None,
641
+ suggested_actions: Optional[List[str]] = None,
642
+ ) -> PerformanceAlert:
643
+ """Create a new performance alert."""
644
+ alert = PerformanceAlert(
645
+ alert_id=f"alert_{int(time.time() * 1000)}",
646
+ level=level,
647
+ title=title,
648
+ description=description,
649
+ metric_type=metric_type,
650
+ threshold_value=threshold_value,
651
+ current_value=current_value,
652
+ provider_name=provider_name,
653
+ persona_name=persona_name,
654
+ suggested_actions=suggested_actions or [],
655
+ )
656
+
657
+ self.alerts.append(alert)
658
+ logger.warning(f"Performance alert created: {title}")
659
+
660
+ # Notify listeners
661
+ for listener in self._event_listeners:
662
+ try:
663
+ listener("performance_alert", alert)
664
+ except Exception as e:
665
+ logger.error(f"Error notifying alert listener: {e}")
666
+
667
+ return alert
668
+
669
+ def add_event_listener(self, listener: Callable) -> None:
670
+ """Add an event listener for performance events."""
671
+ self._event_listeners.append(listener)
672
+
673
+ def remove_event_listener(self, listener: Callable) -> None:
674
+ """Remove an event listener."""
675
+ if listener in self._event_listeners:
676
+ self._event_listeners.remove(listener)
677
+
678
+
679
+ # Global performance monitor instance
680
+ _performance_monitor: Optional[PerformanceMonitor] = None
681
+ _monitor_lock = threading.Lock()
682
+
683
+
684
+ def get_performance_monitor() -> PerformanceMonitor:
685
+ """Get the global performance monitor instance."""
686
+ global _performance_monitor
687
+ if _performance_monitor is None:
688
+ with _monitor_lock:
689
+ if _performance_monitor is None:
690
+ _performance_monitor = PerformanceMonitor()
691
+ return _performance_monitor
692
+
693
+
694
+ def set_performance_monitor(monitor: PerformanceMonitor) -> None:
695
+ """Set the global performance monitor instance."""
696
+ global _performance_monitor
697
+ with _monitor_lock:
698
+ _performance_monitor = monitor
699
+
700
+
701
+ def initialize_performance_monitoring(
702
+ auto_start: bool = True,
703
+ ) -> PerformanceMonitor:
704
+ """Initialize the performance monitoring system."""
705
+ monitor = get_performance_monitor()
706
+ if auto_start:
707
+ monitor.start_monitoring()
708
+ return monitor
709
+
710
+
711
+ def shutdown_performance_monitoring() -> None:
712
+ """Shutdown the performance monitoring system."""
713
+ global _performance_monitor
714
+ if _performance_monitor:
715
+ _performance_monitor.stop_monitoring()
716
+ with _monitor_lock:
717
+ _performance_monitor = None