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,878 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Enhanced MCP Server Integration Layer for Omnimancer Agent.
|
|
3
|
+
|
|
4
|
+
This module provides advanced MCP server integration with capability matching,
|
|
5
|
+
tool discovery, result processing, and context-aware execution.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
import json
|
|
10
|
+
import logging
|
|
11
|
+
import time
|
|
12
|
+
from collections import defaultdict
|
|
13
|
+
from dataclasses import dataclass, field
|
|
14
|
+
from enum import Enum
|
|
15
|
+
from typing import Any, Dict, List, Optional, Set, Tuple
|
|
16
|
+
|
|
17
|
+
from ..core.models import ToolDefinition
|
|
18
|
+
from ..mcp.manager import MCPManager
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ToolCapability(Enum):
|
|
24
|
+
"""Tool capability categories for matching."""
|
|
25
|
+
|
|
26
|
+
FILE_OPERATIONS = "file_operations"
|
|
27
|
+
WEB_REQUESTS = "web_requests"
|
|
28
|
+
DATA_PROCESSING = "data_processing"
|
|
29
|
+
SYSTEM_INFO = "system_info"
|
|
30
|
+
CODE_EXECUTION = "code_execution"
|
|
31
|
+
DATABASE = "database"
|
|
32
|
+
API_INTEGRATION = "api_integration"
|
|
33
|
+
SEARCH = "search"
|
|
34
|
+
COMMUNICATION = "communication"
|
|
35
|
+
MULTIMEDIA = "multimedia"
|
|
36
|
+
UNKNOWN = "unknown"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class ExecutionPriority(Enum):
|
|
40
|
+
"""Priority levels for tool execution."""
|
|
41
|
+
|
|
42
|
+
CRITICAL = 1
|
|
43
|
+
HIGH = 2
|
|
44
|
+
NORMAL = 3
|
|
45
|
+
LOW = 4
|
|
46
|
+
BACKGROUND = 5
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass
|
|
50
|
+
class ToolExecutionContext:
|
|
51
|
+
"""Context information for tool execution."""
|
|
52
|
+
|
|
53
|
+
user_id: Optional[str] = None
|
|
54
|
+
session_id: Optional[str] = None
|
|
55
|
+
conversation_history: List[Dict[str, Any]] = field(default_factory=list)
|
|
56
|
+
task_context: Optional[str] = None
|
|
57
|
+
execution_priority: ExecutionPriority = ExecutionPriority.NORMAL
|
|
58
|
+
timeout_seconds: float = 30.0
|
|
59
|
+
retry_attempts: int = 3
|
|
60
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@dataclass
|
|
64
|
+
class ToolExecutionResult:
|
|
65
|
+
"""Enhanced result from tool execution."""
|
|
66
|
+
|
|
67
|
+
success: bool
|
|
68
|
+
data: Optional[Any] = None
|
|
69
|
+
error: Optional[str] = None
|
|
70
|
+
execution_time: float = 0.0
|
|
71
|
+
server_name: Optional[str] = None
|
|
72
|
+
tool_name: Optional[str] = None
|
|
73
|
+
attempt_count: int = 1
|
|
74
|
+
context_used: bool = False
|
|
75
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@dataclass
|
|
79
|
+
class ToolMetrics:
|
|
80
|
+
"""Metrics for tool performance tracking."""
|
|
81
|
+
|
|
82
|
+
total_executions: int = 0
|
|
83
|
+
successful_executions: int = 0
|
|
84
|
+
failed_executions: int = 0
|
|
85
|
+
average_execution_time: float = 0.0
|
|
86
|
+
last_execution_time: Optional[float] = None
|
|
87
|
+
last_success_time: Optional[float] = None
|
|
88
|
+
last_failure_time: Optional[float] = None
|
|
89
|
+
consecutive_failures: int = 0
|
|
90
|
+
error_patterns: Dict[str, int] = field(default_factory=dict)
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def success_rate(self) -> float:
|
|
94
|
+
"""Calculate success rate."""
|
|
95
|
+
if self.total_executions == 0:
|
|
96
|
+
return 1.0
|
|
97
|
+
return self.successful_executions / self.total_executions
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def reliability_score(self) -> float:
|
|
101
|
+
"""Calculate reliability score (0.0 to 1.0)."""
|
|
102
|
+
base_score = self.success_rate
|
|
103
|
+
|
|
104
|
+
# Penalize consecutive failures
|
|
105
|
+
failure_penalty = min(self.consecutive_failures * 0.1, 0.5)
|
|
106
|
+
|
|
107
|
+
# Bonus for recent success
|
|
108
|
+
recency_bonus = 0.0
|
|
109
|
+
if self.last_success_time and time.time() - self.last_success_time < 300:
|
|
110
|
+
recency_bonus = 0.1
|
|
111
|
+
|
|
112
|
+
return max(0.0, min(1.0, base_score - failure_penalty + recency_bonus))
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class EnhancedMCPIntegrator:
|
|
116
|
+
"""
|
|
117
|
+
Enhanced MCP integration layer with advanced capabilities including
|
|
118
|
+
tool discovery, capability matching, context-aware execution, and performance monitoring.
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
def __init__(self, mcp_manager: Optional[MCPManager] = None):
|
|
122
|
+
"""
|
|
123
|
+
Initialize the enhanced MCP integrator.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
mcp_manager: Optional MCP manager instance
|
|
127
|
+
"""
|
|
128
|
+
self.mcp_manager = mcp_manager
|
|
129
|
+
|
|
130
|
+
# Tool discovery and caching
|
|
131
|
+
self.discovered_tools: Dict[str, ToolDefinition] = {}
|
|
132
|
+
self.tools_by_capability: Dict[ToolCapability, List[str]] = defaultdict(list)
|
|
133
|
+
self.tools_by_server: Dict[str, List[str]] = defaultdict(list)
|
|
134
|
+
self.tool_capabilities: Dict[str, Set[ToolCapability]] = {}
|
|
135
|
+
|
|
136
|
+
# Performance monitoring
|
|
137
|
+
self.tool_metrics: Dict[str, ToolMetrics] = {}
|
|
138
|
+
self.server_metrics: Dict[str, ToolMetrics] = {}
|
|
139
|
+
|
|
140
|
+
# Execution management
|
|
141
|
+
self.execution_queue: Dict[ExecutionPriority, List[Dict[str, Any]]] = (
|
|
142
|
+
defaultdict(list)
|
|
143
|
+
)
|
|
144
|
+
self.concurrent_executions: Dict[str, asyncio.Task] = {}
|
|
145
|
+
self.max_concurrent_executions = 5
|
|
146
|
+
|
|
147
|
+
# Caching
|
|
148
|
+
self.result_cache: Dict[str, Tuple[ToolExecutionResult, float]] = {}
|
|
149
|
+
self.cache_ttl = 300 # 5 minutes
|
|
150
|
+
self.cache_enabled = True
|
|
151
|
+
|
|
152
|
+
# Last discovery time
|
|
153
|
+
self.last_discovery_time = 0
|
|
154
|
+
self.discovery_interval = 60 # 1 minute
|
|
155
|
+
|
|
156
|
+
async def initialize(self) -> bool:
|
|
157
|
+
"""
|
|
158
|
+
Initialize the MCP integrator and discover available tools.
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
True if initialization was successful
|
|
162
|
+
"""
|
|
163
|
+
if not self.mcp_manager or not self.mcp_manager.initialized:
|
|
164
|
+
logger.warning("MCP manager not available or not initialized")
|
|
165
|
+
return False
|
|
166
|
+
|
|
167
|
+
try:
|
|
168
|
+
await self.discover_tools()
|
|
169
|
+
logger.info(
|
|
170
|
+
f"MCP integrator initialized with {len(self.discovered_tools)} tools"
|
|
171
|
+
)
|
|
172
|
+
return True
|
|
173
|
+
except Exception as e:
|
|
174
|
+
logger.error(f"Failed to initialize MCP integrator: {e}")
|
|
175
|
+
return False
|
|
176
|
+
|
|
177
|
+
async def discover_tools(
|
|
178
|
+
self, force_refresh: bool = False
|
|
179
|
+
) -> Dict[str, ToolDefinition]:
|
|
180
|
+
"""
|
|
181
|
+
Discover and categorize available tools from all MCP servers.
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
force_refresh: Force rediscovery even if recently done
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
Dictionary of discovered tools
|
|
188
|
+
"""
|
|
189
|
+
current_time = time.time()
|
|
190
|
+
|
|
191
|
+
# Check if discovery is needed
|
|
192
|
+
if (
|
|
193
|
+
not force_refresh
|
|
194
|
+
and current_time - self.last_discovery_time < self.discovery_interval
|
|
195
|
+
and self.discovered_tools
|
|
196
|
+
):
|
|
197
|
+
return self.discovered_tools
|
|
198
|
+
|
|
199
|
+
if not self.mcp_manager or not self.mcp_manager.initialized:
|
|
200
|
+
logger.warning("Cannot discover tools: MCP manager not available")
|
|
201
|
+
return {}
|
|
202
|
+
|
|
203
|
+
try:
|
|
204
|
+
logger.debug("Discovering MCP tools...")
|
|
205
|
+
|
|
206
|
+
# Get all available tools
|
|
207
|
+
available_tools = await self.mcp_manager.get_available_tools()
|
|
208
|
+
|
|
209
|
+
# Clear previous discoveries
|
|
210
|
+
self.discovered_tools.clear()
|
|
211
|
+
self.tools_by_capability.clear()
|
|
212
|
+
self.tools_by_server.clear()
|
|
213
|
+
self.tool_capabilities.clear()
|
|
214
|
+
|
|
215
|
+
# Process each tool
|
|
216
|
+
for tool in available_tools:
|
|
217
|
+
# Store tool definition
|
|
218
|
+
self.discovered_tools[tool.name] = tool
|
|
219
|
+
|
|
220
|
+
# Determine capabilities
|
|
221
|
+
capabilities = self._analyze_tool_capabilities(tool)
|
|
222
|
+
self.tool_capabilities[tool.name] = capabilities
|
|
223
|
+
|
|
224
|
+
# Categorize by capability
|
|
225
|
+
for capability in capabilities:
|
|
226
|
+
self.tools_by_capability[capability].append(tool.name)
|
|
227
|
+
|
|
228
|
+
# Find server for this tool
|
|
229
|
+
server_name = self._find_tool_server(tool.name)
|
|
230
|
+
if server_name:
|
|
231
|
+
self.tools_by_server[server_name].append(tool.name)
|
|
232
|
+
|
|
233
|
+
# Initialize metrics if not exists
|
|
234
|
+
if tool.name not in self.tool_metrics:
|
|
235
|
+
self.tool_metrics[tool.name] = ToolMetrics()
|
|
236
|
+
|
|
237
|
+
self.last_discovery_time = current_time
|
|
238
|
+
|
|
239
|
+
logger.info(
|
|
240
|
+
f"Discovered {len(self.discovered_tools)} tools across "
|
|
241
|
+
f"{len(self.tools_by_server)} servers"
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
# Log capability distribution
|
|
245
|
+
for capability, tools in self.tools_by_capability.items():
|
|
246
|
+
if tools:
|
|
247
|
+
logger.debug(f"{capability.value}: {len(tools)} tools")
|
|
248
|
+
|
|
249
|
+
return self.discovered_tools
|
|
250
|
+
|
|
251
|
+
except Exception as e:
|
|
252
|
+
logger.error(f"Tool discovery failed: {e}")
|
|
253
|
+
return {}
|
|
254
|
+
|
|
255
|
+
def _analyze_tool_capabilities(self, tool: ToolDefinition) -> Set[ToolCapability]:
|
|
256
|
+
"""
|
|
257
|
+
Analyze a tool's capabilities based on its name, description, and parameters.
|
|
258
|
+
|
|
259
|
+
Args:
|
|
260
|
+
tool: Tool definition to analyze
|
|
261
|
+
|
|
262
|
+
Returns:
|
|
263
|
+
Set of identified capabilities
|
|
264
|
+
"""
|
|
265
|
+
capabilities = set()
|
|
266
|
+
|
|
267
|
+
# Analyze based on tool name and description
|
|
268
|
+
text_to_analyze = f"{tool.name} {getattr(tool, 'description', '')}"
|
|
269
|
+
text_lower = text_to_analyze.lower()
|
|
270
|
+
|
|
271
|
+
# File operations
|
|
272
|
+
if any(
|
|
273
|
+
keyword in text_lower
|
|
274
|
+
for keyword in [
|
|
275
|
+
"file",
|
|
276
|
+
"read",
|
|
277
|
+
"write",
|
|
278
|
+
"directory",
|
|
279
|
+
"folder",
|
|
280
|
+
"path",
|
|
281
|
+
"save",
|
|
282
|
+
"load",
|
|
283
|
+
]
|
|
284
|
+
):
|
|
285
|
+
capabilities.add(ToolCapability.FILE_OPERATIONS)
|
|
286
|
+
|
|
287
|
+
# Web requests
|
|
288
|
+
if any(
|
|
289
|
+
keyword in text_lower
|
|
290
|
+
for keyword in [
|
|
291
|
+
"http",
|
|
292
|
+
"web",
|
|
293
|
+
"request",
|
|
294
|
+
"api",
|
|
295
|
+
"url",
|
|
296
|
+
"fetch",
|
|
297
|
+
"download",
|
|
298
|
+
"upload",
|
|
299
|
+
]
|
|
300
|
+
):
|
|
301
|
+
capabilities.add(ToolCapability.WEB_REQUESTS)
|
|
302
|
+
|
|
303
|
+
# Data processing
|
|
304
|
+
if any(
|
|
305
|
+
keyword in text_lower
|
|
306
|
+
for keyword in [
|
|
307
|
+
"data",
|
|
308
|
+
"process",
|
|
309
|
+
"parse",
|
|
310
|
+
"transform",
|
|
311
|
+
"convert",
|
|
312
|
+
"analyze",
|
|
313
|
+
"filter",
|
|
314
|
+
]
|
|
315
|
+
):
|
|
316
|
+
capabilities.add(ToolCapability.DATA_PROCESSING)
|
|
317
|
+
|
|
318
|
+
# System info
|
|
319
|
+
if any(
|
|
320
|
+
keyword in text_lower
|
|
321
|
+
for keyword in [
|
|
322
|
+
"system",
|
|
323
|
+
"info",
|
|
324
|
+
"status",
|
|
325
|
+
"monitor",
|
|
326
|
+
"health",
|
|
327
|
+
"performance",
|
|
328
|
+
]
|
|
329
|
+
):
|
|
330
|
+
capabilities.add(ToolCapability.SYSTEM_INFO)
|
|
331
|
+
|
|
332
|
+
# Code execution
|
|
333
|
+
if any(
|
|
334
|
+
keyword in text_lower
|
|
335
|
+
for keyword in [
|
|
336
|
+
"execute",
|
|
337
|
+
"run",
|
|
338
|
+
"code",
|
|
339
|
+
"script",
|
|
340
|
+
"command",
|
|
341
|
+
"shell",
|
|
342
|
+
"bash",
|
|
343
|
+
]
|
|
344
|
+
):
|
|
345
|
+
capabilities.add(ToolCapability.CODE_EXECUTION)
|
|
346
|
+
|
|
347
|
+
# Database
|
|
348
|
+
if any(
|
|
349
|
+
keyword in text_lower
|
|
350
|
+
for keyword in [
|
|
351
|
+
"database",
|
|
352
|
+
"db",
|
|
353
|
+
"sql",
|
|
354
|
+
"query",
|
|
355
|
+
"table",
|
|
356
|
+
"record",
|
|
357
|
+
]
|
|
358
|
+
):
|
|
359
|
+
capabilities.add(ToolCapability.DATABASE)
|
|
360
|
+
|
|
361
|
+
# API integration
|
|
362
|
+
if any(
|
|
363
|
+
keyword in text_lower
|
|
364
|
+
for keyword in [
|
|
365
|
+
"api",
|
|
366
|
+
"integration",
|
|
367
|
+
"service",
|
|
368
|
+
"endpoint",
|
|
369
|
+
"rest",
|
|
370
|
+
"graphql",
|
|
371
|
+
]
|
|
372
|
+
):
|
|
373
|
+
capabilities.add(ToolCapability.API_INTEGRATION)
|
|
374
|
+
|
|
375
|
+
# Search
|
|
376
|
+
if any(
|
|
377
|
+
keyword in text_lower
|
|
378
|
+
for keyword in ["search", "find", "lookup", "query", "index"]
|
|
379
|
+
):
|
|
380
|
+
capabilities.add(ToolCapability.SEARCH)
|
|
381
|
+
|
|
382
|
+
# Communication
|
|
383
|
+
if any(
|
|
384
|
+
keyword in text_lower
|
|
385
|
+
for keyword in [
|
|
386
|
+
"email",
|
|
387
|
+
"message",
|
|
388
|
+
"notification",
|
|
389
|
+
"send",
|
|
390
|
+
"communicate",
|
|
391
|
+
]
|
|
392
|
+
):
|
|
393
|
+
capabilities.add(ToolCapability.COMMUNICATION)
|
|
394
|
+
|
|
395
|
+
# Multimedia
|
|
396
|
+
if any(
|
|
397
|
+
keyword in text_lower
|
|
398
|
+
for keyword in [
|
|
399
|
+
"image",
|
|
400
|
+
"video",
|
|
401
|
+
"audio",
|
|
402
|
+
"media",
|
|
403
|
+
"picture",
|
|
404
|
+
"photo",
|
|
405
|
+
]
|
|
406
|
+
):
|
|
407
|
+
capabilities.add(ToolCapability.MULTIMEDIA)
|
|
408
|
+
|
|
409
|
+
# Default to unknown if no capabilities identified
|
|
410
|
+
if not capabilities:
|
|
411
|
+
capabilities.add(ToolCapability.UNKNOWN)
|
|
412
|
+
|
|
413
|
+
return capabilities
|
|
414
|
+
|
|
415
|
+
def _find_tool_server(self, tool_name: str) -> Optional[str]:
|
|
416
|
+
"""Find which server provides a specific tool."""
|
|
417
|
+
if not self.mcp_manager:
|
|
418
|
+
return None
|
|
419
|
+
|
|
420
|
+
return self.mcp_manager._find_tool_server(tool_name)
|
|
421
|
+
|
|
422
|
+
async def execute_tool_with_context(
|
|
423
|
+
self,
|
|
424
|
+
tool_name: str,
|
|
425
|
+
arguments: Dict[str, Any],
|
|
426
|
+
context: Optional[ToolExecutionContext] = None,
|
|
427
|
+
) -> ToolExecutionResult:
|
|
428
|
+
"""
|
|
429
|
+
Execute a tool with context awareness and enhanced error handling.
|
|
430
|
+
|
|
431
|
+
Args:
|
|
432
|
+
tool_name: Name of the tool to execute
|
|
433
|
+
arguments: Arguments to pass to the tool
|
|
434
|
+
context: Execution context
|
|
435
|
+
|
|
436
|
+
Returns:
|
|
437
|
+
Enhanced execution result
|
|
438
|
+
"""
|
|
439
|
+
context = context or ToolExecutionContext()
|
|
440
|
+
start_time = time.time()
|
|
441
|
+
|
|
442
|
+
# Check cache first
|
|
443
|
+
if self.cache_enabled:
|
|
444
|
+
cached_result = self._get_cached_result(tool_name, arguments)
|
|
445
|
+
if cached_result:
|
|
446
|
+
logger.debug(f"Using cached result for tool: {tool_name}")
|
|
447
|
+
return cached_result
|
|
448
|
+
|
|
449
|
+
# Ensure tool is discovered
|
|
450
|
+
if tool_name not in self.discovered_tools:
|
|
451
|
+
await self.discover_tools()
|
|
452
|
+
|
|
453
|
+
if tool_name not in self.discovered_tools:
|
|
454
|
+
return ToolExecutionResult(
|
|
455
|
+
success=False,
|
|
456
|
+
error=f"Tool '{tool_name}' not found",
|
|
457
|
+
execution_time=time.time() - start_time,
|
|
458
|
+
tool_name=tool_name,
|
|
459
|
+
)
|
|
460
|
+
|
|
461
|
+
# Get server name
|
|
462
|
+
server_name = self._find_tool_server(tool_name)
|
|
463
|
+
|
|
464
|
+
# Execute with retries
|
|
465
|
+
last_error = None
|
|
466
|
+
attempt_count = 0
|
|
467
|
+
|
|
468
|
+
for attempt in range(context.retry_attempts):
|
|
469
|
+
attempt_count += 1
|
|
470
|
+
|
|
471
|
+
try:
|
|
472
|
+
# Add context to arguments if applicable
|
|
473
|
+
enhanced_args = self._enhance_arguments_with_context(arguments, context)
|
|
474
|
+
|
|
475
|
+
# Execute the tool
|
|
476
|
+
execution_start = time.time()
|
|
477
|
+
|
|
478
|
+
# Use timeout
|
|
479
|
+
result = await asyncio.wait_for(
|
|
480
|
+
self.mcp_manager.execute_tool(tool_name, enhanced_args),
|
|
481
|
+
timeout=context.timeout_seconds,
|
|
482
|
+
)
|
|
483
|
+
|
|
484
|
+
execution_time = time.time() - execution_start
|
|
485
|
+
|
|
486
|
+
# Create success result
|
|
487
|
+
success_result = ToolExecutionResult(
|
|
488
|
+
success=True,
|
|
489
|
+
data=(result.content if hasattr(result, "content") else result),
|
|
490
|
+
execution_time=execution_time,
|
|
491
|
+
server_name=server_name,
|
|
492
|
+
tool_name=tool_name,
|
|
493
|
+
attempt_count=attempt_count,
|
|
494
|
+
context_used=bool(enhanced_args != arguments),
|
|
495
|
+
metadata={
|
|
496
|
+
"server_response_time": execution_time,
|
|
497
|
+
"context_priority": context.execution_priority.name,
|
|
498
|
+
"retry_attempt": attempt_count,
|
|
499
|
+
},
|
|
500
|
+
)
|
|
501
|
+
|
|
502
|
+
# Record success metrics
|
|
503
|
+
self._record_execution_metrics(
|
|
504
|
+
tool_name, server_name, True, execution_time
|
|
505
|
+
)
|
|
506
|
+
|
|
507
|
+
# Cache result if applicable
|
|
508
|
+
if self.cache_enabled and self._is_cacheable(tool_name, arguments):
|
|
509
|
+
self._cache_result(tool_name, arguments, success_result)
|
|
510
|
+
|
|
511
|
+
return success_result
|
|
512
|
+
|
|
513
|
+
except asyncio.TimeoutError:
|
|
514
|
+
last_error = (
|
|
515
|
+
f"Tool execution timed out after {context.timeout_seconds}s"
|
|
516
|
+
)
|
|
517
|
+
logger.warning(f"Tool {tool_name} timed out on attempt {attempt + 1}")
|
|
518
|
+
|
|
519
|
+
except Exception as e:
|
|
520
|
+
last_error = str(e)
|
|
521
|
+
logger.warning(f"Tool {tool_name} failed on attempt {attempt + 1}: {e}")
|
|
522
|
+
|
|
523
|
+
# Don't retry for certain errors
|
|
524
|
+
if self._is_non_retryable_error(e):
|
|
525
|
+
break
|
|
526
|
+
|
|
527
|
+
# Wait before retry with exponential backoff
|
|
528
|
+
if attempt < context.retry_attempts - 1:
|
|
529
|
+
wait_time = min(2**attempt, 10)
|
|
530
|
+
await asyncio.sleep(wait_time)
|
|
531
|
+
|
|
532
|
+
# All attempts failed
|
|
533
|
+
total_execution_time = time.time() - start_time
|
|
534
|
+
|
|
535
|
+
error_result = ToolExecutionResult(
|
|
536
|
+
success=False,
|
|
537
|
+
error=last_error,
|
|
538
|
+
execution_time=total_execution_time,
|
|
539
|
+
server_name=server_name,
|
|
540
|
+
tool_name=tool_name,
|
|
541
|
+
attempt_count=attempt_count,
|
|
542
|
+
metadata={
|
|
543
|
+
"max_retries_reached": True,
|
|
544
|
+
"context_priority": context.execution_priority.name,
|
|
545
|
+
"final_error": last_error,
|
|
546
|
+
},
|
|
547
|
+
)
|
|
548
|
+
|
|
549
|
+
# Record failure metrics
|
|
550
|
+
self._record_execution_metrics(
|
|
551
|
+
tool_name, server_name, False, total_execution_time, last_error
|
|
552
|
+
)
|
|
553
|
+
|
|
554
|
+
return error_result
|
|
555
|
+
|
|
556
|
+
def _enhance_arguments_with_context(
|
|
557
|
+
self, arguments: Dict[str, Any], context: ToolExecutionContext
|
|
558
|
+
) -> Dict[str, Any]:
|
|
559
|
+
"""
|
|
560
|
+
Enhance tool arguments with context information if applicable.
|
|
561
|
+
|
|
562
|
+
Args:
|
|
563
|
+
arguments: Original arguments
|
|
564
|
+
context: Execution context
|
|
565
|
+
|
|
566
|
+
Returns:
|
|
567
|
+
Enhanced arguments
|
|
568
|
+
"""
|
|
569
|
+
enhanced_args = arguments.copy()
|
|
570
|
+
|
|
571
|
+
# Add context fields that tools might use
|
|
572
|
+
if context.user_id:
|
|
573
|
+
enhanced_args.setdefault("user_id", context.user_id)
|
|
574
|
+
|
|
575
|
+
if context.session_id:
|
|
576
|
+
enhanced_args.setdefault("session_id", context.session_id)
|
|
577
|
+
|
|
578
|
+
if context.task_context:
|
|
579
|
+
enhanced_args.setdefault("task_context", context.task_context)
|
|
580
|
+
|
|
581
|
+
# Add relevant conversation history for context-aware tools
|
|
582
|
+
if context.conversation_history and len(context.conversation_history) > 0:
|
|
583
|
+
# Only add recent history to avoid overwhelming the tool
|
|
584
|
+
recent_history = context.conversation_history[-5:]
|
|
585
|
+
enhanced_args.setdefault("conversation_context", recent_history)
|
|
586
|
+
|
|
587
|
+
return enhanced_args
|
|
588
|
+
|
|
589
|
+
def _is_non_retryable_error(self, error: Exception) -> bool:
|
|
590
|
+
"""Check if an error should not be retried."""
|
|
591
|
+
error_str = str(error).lower()
|
|
592
|
+
non_retryable_patterns = [
|
|
593
|
+
"invalid arguments",
|
|
594
|
+
"permission denied",
|
|
595
|
+
"not found",
|
|
596
|
+
"authentication failed",
|
|
597
|
+
"tool not found",
|
|
598
|
+
]
|
|
599
|
+
|
|
600
|
+
return any(pattern in error_str for pattern in non_retryable_patterns)
|
|
601
|
+
|
|
602
|
+
def _is_cacheable(self, tool_name: str, arguments: Dict[str, Any]) -> bool:
|
|
603
|
+
"""Check if a tool result should be cached."""
|
|
604
|
+
# Don't cache tools that might have side effects
|
|
605
|
+
side_effect_tools = ["write", "delete", "send", "execute", "run"]
|
|
606
|
+
if any(keyword in tool_name.lower() for keyword in side_effect_tools):
|
|
607
|
+
return False
|
|
608
|
+
|
|
609
|
+
# Don't cache if arguments contain dynamic data
|
|
610
|
+
if any(key in arguments for key in ["timestamp", "random", "session_id"]):
|
|
611
|
+
return False
|
|
612
|
+
|
|
613
|
+
return True
|
|
614
|
+
|
|
615
|
+
def _get_cached_result(
|
|
616
|
+
self, tool_name: str, arguments: Dict[str, Any]
|
|
617
|
+
) -> Optional[ToolExecutionResult]:
|
|
618
|
+
"""Get cached result if available and not expired."""
|
|
619
|
+
cache_key = self._generate_cache_key(tool_name, arguments)
|
|
620
|
+
|
|
621
|
+
if cache_key in self.result_cache:
|
|
622
|
+
result, timestamp = self.result_cache[cache_key]
|
|
623
|
+
if time.time() - timestamp < self.cache_ttl:
|
|
624
|
+
return result
|
|
625
|
+
else:
|
|
626
|
+
# Remove expired cache entry
|
|
627
|
+
del self.result_cache[cache_key]
|
|
628
|
+
|
|
629
|
+
return None
|
|
630
|
+
|
|
631
|
+
def _cache_result(
|
|
632
|
+
self,
|
|
633
|
+
tool_name: str,
|
|
634
|
+
arguments: Dict[str, Any],
|
|
635
|
+
result: ToolExecutionResult,
|
|
636
|
+
):
|
|
637
|
+
"""Cache a tool execution result."""
|
|
638
|
+
cache_key = self._generate_cache_key(tool_name, arguments)
|
|
639
|
+
self.result_cache[cache_key] = (result, time.time())
|
|
640
|
+
|
|
641
|
+
# Limit cache size
|
|
642
|
+
if len(self.result_cache) > 100:
|
|
643
|
+
# Remove oldest entries
|
|
644
|
+
sorted_cache = sorted(self.result_cache.items(), key=lambda x: x[1][1])
|
|
645
|
+
for key, _ in sorted_cache[:20]: # Remove 20 oldest entries
|
|
646
|
+
del self.result_cache[key]
|
|
647
|
+
|
|
648
|
+
def _generate_cache_key(self, tool_name: str, arguments: Dict[str, Any]) -> str:
|
|
649
|
+
"""Generate a cache key for tool execution."""
|
|
650
|
+
# Sort arguments for consistent key generation
|
|
651
|
+
sorted_args = json.dumps(arguments, sort_keys=True)
|
|
652
|
+
return f"{tool_name}:{hash(sorted_args)}"
|
|
653
|
+
|
|
654
|
+
def _record_execution_metrics(
|
|
655
|
+
self,
|
|
656
|
+
tool_name: str,
|
|
657
|
+
server_name: Optional[str],
|
|
658
|
+
success: bool,
|
|
659
|
+
execution_time: float,
|
|
660
|
+
error: Optional[str] = None,
|
|
661
|
+
):
|
|
662
|
+
"""Record execution metrics for monitoring."""
|
|
663
|
+
current_time = time.time()
|
|
664
|
+
|
|
665
|
+
# Update tool metrics
|
|
666
|
+
if tool_name not in self.tool_metrics:
|
|
667
|
+
self.tool_metrics[tool_name] = ToolMetrics()
|
|
668
|
+
|
|
669
|
+
tool_metrics = self.tool_metrics[tool_name]
|
|
670
|
+
tool_metrics.total_executions += 1
|
|
671
|
+
tool_metrics.last_execution_time = current_time
|
|
672
|
+
|
|
673
|
+
# Update average execution time
|
|
674
|
+
if tool_metrics.average_execution_time == 0:
|
|
675
|
+
tool_metrics.average_execution_time = execution_time
|
|
676
|
+
else:
|
|
677
|
+
tool_metrics.average_execution_time = (
|
|
678
|
+
tool_metrics.average_execution_time * 0.8 + execution_time * 0.2
|
|
679
|
+
)
|
|
680
|
+
|
|
681
|
+
if success:
|
|
682
|
+
tool_metrics.successful_executions += 1
|
|
683
|
+
tool_metrics.last_success_time = current_time
|
|
684
|
+
tool_metrics.consecutive_failures = 0
|
|
685
|
+
else:
|
|
686
|
+
tool_metrics.failed_executions += 1
|
|
687
|
+
tool_metrics.last_failure_time = current_time
|
|
688
|
+
tool_metrics.consecutive_failures += 1
|
|
689
|
+
|
|
690
|
+
# Track error patterns
|
|
691
|
+
if error:
|
|
692
|
+
error_pattern = error[:50] # First 50 chars
|
|
693
|
+
tool_metrics.error_patterns[error_pattern] = (
|
|
694
|
+
tool_metrics.error_patterns.get(error_pattern, 0) + 1
|
|
695
|
+
)
|
|
696
|
+
|
|
697
|
+
# Update server metrics
|
|
698
|
+
if server_name:
|
|
699
|
+
if server_name not in self.server_metrics:
|
|
700
|
+
self.server_metrics[server_name] = ToolMetrics()
|
|
701
|
+
|
|
702
|
+
server_metrics = self.server_metrics[server_name]
|
|
703
|
+
server_metrics.total_executions += 1
|
|
704
|
+
server_metrics.last_execution_time = current_time
|
|
705
|
+
|
|
706
|
+
if success:
|
|
707
|
+
server_metrics.successful_executions += 1
|
|
708
|
+
server_metrics.last_success_time = current_time
|
|
709
|
+
server_metrics.consecutive_failures = 0
|
|
710
|
+
else:
|
|
711
|
+
server_metrics.failed_executions += 1
|
|
712
|
+
server_metrics.last_failure_time = current_time
|
|
713
|
+
server_metrics.consecutive_failures += 1
|
|
714
|
+
|
|
715
|
+
def find_tools_by_capability(self, capability: ToolCapability) -> List[str]:
|
|
716
|
+
"""Find tools that have a specific capability."""
|
|
717
|
+
return self.tools_by_capability.get(capability, [])
|
|
718
|
+
|
|
719
|
+
def find_best_tool_for_task(
|
|
720
|
+
self,
|
|
721
|
+
task_description: str,
|
|
722
|
+
capability: Optional[ToolCapability] = None,
|
|
723
|
+
) -> Optional[str]:
|
|
724
|
+
"""
|
|
725
|
+
Find the best tool for a given task based on description and capability.
|
|
726
|
+
|
|
727
|
+
Args:
|
|
728
|
+
task_description: Description of the task
|
|
729
|
+
capability: Optional capability requirement
|
|
730
|
+
|
|
731
|
+
Returns:
|
|
732
|
+
Name of the best matching tool or None
|
|
733
|
+
"""
|
|
734
|
+
candidates = []
|
|
735
|
+
|
|
736
|
+
if capability:
|
|
737
|
+
# Filter by capability first
|
|
738
|
+
candidates = self.find_tools_by_capability(capability)
|
|
739
|
+
else:
|
|
740
|
+
# Use all available tools
|
|
741
|
+
candidates = list(self.discovered_tools.keys())
|
|
742
|
+
|
|
743
|
+
if not candidates:
|
|
744
|
+
return None
|
|
745
|
+
|
|
746
|
+
# Score tools based on task description
|
|
747
|
+
task_lower = task_description.lower()
|
|
748
|
+
scored_tools = []
|
|
749
|
+
|
|
750
|
+
for tool_name in candidates:
|
|
751
|
+
tool = self.discovered_tools[tool_name]
|
|
752
|
+
score = 0
|
|
753
|
+
|
|
754
|
+
# Score based on name similarity
|
|
755
|
+
if any(word in tool_name.lower() for word in task_lower.split()):
|
|
756
|
+
score += 2
|
|
757
|
+
|
|
758
|
+
# Score based on description similarity
|
|
759
|
+
description = getattr(tool, "description", "")
|
|
760
|
+
if description:
|
|
761
|
+
desc_lower = description.lower()
|
|
762
|
+
common_words = set(task_lower.split()) & set(desc_lower.split())
|
|
763
|
+
score += len(common_words)
|
|
764
|
+
|
|
765
|
+
# Boost score based on reliability
|
|
766
|
+
metrics = self.tool_metrics.get(tool_name)
|
|
767
|
+
if metrics:
|
|
768
|
+
score += metrics.reliability_score * 5
|
|
769
|
+
|
|
770
|
+
# Penalize if tool has been failing recently
|
|
771
|
+
if metrics.consecutive_failures > 2:
|
|
772
|
+
score -= metrics.consecutive_failures
|
|
773
|
+
|
|
774
|
+
scored_tools.append((tool_name, score))
|
|
775
|
+
|
|
776
|
+
# Return the highest scoring tool
|
|
777
|
+
if scored_tools:
|
|
778
|
+
scored_tools.sort(key=lambda x: x[1], reverse=True)
|
|
779
|
+
return scored_tools[0][0]
|
|
780
|
+
|
|
781
|
+
return None
|
|
782
|
+
|
|
783
|
+
def get_tool_metrics(self, tool_name: Optional[str] = None) -> Dict[str, Any]:
|
|
784
|
+
"""Get performance metrics for tools."""
|
|
785
|
+
if tool_name:
|
|
786
|
+
metrics = self.tool_metrics.get(tool_name)
|
|
787
|
+
if metrics:
|
|
788
|
+
return {
|
|
789
|
+
"tool_name": tool_name,
|
|
790
|
+
"total_executions": metrics.total_executions,
|
|
791
|
+
"success_rate": metrics.success_rate,
|
|
792
|
+
"reliability_score": metrics.reliability_score,
|
|
793
|
+
"average_execution_time": metrics.average_execution_time,
|
|
794
|
+
"consecutive_failures": metrics.consecutive_failures,
|
|
795
|
+
"error_patterns": dict(metrics.error_patterns),
|
|
796
|
+
}
|
|
797
|
+
return {}
|
|
798
|
+
else:
|
|
799
|
+
# Return summary for all tools
|
|
800
|
+
summary = {}
|
|
801
|
+
for name, metrics in self.tool_metrics.items():
|
|
802
|
+
summary[name] = {
|
|
803
|
+
"success_rate": metrics.success_rate,
|
|
804
|
+
"reliability_score": metrics.reliability_score,
|
|
805
|
+
"total_executions": metrics.total_executions,
|
|
806
|
+
}
|
|
807
|
+
return summary
|
|
808
|
+
|
|
809
|
+
def get_server_metrics(self) -> Dict[str, Any]:
|
|
810
|
+
"""Get performance metrics for servers."""
|
|
811
|
+
summary = {}
|
|
812
|
+
for server_name, metrics in self.server_metrics.items():
|
|
813
|
+
summary[server_name] = {
|
|
814
|
+
"success_rate": metrics.success_rate,
|
|
815
|
+
"reliability_score": metrics.reliability_score,
|
|
816
|
+
"total_executions": metrics.total_executions,
|
|
817
|
+
"tool_count": len(self.tools_by_server.get(server_name, [])),
|
|
818
|
+
}
|
|
819
|
+
return summary
|
|
820
|
+
|
|
821
|
+
def get_capability_summary(self) -> Dict[str, int]:
|
|
822
|
+
"""Get summary of tools by capability."""
|
|
823
|
+
return {
|
|
824
|
+
capability.value: len(tools)
|
|
825
|
+
for capability, tools in self.tools_by_capability.items()
|
|
826
|
+
if tools
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
def clear_cache(self):
|
|
830
|
+
"""Clear the result cache."""
|
|
831
|
+
self.result_cache.clear()
|
|
832
|
+
logger.info("Tool result cache cleared")
|
|
833
|
+
|
|
834
|
+
def reset_metrics(self, tool_name: Optional[str] = None):
|
|
835
|
+
"""Reset performance metrics."""
|
|
836
|
+
if tool_name:
|
|
837
|
+
if tool_name in self.tool_metrics:
|
|
838
|
+
self.tool_metrics[tool_name] = ToolMetrics()
|
|
839
|
+
logger.info(f"Reset metrics for tool: {tool_name}")
|
|
840
|
+
else:
|
|
841
|
+
self.tool_metrics.clear()
|
|
842
|
+
self.server_metrics.clear()
|
|
843
|
+
logger.info("Reset all tool metrics")
|
|
844
|
+
|
|
845
|
+
async def health_check(self) -> Dict[str, Any]:
|
|
846
|
+
"""Perform health check on the MCP integration layer."""
|
|
847
|
+
health_status = {
|
|
848
|
+
"integrator_healthy": True,
|
|
849
|
+
"mcp_manager_available": bool(self.mcp_manager),
|
|
850
|
+
"mcp_manager_initialized": bool(
|
|
851
|
+
self.mcp_manager and self.mcp_manager.initialized
|
|
852
|
+
),
|
|
853
|
+
"discovered_tools_count": len(self.discovered_tools),
|
|
854
|
+
"servers_count": len(self.tools_by_server),
|
|
855
|
+
"cache_size": len(self.result_cache),
|
|
856
|
+
"metrics_tracked_tools": len(self.tool_metrics),
|
|
857
|
+
"last_discovery_time": self.last_discovery_time,
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
# Check if discovery is stale
|
|
861
|
+
if time.time() - self.last_discovery_time > self.discovery_interval * 2:
|
|
862
|
+
health_status["discovery_stale"] = True
|
|
863
|
+
health_status["integrator_healthy"] = False
|
|
864
|
+
|
|
865
|
+
# Check for tools with high failure rates
|
|
866
|
+
problematic_tools = []
|
|
867
|
+
for tool_name, metrics in self.tool_metrics.items():
|
|
868
|
+
if metrics.reliability_score < 0.5 and metrics.total_executions >= 5:
|
|
869
|
+
problematic_tools.append(tool_name)
|
|
870
|
+
|
|
871
|
+
if problematic_tools:
|
|
872
|
+
health_status["problematic_tools"] = problematic_tools
|
|
873
|
+
if (
|
|
874
|
+
len(problematic_tools) > len(self.discovered_tools) * 0.3
|
|
875
|
+
): # More than 30% problematic
|
|
876
|
+
health_status["integrator_healthy"] = False
|
|
877
|
+
|
|
878
|
+
return health_status
|