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.
- omnimancer/__init__.py +42 -0
- omnimancer/__main__.py +9 -0
- omnimancer/cli/__init__.py +8 -0
- omnimancer/cli/agent_manager.py +826 -0
- omnimancer/cli/agent_wizard.py +944 -0
- omnimancer/cli/approval_formatter.py +734 -0
- omnimancer/cli/approval_integration.py +734 -0
- omnimancer/cli/approval_prompt.py +1286 -0
- omnimancer/cli/batch_approval_display.py +617 -0
- omnimancer/cli/batch_approval_filters.py +729 -0
- omnimancer/cli/batch_operation_monitor.py +775 -0
- omnimancer/cli/commands/performance_cmd.py +624 -0
- omnimancer/cli/commands.py +757 -0
- omnimancer/cli/completion_handler.py +281 -0
- omnimancer/cli/config_setup_wizard.py +783 -0
- omnimancer/cli/dynamic_commands.py +371 -0
- omnimancer/cli/handlers/__init__.py +18 -0
- omnimancer/cli/handlers/agent_handler.py +865 -0
- omnimancer/cli/handlers/agent_persona_handler.py +1048 -0
- omnimancer/cli/handlers/custom_agent_handler.py +352 -0
- omnimancer/cli/handlers/permissions_handler.py +1609 -0
- omnimancer/cli/interface.py +6695 -0
- omnimancer/cli/performance_dashboard.py +612 -0
- omnimancer/core/__init__.py +34 -0
- omnimancer/core/agent/__init__.py +256 -0
- omnimancer/core/agent/agent_config.py +715 -0
- omnimancer/core/agent/agent_switcher.py +668 -0
- omnimancer/core/agent/approval_context.py +460 -0
- omnimancer/core/agent/approval_dialog.py +902 -0
- omnimancer/core/agent/approval_interface.py +628 -0
- omnimancer/core/agent/approval_manager.py +760 -0
- omnimancer/core/agent/cli_integration.py +470 -0
- omnimancer/core/agent/config.py +845 -0
- omnimancer/core/agent/custom_agent_integration.py +654 -0
- omnimancer/core/agent/diff_renderer.py +706 -0
- omnimancer/core/agent/file_content_display.py +503 -0
- omnimancer/core/agent/file_modification_workflow.py +658 -0
- omnimancer/core/agent/file_system_manager.py +1790 -0
- omnimancer/core/agent/input_handler.py +614 -0
- omnimancer/core/agent/metrics_collector.py +771 -0
- omnimancer/core/agent/optimization_engine.py +729 -0
- omnimancer/core/agent/performance_monitor.py +717 -0
- omnimancer/core/agent/persona.py +870 -0
- omnimancer/core/agent/persona_validator.py +672 -0
- omnimancer/core/agent/program_executor.py +748 -0
- omnimancer/core/agent/proposed_changes_integration.py +663 -0
- omnimancer/core/agent/read_before_write_errors.py +492 -0
- omnimancer/core/agent/read_before_write_ui.py +647 -0
- omnimancer/core/agent/rich_renderer.py +763 -0
- omnimancer/core/agent/status_config.py +238 -0
- omnimancer/core/agent/status_core.py +446 -0
- omnimancer/core/agent/status_integrated_engine.py +540 -0
- omnimancer/core/agent/status_manager.py +878 -0
- omnimancer/core/agent/status_ui_components.py +535 -0
- omnimancer/core/agent/template_applicator.py +548 -0
- omnimancer/core/agent/terminal_status_listener.py +394 -0
- omnimancer/core/agent/token_tracker.py +679 -0
- omnimancer/core/agent/types.py +60 -0
- omnimancer/core/agent/web_client.py +745 -0
- omnimancer/core/agent/workflow_orchestrator.py +826 -0
- omnimancer/core/agent_engine.py +1625 -0
- omnimancer/core/agent_mode_manager.py +706 -0
- omnimancer/core/agent_progress_ui.py +545 -0
- omnimancer/core/chat_manager.py +141 -0
- omnimancer/core/config_manager.py +2199 -0
- omnimancer/core/config_migration.py +772 -0
- omnimancer/core/config_provider.py +1974 -0
- omnimancer/core/config_security_validator.py +393 -0
- omnimancer/core/config_validator.py +768 -0
- omnimancer/core/conversation_manager.py +352 -0
- omnimancer/core/engine.py +787 -0
- omnimancer/core/enhanced_input.py +523 -0
- omnimancer/core/env_loader.py +83 -0
- omnimancer/core/fallback_manager.py +568 -0
- omnimancer/core/health_monitor.py +462 -0
- omnimancer/core/history_manager.py +462 -0
- omnimancer/core/mcp_integration_layer.py +878 -0
- omnimancer/core/models.py +1889 -0
- omnimancer/core/provider_capabilities.py +403 -0
- omnimancer/core/provider_initializer.py +450 -0
- omnimancer/core/provider_registry.py +432 -0
- omnimancer/core/security/__init__.py +16 -0
- omnimancer/core/security/approval_workflow.py +406 -0
- omnimancer/core/security/audit_logger.py +511 -0
- omnimancer/core/security/permission_controller.py +511 -0
- omnimancer/core/security/sandbox_manager.py +379 -0
- omnimancer/core/security/security_manager.py +530 -0
- omnimancer/core/setup_wizard.py +345 -0
- omnimancer/core/setup_wizard_core.py +449 -0
- omnimancer/core/setup_wizard_guide_generator.py +376 -0
- omnimancer/core/setup_wizard_provider_setup.py +454 -0
- omnimancer/core/setup_wizard_ui.py +317 -0
- omnimancer/core/setup_wizard_validation.py +371 -0
- omnimancer/core/signal_handler.py +174 -0
- omnimancer/mcp/__init__.py +11 -0
- omnimancer/mcp/client.py +725 -0
- omnimancer/mcp/manager.py +649 -0
- omnimancer/providers/__init__.py +24 -0
- omnimancer/providers/azure.py +608 -0
- omnimancer/providers/base.py +262 -0
- omnimancer/providers/bedrock.py +711 -0
- omnimancer/providers/claude.py +426 -0
- omnimancer/providers/claude_code.py +406 -0
- omnimancer/providers/cohere.py +412 -0
- omnimancer/providers/factory.py +988 -0
- omnimancer/providers/gemini.py +708 -0
- omnimancer/providers/mistral.py +554 -0
- omnimancer/providers/ollama.py +510 -0
- omnimancer/providers/openai.py +362 -0
- omnimancer/providers/openrouter.py +709 -0
- omnimancer/providers/perplexity.py +476 -0
- omnimancer/providers/vertex.py +617 -0
- omnimancer/providers/xai.py +484 -0
- omnimancer/ui/__init__.py +6 -0
- omnimancer/ui/cancellation_handler.py +235 -0
- omnimancer/ui/progress_indicator.py +318 -0
- omnimancer/utils/__init__.py +22 -0
- omnimancer/utils/error_handler.py +589 -0
- omnimancer/utils/errors.py +667 -0
- omnimancer/utils/retry.py +238 -0
- omnimancer_cli-0.1.0.dist-info/METADATA +413 -0
- omnimancer_cli-0.1.0.dist-info/RECORD +126 -0
- omnimancer_cli-0.1.0.dist-info/WHEEL +5 -0
- omnimancer_cli-0.1.0.dist-info/entry_points.txt +4 -0
- omnimancer_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
- omnimancer_cli-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,729 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Optimization Engine and Alert System for Omnimancer Agent Performance.
|
|
3
|
+
|
|
4
|
+
This module provides intelligent optimization suggestions and proactive alerting
|
|
5
|
+
based on performance patterns, resource usage, and cost analysis.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
import statistics
|
|
10
|
+
import threading
|
|
11
|
+
import time
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
from datetime import datetime, timedelta
|
|
14
|
+
from enum import Enum
|
|
15
|
+
from typing import Any, Callable, Dict, List, Optional
|
|
16
|
+
|
|
17
|
+
from .metrics_collector import (
|
|
18
|
+
AggregationMethod,
|
|
19
|
+
PerformanceMetricsCollector,
|
|
20
|
+
get_metrics_collector,
|
|
21
|
+
)
|
|
22
|
+
from .performance_monitor import (
|
|
23
|
+
AlertLevel,
|
|
24
|
+
OptimizationSuggestion,
|
|
25
|
+
OptimizationType,
|
|
26
|
+
PerformanceAlert,
|
|
27
|
+
PerformanceMonitor,
|
|
28
|
+
get_performance_monitor,
|
|
29
|
+
)
|
|
30
|
+
from .persona import get_persona_manager
|
|
31
|
+
from .token_tracker import TokenUsageTracker, get_token_tracker
|
|
32
|
+
|
|
33
|
+
logger = logging.getLogger(__name__)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class OptimizationCategory(Enum):
|
|
37
|
+
"""Categories of optimizations."""
|
|
38
|
+
|
|
39
|
+
COST_REDUCTION = "cost_reduction"
|
|
40
|
+
PERFORMANCE_IMPROVEMENT = "performance_improvement"
|
|
41
|
+
RESOURCE_EFFICIENCY = "resource_efficiency"
|
|
42
|
+
RELIABILITY_ENHANCEMENT = "reliability_enhancement"
|
|
43
|
+
USER_EXPERIENCE = "user_experience"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class AlertTrigger(Enum):
|
|
47
|
+
"""Types of alert triggers."""
|
|
48
|
+
|
|
49
|
+
THRESHOLD_EXCEEDED = "threshold_exceeded"
|
|
50
|
+
TREND_DETECTION = "trend_detection"
|
|
51
|
+
ANOMALY_DETECTION = "anomaly_detection"
|
|
52
|
+
PATTERN_RECOGNITION = "pattern_recognition"
|
|
53
|
+
COST_SPIKE = "cost_spike"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@dataclass
|
|
57
|
+
class OptimizationRule:
|
|
58
|
+
"""Rule for generating optimization suggestions."""
|
|
59
|
+
|
|
60
|
+
rule_id: str
|
|
61
|
+
name: str
|
|
62
|
+
category: OptimizationCategory
|
|
63
|
+
description: str
|
|
64
|
+
condition_check: Callable[[Dict[str, Any]], bool]
|
|
65
|
+
suggestion_generator: Callable[[Dict[str, Any]], OptimizationSuggestion]
|
|
66
|
+
priority: int = 50 # 0-100, higher is more important
|
|
67
|
+
enabled: bool = True
|
|
68
|
+
|
|
69
|
+
def evaluate(self, context: Dict[str, Any]) -> Optional[OptimizationSuggestion]:
|
|
70
|
+
"""Evaluate rule and generate suggestion if conditions are met."""
|
|
71
|
+
if not self.enabled:
|
|
72
|
+
return None
|
|
73
|
+
|
|
74
|
+
try:
|
|
75
|
+
if self.condition_check(context):
|
|
76
|
+
return self.suggestion_generator(context)
|
|
77
|
+
except Exception as e:
|
|
78
|
+
logger.error(f"Error evaluating optimization rule {self.rule_id}: {e}")
|
|
79
|
+
|
|
80
|
+
return None
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@dataclass
|
|
84
|
+
class AlertRule:
|
|
85
|
+
"""Rule for generating performance alerts."""
|
|
86
|
+
|
|
87
|
+
rule_id: str
|
|
88
|
+
name: str
|
|
89
|
+
metric_name: str
|
|
90
|
+
trigger_type: AlertTrigger
|
|
91
|
+
threshold_value: float
|
|
92
|
+
comparison_operator: str # "gt", "lt", "eq", "gte", "lte"
|
|
93
|
+
alert_level: AlertLevel
|
|
94
|
+
time_window: timedelta
|
|
95
|
+
min_samples: int = 1
|
|
96
|
+
enabled: bool = True
|
|
97
|
+
cooldown_period: timedelta = timedelta(minutes=15)
|
|
98
|
+
last_triggered: Optional[datetime] = None
|
|
99
|
+
|
|
100
|
+
def check_condition(self, current_value: float, context: Dict[str, Any]) -> bool:
|
|
101
|
+
"""Check if alert condition is met."""
|
|
102
|
+
if not self.enabled:
|
|
103
|
+
return False
|
|
104
|
+
|
|
105
|
+
# Check cooldown period
|
|
106
|
+
if (
|
|
107
|
+
self.last_triggered
|
|
108
|
+
and datetime.now() - self.last_triggered < self.cooldown_period
|
|
109
|
+
):
|
|
110
|
+
return False
|
|
111
|
+
|
|
112
|
+
# Evaluate condition
|
|
113
|
+
if self.comparison_operator == "gt":
|
|
114
|
+
return current_value > self.threshold_value
|
|
115
|
+
elif self.comparison_operator == "lt":
|
|
116
|
+
return current_value < self.threshold_value
|
|
117
|
+
elif self.comparison_operator == "eq":
|
|
118
|
+
return abs(current_value - self.threshold_value) < 0.001
|
|
119
|
+
elif self.comparison_operator == "gte":
|
|
120
|
+
return current_value >= self.threshold_value
|
|
121
|
+
elif self.comparison_operator == "lte":
|
|
122
|
+
return current_value <= self.threshold_value
|
|
123
|
+
|
|
124
|
+
return False
|
|
125
|
+
|
|
126
|
+
def trigger_alert(
|
|
127
|
+
self, current_value: float, context: Dict[str, Any]
|
|
128
|
+
) -> PerformanceAlert:
|
|
129
|
+
"""Generate alert when rule is triggered."""
|
|
130
|
+
self.last_triggered = datetime.now()
|
|
131
|
+
|
|
132
|
+
return PerformanceAlert(
|
|
133
|
+
alert_id=f"alert_{self.rule_id}_{int(time.time() * 1000)}",
|
|
134
|
+
level=self.alert_level,
|
|
135
|
+
title=self.name,
|
|
136
|
+
description=f"Metric '{self.metric_name}' {self.comparison_operator} {self.threshold_value}",
|
|
137
|
+
metric_type=context.get("metric_type", "unknown"),
|
|
138
|
+
threshold_value=self.threshold_value,
|
|
139
|
+
current_value=current_value,
|
|
140
|
+
provider_name=context.get("provider_name"),
|
|
141
|
+
persona_name=context.get("persona_name"),
|
|
142
|
+
suggested_actions=self._generate_suggested_actions(current_value, context),
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
def _generate_suggested_actions(
|
|
146
|
+
self, current_value: float, context: Dict[str, Any]
|
|
147
|
+
) -> List[str]:
|
|
148
|
+
"""Generate suggested actions based on the alert."""
|
|
149
|
+
actions = []
|
|
150
|
+
|
|
151
|
+
if "token" in self.metric_name.lower():
|
|
152
|
+
actions.extend(
|
|
153
|
+
[
|
|
154
|
+
"Review recent token usage patterns",
|
|
155
|
+
"Consider switching to a more efficient model",
|
|
156
|
+
"Implement response caching for repeated queries",
|
|
157
|
+
]
|
|
158
|
+
)
|
|
159
|
+
elif "response_time" in self.metric_name.lower():
|
|
160
|
+
actions.extend(
|
|
161
|
+
[
|
|
162
|
+
"Check network connectivity",
|
|
163
|
+
"Consider switching to a faster provider",
|
|
164
|
+
"Review context size and complexity",
|
|
165
|
+
]
|
|
166
|
+
)
|
|
167
|
+
elif "cpu" in self.metric_name.lower() or "memory" in self.metric_name.lower():
|
|
168
|
+
actions.extend(
|
|
169
|
+
[
|
|
170
|
+
"Monitor system resource usage",
|
|
171
|
+
"Consider reducing concurrent operations",
|
|
172
|
+
"Review background processes",
|
|
173
|
+
]
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
return actions
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
class TrendAnalyzer:
|
|
180
|
+
"""Analyzes trends in performance metrics."""
|
|
181
|
+
|
|
182
|
+
def __init__(self, min_data_points: int = 10):
|
|
183
|
+
"""Initialize trend analyzer."""
|
|
184
|
+
self.min_data_points = min_data_points
|
|
185
|
+
|
|
186
|
+
def detect_trend(
|
|
187
|
+
self, values: List[float], timestamps: List[datetime]
|
|
188
|
+
) -> Dict[str, Any]:
|
|
189
|
+
"""Detect trend in time series data."""
|
|
190
|
+
if len(values) < self.min_data_points:
|
|
191
|
+
return {"trend": "insufficient_data", "confidence": 0.0}
|
|
192
|
+
|
|
193
|
+
# Simple linear regression to detect trend
|
|
194
|
+
try:
|
|
195
|
+
# Convert timestamps to numeric values (seconds since first timestamp)
|
|
196
|
+
time_numeric = [(t - timestamps[0]).total_seconds() for t in timestamps]
|
|
197
|
+
|
|
198
|
+
# Calculate trend using linear regression
|
|
199
|
+
n = len(values)
|
|
200
|
+
sum_x = sum(time_numeric)
|
|
201
|
+
sum_y = sum(values)
|
|
202
|
+
sum_xy = sum(x * y for x, y in zip(time_numeric, values))
|
|
203
|
+
sum_x_squared = sum(x * x for x in time_numeric)
|
|
204
|
+
|
|
205
|
+
# Calculate slope (trend direction)
|
|
206
|
+
slope = (n * sum_xy - sum_x * sum_y) / (n * sum_x_squared - sum_x * sum_x)
|
|
207
|
+
|
|
208
|
+
# Calculate correlation coefficient for confidence
|
|
209
|
+
mean_x = sum_x / n
|
|
210
|
+
mean_y = sum_y / n
|
|
211
|
+
|
|
212
|
+
numerator = sum(
|
|
213
|
+
(x - mean_x) * (y - mean_y) for x, y in zip(time_numeric, values)
|
|
214
|
+
)
|
|
215
|
+
denom_x = sum((x - mean_x) ** 2 for x in time_numeric)
|
|
216
|
+
denom_y = sum((y - mean_y) ** 2 for y in values)
|
|
217
|
+
|
|
218
|
+
correlation = (
|
|
219
|
+
numerator / (denom_x * denom_y) ** 0.5 if denom_x * denom_y > 0 else 0
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
# Determine trend direction
|
|
223
|
+
if abs(slope) < 0.001: # Threshold for "no trend"
|
|
224
|
+
trend = "stable"
|
|
225
|
+
elif slope > 0:
|
|
226
|
+
trend = "increasing"
|
|
227
|
+
else:
|
|
228
|
+
trend = "decreasing"
|
|
229
|
+
|
|
230
|
+
return {
|
|
231
|
+
"trend": trend,
|
|
232
|
+
"slope": slope,
|
|
233
|
+
"confidence": abs(correlation),
|
|
234
|
+
"correlation": correlation,
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
except (ValueError, ZeroDivisionError):
|
|
238
|
+
return {"trend": "unknown", "confidence": 0.0}
|
|
239
|
+
|
|
240
|
+
def detect_anomalies(
|
|
241
|
+
self, values: List[float], sensitivity: float = 2.0
|
|
242
|
+
) -> List[int]:
|
|
243
|
+
"""Detect anomalies using statistical methods."""
|
|
244
|
+
if len(values) < 3:
|
|
245
|
+
return []
|
|
246
|
+
|
|
247
|
+
# Use simple statistical outlier detection
|
|
248
|
+
mean_val = statistics.mean(values)
|
|
249
|
+
std_dev = statistics.stdev(values) if len(values) > 1 else 0
|
|
250
|
+
|
|
251
|
+
if std_dev == 0:
|
|
252
|
+
return []
|
|
253
|
+
|
|
254
|
+
anomalies = []
|
|
255
|
+
for i, value in enumerate(values):
|
|
256
|
+
z_score = abs(value - mean_val) / std_dev
|
|
257
|
+
if z_score > sensitivity:
|
|
258
|
+
anomalies.append(i)
|
|
259
|
+
|
|
260
|
+
return anomalies
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
class OptimizationEngine:
|
|
264
|
+
"""Main optimization engine with rule-based suggestions and alerting."""
|
|
265
|
+
|
|
266
|
+
def __init__(
|
|
267
|
+
self,
|
|
268
|
+
performance_monitor: Optional[PerformanceMonitor] = None,
|
|
269
|
+
token_tracker: Optional[TokenUsageTracker] = None,
|
|
270
|
+
metrics_collector: Optional[PerformanceMetricsCollector] = None,
|
|
271
|
+
):
|
|
272
|
+
"""Initialize optimization engine."""
|
|
273
|
+
self.performance_monitor = performance_monitor or get_performance_monitor()
|
|
274
|
+
self.token_tracker = token_tracker or get_token_tracker()
|
|
275
|
+
self.metrics_collector = metrics_collector or get_metrics_collector()
|
|
276
|
+
self.persona_manager = get_persona_manager()
|
|
277
|
+
|
|
278
|
+
# Rule engines
|
|
279
|
+
self.optimization_rules: Dict[str, OptimizationRule] = {}
|
|
280
|
+
self.alert_rules: Dict[str, AlertRule] = {}
|
|
281
|
+
|
|
282
|
+
# Analysis components
|
|
283
|
+
self.trend_analyzer = TrendAnalyzer()
|
|
284
|
+
|
|
285
|
+
# State
|
|
286
|
+
self.is_running = False
|
|
287
|
+
self.analysis_thread: Optional[threading.Thread] = None
|
|
288
|
+
self.analysis_interval = 60.0 # seconds
|
|
289
|
+
|
|
290
|
+
# Event handlers
|
|
291
|
+
self.optimization_handlers: List[Callable] = []
|
|
292
|
+
self.alert_handlers: List[Callable] = []
|
|
293
|
+
|
|
294
|
+
self._initialize_default_rules()
|
|
295
|
+
logger.info("Optimization engine initialized")
|
|
296
|
+
|
|
297
|
+
def _initialize_default_rules(self) -> None:
|
|
298
|
+
"""Initialize default optimization and alert rules."""
|
|
299
|
+
# Cost optimization rules
|
|
300
|
+
self.add_optimization_rule(
|
|
301
|
+
OptimizationRule(
|
|
302
|
+
rule_id="high_cost_provider",
|
|
303
|
+
name="High Cost Provider Usage",
|
|
304
|
+
category=OptimizationCategory.COST_REDUCTION,
|
|
305
|
+
description="Detects heavy usage of expensive providers",
|
|
306
|
+
condition_check=self._check_high_cost_provider,
|
|
307
|
+
suggestion_generator=self._suggest_cost_optimization,
|
|
308
|
+
priority=80,
|
|
309
|
+
)
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
self.add_optimization_rule(
|
|
313
|
+
OptimizationRule(
|
|
314
|
+
rule_id="inefficient_token_usage",
|
|
315
|
+
name="Inefficient Token Usage",
|
|
316
|
+
category=OptimizationCategory.COST_REDUCTION,
|
|
317
|
+
description="Detects patterns of inefficient token usage",
|
|
318
|
+
condition_check=self._check_inefficient_tokens,
|
|
319
|
+
suggestion_generator=self._suggest_token_optimization,
|
|
320
|
+
priority=70,
|
|
321
|
+
)
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
# Performance optimization rules
|
|
325
|
+
self.add_optimization_rule(
|
|
326
|
+
OptimizationRule(
|
|
327
|
+
rule_id="slow_response_times",
|
|
328
|
+
name="Slow Response Times",
|
|
329
|
+
category=OptimizationCategory.PERFORMANCE_IMPROVEMENT,
|
|
330
|
+
description="Detects consistently slow response times",
|
|
331
|
+
condition_check=self._check_slow_responses,
|
|
332
|
+
suggestion_generator=self._suggest_performance_improvement,
|
|
333
|
+
priority=75,
|
|
334
|
+
)
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
# Alert rules
|
|
338
|
+
self.add_alert_rule(
|
|
339
|
+
AlertRule(
|
|
340
|
+
rule_id="excessive_token_usage",
|
|
341
|
+
name="Excessive Token Usage",
|
|
342
|
+
metric_name="agent.tokens_per_minute",
|
|
343
|
+
trigger_type=AlertTrigger.THRESHOLD_EXCEEDED,
|
|
344
|
+
threshold_value=5000,
|
|
345
|
+
comparison_operator="gt",
|
|
346
|
+
alert_level=AlertLevel.WARNING,
|
|
347
|
+
time_window=timedelta(minutes=5),
|
|
348
|
+
)
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
self.add_alert_rule(
|
|
352
|
+
AlertRule(
|
|
353
|
+
rule_id="high_cost_rate",
|
|
354
|
+
name="High Cost Rate",
|
|
355
|
+
metric_name="agent.cost_per_minute",
|
|
356
|
+
trigger_type=AlertTrigger.THRESHOLD_EXCEEDED,
|
|
357
|
+
threshold_value=1.0,
|
|
358
|
+
comparison_operator="gt",
|
|
359
|
+
alert_level=AlertLevel.CRITICAL,
|
|
360
|
+
time_window=timedelta(minutes=5),
|
|
361
|
+
)
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
self.add_alert_rule(
|
|
365
|
+
AlertRule(
|
|
366
|
+
rule_id="slow_response_alert",
|
|
367
|
+
name="Slow Response Times",
|
|
368
|
+
metric_name="operation.duration_ms",
|
|
369
|
+
trigger_type=AlertTrigger.THRESHOLD_EXCEEDED,
|
|
370
|
+
threshold_value=10000,
|
|
371
|
+
comparison_operator="gt",
|
|
372
|
+
alert_level=AlertLevel.WARNING,
|
|
373
|
+
time_window=timedelta(minutes=10),
|
|
374
|
+
)
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
def add_optimization_rule(self, rule: OptimizationRule) -> None:
|
|
378
|
+
"""Add optimization rule."""
|
|
379
|
+
self.optimization_rules[rule.rule_id] = rule
|
|
380
|
+
logger.debug(f"Added optimization rule: {rule.name}")
|
|
381
|
+
|
|
382
|
+
def add_alert_rule(self, rule: AlertRule) -> None:
|
|
383
|
+
"""Add alert rule."""
|
|
384
|
+
self.alert_rules[rule.rule_id] = rule
|
|
385
|
+
logger.debug(f"Added alert rule: {rule.name}")
|
|
386
|
+
|
|
387
|
+
def start_analysis(self) -> None:
|
|
388
|
+
"""Start continuous optimization analysis."""
|
|
389
|
+
if self.is_running:
|
|
390
|
+
logger.warning("Optimization engine is already running")
|
|
391
|
+
return
|
|
392
|
+
|
|
393
|
+
self.is_running = True
|
|
394
|
+
self.analysis_thread = threading.Thread(
|
|
395
|
+
target=self._analysis_loop,
|
|
396
|
+
name="OptimizationAnalysis",
|
|
397
|
+
daemon=True,
|
|
398
|
+
)
|
|
399
|
+
self.analysis_thread.start()
|
|
400
|
+
logger.info("Optimization analysis started")
|
|
401
|
+
|
|
402
|
+
def stop_analysis(self) -> None:
|
|
403
|
+
"""Stop continuous optimization analysis."""
|
|
404
|
+
if not self.is_running:
|
|
405
|
+
return
|
|
406
|
+
|
|
407
|
+
self.is_running = False
|
|
408
|
+
if self.analysis_thread and self.analysis_thread.is_alive():
|
|
409
|
+
self.analysis_thread.join(timeout=5.0)
|
|
410
|
+
logger.info("Optimization analysis stopped")
|
|
411
|
+
|
|
412
|
+
def _analysis_loop(self) -> None:
|
|
413
|
+
"""Main analysis loop."""
|
|
414
|
+
while self.is_running:
|
|
415
|
+
try:
|
|
416
|
+
self._run_analysis_cycle()
|
|
417
|
+
time.sleep(self.analysis_interval)
|
|
418
|
+
except Exception as e:
|
|
419
|
+
logger.error(f"Error in optimization analysis loop: {e}")
|
|
420
|
+
time.sleep(self.analysis_interval)
|
|
421
|
+
|
|
422
|
+
def _run_analysis_cycle(self) -> None:
|
|
423
|
+
"""Run a single analysis cycle."""
|
|
424
|
+
# Gather context data
|
|
425
|
+
context = self._gather_analysis_context()
|
|
426
|
+
|
|
427
|
+
# Check optimization rules
|
|
428
|
+
suggestions = self._evaluate_optimization_rules(context)
|
|
429
|
+
|
|
430
|
+
# Check alert rules
|
|
431
|
+
alerts = self._evaluate_alert_rules(context)
|
|
432
|
+
|
|
433
|
+
# Process suggestions
|
|
434
|
+
for suggestion in suggestions:
|
|
435
|
+
self._handle_optimization_suggestion(suggestion)
|
|
436
|
+
|
|
437
|
+
# Process alerts
|
|
438
|
+
for alert in alerts:
|
|
439
|
+
self._handle_performance_alert(alert)
|
|
440
|
+
|
|
441
|
+
def _gather_analysis_context(self) -> Dict[str, Any]:
|
|
442
|
+
"""Gather context data for analysis."""
|
|
443
|
+
context = {}
|
|
444
|
+
|
|
445
|
+
try:
|
|
446
|
+
# Performance dashboard data
|
|
447
|
+
if self.performance_monitor:
|
|
448
|
+
context["dashboard_data"] = (
|
|
449
|
+
self.performance_monitor.get_performance_dashboard_data()
|
|
450
|
+
)
|
|
451
|
+
|
|
452
|
+
# Token usage data
|
|
453
|
+
if self.token_tracker:
|
|
454
|
+
context["token_summary_1h"] = self.token_tracker.get_usage_summary(
|
|
455
|
+
timedelta(hours=1)
|
|
456
|
+
)
|
|
457
|
+
context["token_summary_24h"] = self.token_tracker.get_usage_summary(
|
|
458
|
+
timedelta(hours=24)
|
|
459
|
+
)
|
|
460
|
+
context["usage_patterns"] = self.token_tracker.analyze_usage_patterns(7)
|
|
461
|
+
|
|
462
|
+
# Metrics data
|
|
463
|
+
if self.metrics_collector:
|
|
464
|
+
context["metrics_data"] = self.metrics_collector.get_dashboard_metrics()
|
|
465
|
+
|
|
466
|
+
# Current timestamp
|
|
467
|
+
context["analysis_time"] = datetime.now()
|
|
468
|
+
|
|
469
|
+
except Exception as e:
|
|
470
|
+
logger.error(f"Error gathering analysis context: {e}")
|
|
471
|
+
|
|
472
|
+
return context
|
|
473
|
+
|
|
474
|
+
def _evaluate_optimization_rules(
|
|
475
|
+
self, context: Dict[str, Any]
|
|
476
|
+
) -> List[OptimizationSuggestion]:
|
|
477
|
+
"""Evaluate all optimization rules."""
|
|
478
|
+
suggestions = []
|
|
479
|
+
|
|
480
|
+
for rule in self.optimization_rules.values():
|
|
481
|
+
try:
|
|
482
|
+
suggestion = rule.evaluate(context)
|
|
483
|
+
if suggestion:
|
|
484
|
+
suggestions.append(suggestion)
|
|
485
|
+
except Exception as e:
|
|
486
|
+
logger.error(f"Error evaluating optimization rule {rule.rule_id}: {e}")
|
|
487
|
+
|
|
488
|
+
return suggestions
|
|
489
|
+
|
|
490
|
+
def _evaluate_alert_rules(self, context: Dict[str, Any]) -> List[PerformanceAlert]:
|
|
491
|
+
"""Evaluate all alert rules."""
|
|
492
|
+
alerts = []
|
|
493
|
+
|
|
494
|
+
for rule in self.alert_rules.values():
|
|
495
|
+
try:
|
|
496
|
+
# Get current metric value
|
|
497
|
+
metric_summary = self.metrics_collector.get_metric_summary(
|
|
498
|
+
rule.metric_name,
|
|
499
|
+
rule.time_window,
|
|
500
|
+
AggregationMethod.AVERAGE,
|
|
501
|
+
)
|
|
502
|
+
|
|
503
|
+
if metric_summary and rule.check_condition(
|
|
504
|
+
metric_summary.value, context
|
|
505
|
+
):
|
|
506
|
+
alert = rule.trigger_alert(metric_summary.value, context)
|
|
507
|
+
alerts.append(alert)
|
|
508
|
+
|
|
509
|
+
except Exception as e:
|
|
510
|
+
logger.error(f"Error evaluating alert rule {rule.rule_id}: {e}")
|
|
511
|
+
|
|
512
|
+
return alerts
|
|
513
|
+
|
|
514
|
+
def _handle_optimization_suggestion(
|
|
515
|
+
self, suggestion: OptimizationSuggestion
|
|
516
|
+
) -> None:
|
|
517
|
+
"""Handle optimization suggestion."""
|
|
518
|
+
# Add to performance monitor
|
|
519
|
+
if self.performance_monitor:
|
|
520
|
+
self.performance_monitor.suggestions.append(suggestion)
|
|
521
|
+
|
|
522
|
+
# Notify handlers
|
|
523
|
+
for handler in self.optimization_handlers:
|
|
524
|
+
try:
|
|
525
|
+
handler(suggestion)
|
|
526
|
+
except Exception as e:
|
|
527
|
+
logger.error(f"Error in optimization handler: {e}")
|
|
528
|
+
|
|
529
|
+
logger.info(f"Generated optimization suggestion: {suggestion.title}")
|
|
530
|
+
|
|
531
|
+
def _handle_performance_alert(self, alert: PerformanceAlert) -> None:
|
|
532
|
+
"""Handle performance alert."""
|
|
533
|
+
# Add to performance monitor
|
|
534
|
+
if self.performance_monitor:
|
|
535
|
+
self.performance_monitor.alerts.append(alert)
|
|
536
|
+
|
|
537
|
+
# Notify handlers
|
|
538
|
+
for handler in self.alert_handlers:
|
|
539
|
+
try:
|
|
540
|
+
handler(alert)
|
|
541
|
+
except Exception as e:
|
|
542
|
+
logger.error(f"Error in alert handler: {e}")
|
|
543
|
+
|
|
544
|
+
logger.warning(f"Generated performance alert: {alert.title}")
|
|
545
|
+
|
|
546
|
+
# Optimization rule condition checkers
|
|
547
|
+
def _check_high_cost_provider(self, context: Dict[str, Any]) -> bool:
|
|
548
|
+
"""Check for high cost provider usage."""
|
|
549
|
+
token_summary = context.get("token_summary_24h", {})
|
|
550
|
+
provider_breakdown = token_summary.get("provider_breakdown", {})
|
|
551
|
+
|
|
552
|
+
for provider, stats in provider_breakdown.items():
|
|
553
|
+
if stats.get("cost", 0) > 5.0: # More than $5 in 24 hours
|
|
554
|
+
return True
|
|
555
|
+
|
|
556
|
+
return False
|
|
557
|
+
|
|
558
|
+
def _check_inefficient_tokens(self, context: Dict[str, Any]) -> bool:
|
|
559
|
+
"""Check for inefficient token usage patterns."""
|
|
560
|
+
token_summary = context.get("token_summary_1h", {})
|
|
561
|
+
avg_tokens_per_request = token_summary.get("average_tokens_per_request", 0)
|
|
562
|
+
|
|
563
|
+
# Alert if average request uses more than 2000 tokens
|
|
564
|
+
return avg_tokens_per_request > 2000
|
|
565
|
+
|
|
566
|
+
def _check_slow_responses(self, context: Dict[str, Any]) -> bool:
|
|
567
|
+
"""Check for slow response times."""
|
|
568
|
+
metrics_data = context.get("metrics_data", {})
|
|
569
|
+
performance = metrics_data.get("performance", {})
|
|
570
|
+
avg_response_time = performance.get("avg_response_time_ms", 0)
|
|
571
|
+
|
|
572
|
+
# Alert if average response time is over 5 seconds
|
|
573
|
+
return avg_response_time > 5000
|
|
574
|
+
|
|
575
|
+
# Optimization suggestion generators
|
|
576
|
+
def _suggest_cost_optimization(
|
|
577
|
+
self, context: Dict[str, Any]
|
|
578
|
+
) -> OptimizationSuggestion:
|
|
579
|
+
"""Generate cost optimization suggestion."""
|
|
580
|
+
token_summary = context.get("token_summary_24h", {})
|
|
581
|
+
total_cost = token_summary.get("total_cost", 0)
|
|
582
|
+
|
|
583
|
+
return OptimizationSuggestion(
|
|
584
|
+
suggestion_id=f"cost_opt_{int(time.time())}",
|
|
585
|
+
optimization_type=OptimizationType.COST_OPTIMIZATION,
|
|
586
|
+
title="Reduce Provider Costs",
|
|
587
|
+
description=f"High daily cost of ${total_cost:.2f} detected",
|
|
588
|
+
potential_improvement="30-50% cost reduction",
|
|
589
|
+
confidence=0.8,
|
|
590
|
+
implementation_difficulty="medium",
|
|
591
|
+
estimated_impact={"cost_savings": total_cost * 0.4},
|
|
592
|
+
required_actions=[
|
|
593
|
+
"Switch to more cost-effective models for routine tasks",
|
|
594
|
+
"Implement response caching",
|
|
595
|
+
"Optimize context sizes",
|
|
596
|
+
],
|
|
597
|
+
)
|
|
598
|
+
|
|
599
|
+
def _suggest_token_optimization(
|
|
600
|
+
self, context: Dict[str, Any]
|
|
601
|
+
) -> OptimizationSuggestion:
|
|
602
|
+
"""Generate token usage optimization suggestion."""
|
|
603
|
+
token_summary = context.get("token_summary_1h", {})
|
|
604
|
+
avg_tokens = token_summary.get("average_tokens_per_request", 0)
|
|
605
|
+
|
|
606
|
+
return OptimizationSuggestion(
|
|
607
|
+
suggestion_id=f"token_opt_{int(time.time())}",
|
|
608
|
+
optimization_type=OptimizationType.CONTEXT_REDUCTION,
|
|
609
|
+
title="Optimize Token Usage",
|
|
610
|
+
description=f"High average of {avg_tokens:.0f} tokens per request",
|
|
611
|
+
potential_improvement="20-40% token reduction",
|
|
612
|
+
confidence=0.75,
|
|
613
|
+
implementation_difficulty="easy",
|
|
614
|
+
estimated_impact={"token_savings": avg_tokens * 0.3},
|
|
615
|
+
required_actions=[
|
|
616
|
+
"Review and reduce context sizes",
|
|
617
|
+
"Implement context compression",
|
|
618
|
+
"Use more efficient prompting strategies",
|
|
619
|
+
],
|
|
620
|
+
)
|
|
621
|
+
|
|
622
|
+
def _suggest_performance_improvement(
|
|
623
|
+
self, context: Dict[str, Any]
|
|
624
|
+
) -> OptimizationSuggestion:
|
|
625
|
+
"""Generate performance improvement suggestion."""
|
|
626
|
+
metrics_data = context.get("metrics_data", {})
|
|
627
|
+
performance = metrics_data.get("performance", {})
|
|
628
|
+
response_time = performance.get("avg_response_time_ms", 0)
|
|
629
|
+
|
|
630
|
+
return OptimizationSuggestion(
|
|
631
|
+
suggestion_id=f"perf_opt_{int(time.time())}",
|
|
632
|
+
optimization_type=OptimizationType.PROVIDER_SWITCHING,
|
|
633
|
+
title="Improve Response Times",
|
|
634
|
+
description=f"Slow average response time of {response_time:.0f}ms",
|
|
635
|
+
potential_improvement="40-60% faster responses",
|
|
636
|
+
confidence=0.7,
|
|
637
|
+
implementation_difficulty="medium",
|
|
638
|
+
estimated_impact={"response_time_improvement": response_time * 0.5},
|
|
639
|
+
required_actions=[
|
|
640
|
+
"Switch to faster providers",
|
|
641
|
+
"Optimize network connectivity",
|
|
642
|
+
"Reduce context complexity",
|
|
643
|
+
],
|
|
644
|
+
)
|
|
645
|
+
|
|
646
|
+
def add_optimization_handler(
|
|
647
|
+
self, handler: Callable[[OptimizationSuggestion], None]
|
|
648
|
+
) -> None:
|
|
649
|
+
"""Add optimization suggestion handler."""
|
|
650
|
+
self.optimization_handlers.append(handler)
|
|
651
|
+
|
|
652
|
+
def add_alert_handler(self, handler: Callable[[PerformanceAlert], None]) -> None:
|
|
653
|
+
"""Add alert handler."""
|
|
654
|
+
self.alert_handlers.append(handler)
|
|
655
|
+
|
|
656
|
+
def get_current_suggestions(
|
|
657
|
+
self, category: Optional[OptimizationCategory] = None
|
|
658
|
+
) -> List[OptimizationSuggestion]:
|
|
659
|
+
"""Get current optimization suggestions."""
|
|
660
|
+
if not self.performance_monitor:
|
|
661
|
+
return []
|
|
662
|
+
|
|
663
|
+
suggestions = self.performance_monitor.suggestions
|
|
664
|
+
|
|
665
|
+
if category:
|
|
666
|
+
# Filter suggestions would require additional logic since OptimizationSuggestion
|
|
667
|
+
# doesn't have category field in the original design
|
|
668
|
+
pass
|
|
669
|
+
|
|
670
|
+
# Return recent suggestions (last 24 hours)
|
|
671
|
+
cutoff_time = datetime.now() - timedelta(hours=24)
|
|
672
|
+
return [s for s in suggestions if s.timestamp >= cutoff_time]
|
|
673
|
+
|
|
674
|
+
def get_active_alerts(
|
|
675
|
+
self, level: Optional[AlertLevel] = None
|
|
676
|
+
) -> List[PerformanceAlert]:
|
|
677
|
+
"""Get active performance alerts."""
|
|
678
|
+
if not self.performance_monitor:
|
|
679
|
+
return []
|
|
680
|
+
|
|
681
|
+
alerts = self.performance_monitor.alerts
|
|
682
|
+
|
|
683
|
+
if level:
|
|
684
|
+
alerts = [a for a in alerts if a.level == level]
|
|
685
|
+
|
|
686
|
+
# Return recent alerts (last 6 hours)
|
|
687
|
+
cutoff_time = datetime.now() - timedelta(hours=6)
|
|
688
|
+
return [a for a in alerts if a.timestamp >= cutoff_time]
|
|
689
|
+
|
|
690
|
+
|
|
691
|
+
# Global optimization engine instance
|
|
692
|
+
_optimization_engine: Optional[OptimizationEngine] = None
|
|
693
|
+
_engine_lock = threading.Lock()
|
|
694
|
+
|
|
695
|
+
|
|
696
|
+
def get_optimization_engine() -> OptimizationEngine:
|
|
697
|
+
"""Get the global optimization engine instance."""
|
|
698
|
+
global _optimization_engine
|
|
699
|
+
if _optimization_engine is None:
|
|
700
|
+
with _engine_lock:
|
|
701
|
+
if _optimization_engine is None:
|
|
702
|
+
_optimization_engine = OptimizationEngine()
|
|
703
|
+
return _optimization_engine
|
|
704
|
+
|
|
705
|
+
|
|
706
|
+
def set_optimization_engine(engine: OptimizationEngine) -> None:
|
|
707
|
+
"""Set the global optimization engine instance."""
|
|
708
|
+
global _optimization_engine
|
|
709
|
+
with _engine_lock:
|
|
710
|
+
_optimization_engine = engine
|
|
711
|
+
|
|
712
|
+
|
|
713
|
+
def initialize_optimization_engine(
|
|
714
|
+
auto_start: bool = True,
|
|
715
|
+
) -> OptimizationEngine:
|
|
716
|
+
"""Initialize optimization engine."""
|
|
717
|
+
engine = get_optimization_engine()
|
|
718
|
+
if auto_start:
|
|
719
|
+
engine.start_analysis()
|
|
720
|
+
return engine
|
|
721
|
+
|
|
722
|
+
|
|
723
|
+
def shutdown_optimization_engine() -> None:
|
|
724
|
+
"""Shutdown optimization engine."""
|
|
725
|
+
global _optimization_engine
|
|
726
|
+
if _optimization_engine:
|
|
727
|
+
_optimization_engine.stop_analysis()
|
|
728
|
+
with _engine_lock:
|
|
729
|
+
_optimization_engine = None
|