mcp-mesh 0.7.21__py3-none-any.whl → 0.8.0b1__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.
- _mcp_mesh/__init__.py +1 -1
- _mcp_mesh/engine/dependency_injector.py +4 -6
- _mcp_mesh/engine/http_wrapper.py +69 -10
- _mcp_mesh/engine/mesh_llm_agent.py +4 -7
- _mcp_mesh/engine/mesh_llm_agent_injector.py +2 -1
- _mcp_mesh/engine/provider_handlers/__init__.py +14 -1
- _mcp_mesh/engine/provider_handlers/base_provider_handler.py +114 -8
- _mcp_mesh/engine/provider_handlers/claude_handler.py +15 -57
- _mcp_mesh/engine/provider_handlers/gemini_handler.py +181 -0
- _mcp_mesh/engine/provider_handlers/openai_handler.py +8 -63
- _mcp_mesh/engine/provider_handlers/provider_handler_registry.py +16 -10
- _mcp_mesh/engine/response_parser.py +61 -15
- _mcp_mesh/engine/unified_mcp_proxy.py +18 -34
- _mcp_mesh/pipeline/__init__.py +9 -20
- _mcp_mesh/pipeline/api_heartbeat/__init__.py +12 -7
- _mcp_mesh/pipeline/api_heartbeat/api_lifespan_integration.py +23 -49
- _mcp_mesh/pipeline/api_heartbeat/rust_api_heartbeat.py +425 -0
- _mcp_mesh/pipeline/api_startup/api_pipeline.py +7 -9
- _mcp_mesh/pipeline/api_startup/api_server_setup.py +91 -70
- _mcp_mesh/pipeline/api_startup/fastapi_discovery.py +22 -23
- _mcp_mesh/pipeline/api_startup/middleware_integration.py +32 -24
- _mcp_mesh/pipeline/api_startup/route_collection.py +2 -4
- _mcp_mesh/pipeline/mcp_heartbeat/__init__.py +5 -17
- _mcp_mesh/pipeline/mcp_heartbeat/rust_heartbeat.py +695 -0
- _mcp_mesh/pipeline/mcp_startup/__init__.py +2 -5
- _mcp_mesh/pipeline/mcp_startup/configuration.py +1 -1
- _mcp_mesh/pipeline/mcp_startup/fastapiserver_setup.py +5 -6
- _mcp_mesh/pipeline/mcp_startup/heartbeat_loop.py +6 -7
- _mcp_mesh/pipeline/mcp_startup/startup_orchestrator.py +21 -9
- _mcp_mesh/pipeline/mcp_startup/startup_pipeline.py +3 -8
- _mcp_mesh/pipeline/shared/mesh_pipeline.py +0 -2
- _mcp_mesh/reload.py +1 -3
- _mcp_mesh/shared/__init__.py +2 -8
- _mcp_mesh/shared/config_resolver.py +124 -80
- _mcp_mesh/shared/defaults.py +89 -14
- _mcp_mesh/shared/fastapi_middleware_manager.py +149 -91
- _mcp_mesh/shared/host_resolver.py +8 -46
- _mcp_mesh/shared/server_discovery.py +115 -86
- _mcp_mesh/shared/simple_shutdown.py +44 -86
- _mcp_mesh/tracing/execution_tracer.py +2 -6
- _mcp_mesh/tracing/redis_metadata_publisher.py +24 -79
- _mcp_mesh/tracing/trace_context_helper.py +3 -13
- _mcp_mesh/tracing/utils.py +29 -15
- _mcp_mesh/utils/fastmcp_schema_extractor.py +2 -1
- {mcp_mesh-0.7.21.dist-info → mcp_mesh-0.8.0b1.dist-info}/METADATA +2 -1
- mcp_mesh-0.8.0b1.dist-info/RECORD +85 -0
- mesh/__init__.py +2 -1
- mesh/decorators.py +89 -5
- _mcp_mesh/generated/.openapi-generator/FILES +0 -50
- _mcp_mesh/generated/.openapi-generator/VERSION +0 -1
- _mcp_mesh/generated/.openapi-generator-ignore +0 -15
- _mcp_mesh/generated/mcp_mesh_registry_client/__init__.py +0 -90
- _mcp_mesh/generated/mcp_mesh_registry_client/api/__init__.py +0 -6
- _mcp_mesh/generated/mcp_mesh_registry_client/api/agents_api.py +0 -1088
- _mcp_mesh/generated/mcp_mesh_registry_client/api/health_api.py +0 -764
- _mcp_mesh/generated/mcp_mesh_registry_client/api/tracing_api.py +0 -303
- _mcp_mesh/generated/mcp_mesh_registry_client/api_client.py +0 -798
- _mcp_mesh/generated/mcp_mesh_registry_client/api_response.py +0 -21
- _mcp_mesh/generated/mcp_mesh_registry_client/configuration.py +0 -577
- _mcp_mesh/generated/mcp_mesh_registry_client/exceptions.py +0 -217
- _mcp_mesh/generated/mcp_mesh_registry_client/models/__init__.py +0 -55
- _mcp_mesh/generated/mcp_mesh_registry_client/models/agent_info.py +0 -158
- _mcp_mesh/generated/mcp_mesh_registry_client/models/agent_metadata.py +0 -126
- _mcp_mesh/generated/mcp_mesh_registry_client/models/agent_metadata_dependencies_inner.py +0 -139
- _mcp_mesh/generated/mcp_mesh_registry_client/models/agent_metadata_dependencies_inner_one_of.py +0 -92
- _mcp_mesh/generated/mcp_mesh_registry_client/models/agent_registration.py +0 -103
- _mcp_mesh/generated/mcp_mesh_registry_client/models/agent_registration_metadata.py +0 -136
- _mcp_mesh/generated/mcp_mesh_registry_client/models/agents_list_response.py +0 -100
- _mcp_mesh/generated/mcp_mesh_registry_client/models/capability_info.py +0 -107
- _mcp_mesh/generated/mcp_mesh_registry_client/models/decorator_agent_metadata.py +0 -112
- _mcp_mesh/generated/mcp_mesh_registry_client/models/decorator_agent_request.py +0 -103
- _mcp_mesh/generated/mcp_mesh_registry_client/models/decorator_info.py +0 -105
- _mcp_mesh/generated/mcp_mesh_registry_client/models/dependency_info.py +0 -103
- _mcp_mesh/generated/mcp_mesh_registry_client/models/dependency_resolution_info.py +0 -106
- _mcp_mesh/generated/mcp_mesh_registry_client/models/error_response.py +0 -91
- _mcp_mesh/generated/mcp_mesh_registry_client/models/health_response.py +0 -103
- _mcp_mesh/generated/mcp_mesh_registry_client/models/heartbeat_request.py +0 -101
- _mcp_mesh/generated/mcp_mesh_registry_client/models/heartbeat_request_metadata.py +0 -111
- _mcp_mesh/generated/mcp_mesh_registry_client/models/heartbeat_response.py +0 -117
- _mcp_mesh/generated/mcp_mesh_registry_client/models/llm_provider.py +0 -93
- _mcp_mesh/generated/mcp_mesh_registry_client/models/llm_provider_resolution_info.py +0 -106
- _mcp_mesh/generated/mcp_mesh_registry_client/models/llm_tool_filter.py +0 -109
- _mcp_mesh/generated/mcp_mesh_registry_client/models/llm_tool_filter_filter_inner.py +0 -139
- _mcp_mesh/generated/mcp_mesh_registry_client/models/llm_tool_filter_filter_inner_one_of.py +0 -91
- _mcp_mesh/generated/mcp_mesh_registry_client/models/llm_tool_info.py +0 -101
- _mcp_mesh/generated/mcp_mesh_registry_client/models/llm_tool_resolution_info.py +0 -120
- _mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_agent_register_metadata.py +0 -112
- _mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_agent_registration.py +0 -129
- _mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_registration_response.py +0 -153
- _mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_registration_response_dependencies_resolved_value_inner.py +0 -101
- _mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_tool_dependency_registration.py +0 -93
- _mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_tool_register_metadata.py +0 -107
- _mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_tool_registration.py +0 -117
- _mcp_mesh/generated/mcp_mesh_registry_client/models/registration_response.py +0 -119
- _mcp_mesh/generated/mcp_mesh_registry_client/models/resolved_llm_provider.py +0 -110
- _mcp_mesh/generated/mcp_mesh_registry_client/models/rich_dependency.py +0 -93
- _mcp_mesh/generated/mcp_mesh_registry_client/models/root_response.py +0 -92
- _mcp_mesh/generated/mcp_mesh_registry_client/models/standardized_dependency.py +0 -93
- _mcp_mesh/generated/mcp_mesh_registry_client/models/trace_event.py +0 -106
- _mcp_mesh/generated/mcp_mesh_registry_client/py.typed +0 -0
- _mcp_mesh/generated/mcp_mesh_registry_client/rest.py +0 -259
- _mcp_mesh/pipeline/api_heartbeat/api_dependency_resolution.py +0 -418
- _mcp_mesh/pipeline/api_heartbeat/api_fast_heartbeat_check.py +0 -117
- _mcp_mesh/pipeline/api_heartbeat/api_health_check.py +0 -140
- _mcp_mesh/pipeline/api_heartbeat/api_heartbeat_orchestrator.py +0 -243
- _mcp_mesh/pipeline/api_heartbeat/api_heartbeat_pipeline.py +0 -311
- _mcp_mesh/pipeline/api_heartbeat/api_heartbeat_send.py +0 -386
- _mcp_mesh/pipeline/api_heartbeat/api_registry_connection.py +0 -104
- _mcp_mesh/pipeline/mcp_heartbeat/dependency_resolution.py +0 -396
- _mcp_mesh/pipeline/mcp_heartbeat/fast_heartbeat_check.py +0 -116
- _mcp_mesh/pipeline/mcp_heartbeat/heartbeat_orchestrator.py +0 -311
- _mcp_mesh/pipeline/mcp_heartbeat/heartbeat_pipeline.py +0 -282
- _mcp_mesh/pipeline/mcp_heartbeat/heartbeat_send.py +0 -98
- _mcp_mesh/pipeline/mcp_heartbeat/lifespan_integration.py +0 -84
- _mcp_mesh/pipeline/mcp_heartbeat/llm_tools_resolution.py +0 -264
- _mcp_mesh/pipeline/mcp_heartbeat/registry_connection.py +0 -79
- _mcp_mesh/pipeline/shared/registry_connection.py +0 -80
- _mcp_mesh/shared/registry_client_wrapper.py +0 -515
- mcp_mesh-0.7.21.dist-info/RECORD +0 -152
- {mcp_mesh-0.7.21.dist-info → mcp_mesh-0.8.0b1.dist-info}/WHEEL +0 -0
- {mcp_mesh-0.7.21.dist-info → mcp_mesh-0.8.0b1.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,243 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
API heartbeat orchestrator for managing periodic pipeline execution.
|
|
3
|
-
|
|
4
|
-
Provides a high-level interface for executing API heartbeat pipelines
|
|
5
|
-
with proper context management and error handling for FastAPI services.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
import json
|
|
9
|
-
import logging
|
|
10
|
-
from datetime import UTC, datetime
|
|
11
|
-
from typing import Any, Dict, Optional
|
|
12
|
-
|
|
13
|
-
from .api_heartbeat_pipeline import APIHeartbeatPipeline
|
|
14
|
-
|
|
15
|
-
logger = logging.getLogger(__name__)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class APIHeartbeatOrchestrator:
|
|
19
|
-
"""
|
|
20
|
-
Orchestrates API heartbeat pipeline execution for periodic registry communication.
|
|
21
|
-
|
|
22
|
-
Manages the context preparation, pipeline execution, and result processing
|
|
23
|
-
for the periodic heartbeat cycle of FastAPI services using @mesh.route decorators.
|
|
24
|
-
"""
|
|
25
|
-
|
|
26
|
-
def __init__(self):
|
|
27
|
-
self.logger = logging.getLogger(f"{__name__}.APIHeartbeatOrchestrator")
|
|
28
|
-
self.pipeline = APIHeartbeatPipeline()
|
|
29
|
-
self._heartbeat_count = 0
|
|
30
|
-
|
|
31
|
-
async def execute_api_heartbeat(
|
|
32
|
-
self, service_id: str, context: dict[str, Any]
|
|
33
|
-
) -> bool:
|
|
34
|
-
"""
|
|
35
|
-
Execute a complete API heartbeat cycle with comprehensive error handling.
|
|
36
|
-
|
|
37
|
-
Args:
|
|
38
|
-
service_id: Service identifier for the FastAPI application
|
|
39
|
-
context: Full pipeline context from API startup
|
|
40
|
-
|
|
41
|
-
Returns:
|
|
42
|
-
bool: True if heartbeat succeeded, False if failed
|
|
43
|
-
"""
|
|
44
|
-
self._heartbeat_count += 1
|
|
45
|
-
|
|
46
|
-
try:
|
|
47
|
-
# Prepare heartbeat context with validation
|
|
48
|
-
heartbeat_context = self._prepare_api_heartbeat_context(service_id, context)
|
|
49
|
-
|
|
50
|
-
# Validate required context before proceeding
|
|
51
|
-
if not self._validate_api_heartbeat_context(heartbeat_context):
|
|
52
|
-
self.logger.error(
|
|
53
|
-
f"❌ API heartbeat #{self._heartbeat_count} failed: invalid context"
|
|
54
|
-
)
|
|
55
|
-
return False
|
|
56
|
-
|
|
57
|
-
# Log heartbeat request details for debugging
|
|
58
|
-
self._log_api_heartbeat_request(heartbeat_context, self._heartbeat_count)
|
|
59
|
-
|
|
60
|
-
# Execute API heartbeat pipeline with timeout protection
|
|
61
|
-
self.logger.trace(f"💓 Executing API heartbeat #{self._heartbeat_count} for service '{service_id}'")
|
|
62
|
-
|
|
63
|
-
# Add timeout to prevent hanging heartbeats (30 seconds max)
|
|
64
|
-
import asyncio
|
|
65
|
-
|
|
66
|
-
try:
|
|
67
|
-
self.logger.trace("Starting API heartbeat pipeline execution")
|
|
68
|
-
result = await asyncio.wait_for(
|
|
69
|
-
self.pipeline.execute_api_heartbeat_cycle(heartbeat_context),
|
|
70
|
-
timeout=30.0,
|
|
71
|
-
)
|
|
72
|
-
if result.is_success():
|
|
73
|
-
self.logger.trace("✅ API heartbeat pipeline completed successfully")
|
|
74
|
-
else:
|
|
75
|
-
self.logger.error(f"❌ API heartbeat pipeline failed: {result.message}")
|
|
76
|
-
except TimeoutError:
|
|
77
|
-
self.logger.error(
|
|
78
|
-
f"❌ API heartbeat #{self._heartbeat_count} timed out after 30 seconds"
|
|
79
|
-
)
|
|
80
|
-
return False
|
|
81
|
-
except Exception as e:
|
|
82
|
-
self.logger.error(f"❌ [DEBUG] Pipeline execution exception: {e}")
|
|
83
|
-
import traceback
|
|
84
|
-
self.logger.error(f"❌ [DEBUG] Traceback: {traceback.format_exc()}")
|
|
85
|
-
return False
|
|
86
|
-
|
|
87
|
-
# Process results
|
|
88
|
-
success = self._process_api_heartbeat_result(
|
|
89
|
-
result, service_id, self._heartbeat_count
|
|
90
|
-
)
|
|
91
|
-
|
|
92
|
-
# Log periodic status updates
|
|
93
|
-
if self._heartbeat_count % 10 == 0:
|
|
94
|
-
elapsed_time = self._heartbeat_count * 5 # Using 5s interval (MeshDefaults.HEALTH_INTERVAL)
|
|
95
|
-
self.logger.info(
|
|
96
|
-
f"💓 API heartbeat #{self._heartbeat_count} for service '{service_id}' - "
|
|
97
|
-
f"running for {elapsed_time} seconds"
|
|
98
|
-
)
|
|
99
|
-
|
|
100
|
-
return success
|
|
101
|
-
|
|
102
|
-
except Exception as e:
|
|
103
|
-
# Log detailed error information for debugging
|
|
104
|
-
import traceback
|
|
105
|
-
|
|
106
|
-
self.logger.error(
|
|
107
|
-
f"❌ API heartbeat #{self._heartbeat_count} failed for service '{service_id}': {e}\n"
|
|
108
|
-
f"Traceback: {traceback.format_exc()}"
|
|
109
|
-
)
|
|
110
|
-
return False
|
|
111
|
-
|
|
112
|
-
def _prepare_api_heartbeat_context(
|
|
113
|
-
self, service_id: str, startup_context: dict[str, Any]
|
|
114
|
-
) -> dict[str, Any]:
|
|
115
|
-
"""Prepare context for API heartbeat pipeline execution."""
|
|
116
|
-
|
|
117
|
-
# Get FastAPI app and other essential components from startup context
|
|
118
|
-
fastapi_app = startup_context.get("fastapi_app")
|
|
119
|
-
display_config = startup_context.get("display_config", {})
|
|
120
|
-
|
|
121
|
-
# Get API service metadata from startup context
|
|
122
|
-
api_service_metadata = startup_context.get("api_service_metadata", {})
|
|
123
|
-
|
|
124
|
-
# Build heartbeat-specific context
|
|
125
|
-
heartbeat_context = {
|
|
126
|
-
"service_id": service_id,
|
|
127
|
-
"agent_id": service_id, # For compatibility with registry calls
|
|
128
|
-
"fastapi_app": fastapi_app,
|
|
129
|
-
"display_config": display_config,
|
|
130
|
-
# Include registry and configuration from startup
|
|
131
|
-
"agent_config": startup_context.get("agent_config", {}),
|
|
132
|
-
"registration_data": startup_context.get("registration_data", {}),
|
|
133
|
-
"registry_wrapper": startup_context.get("registry_wrapper"),
|
|
134
|
-
# CRITICAL: Include API service metadata with route dependencies
|
|
135
|
-
"api_service_metadata": api_service_metadata,
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
return heartbeat_context
|
|
139
|
-
|
|
140
|
-
def _validate_api_heartbeat_context(self, heartbeat_context: dict[str, Any]) -> bool:
|
|
141
|
-
"""Validate that API heartbeat context has all required components."""
|
|
142
|
-
|
|
143
|
-
required_fields = ["service_id", "fastapi_app"]
|
|
144
|
-
|
|
145
|
-
for field in required_fields:
|
|
146
|
-
if field not in heartbeat_context or heartbeat_context[field] is None:
|
|
147
|
-
self.logger.error(
|
|
148
|
-
f"❌ API heartbeat context validation failed: missing '{field}'"
|
|
149
|
-
)
|
|
150
|
-
return False
|
|
151
|
-
|
|
152
|
-
# Additional validation for FastAPI app
|
|
153
|
-
fastapi_app = heartbeat_context.get("fastapi_app")
|
|
154
|
-
if not hasattr(fastapi_app, "routes"):
|
|
155
|
-
self.logger.error(
|
|
156
|
-
"❌ API heartbeat context validation failed: invalid FastAPI app object"
|
|
157
|
-
)
|
|
158
|
-
return False
|
|
159
|
-
|
|
160
|
-
return True
|
|
161
|
-
|
|
162
|
-
def _log_api_heartbeat_request(
|
|
163
|
-
self, heartbeat_context: dict[str, Any], heartbeat_count: int
|
|
164
|
-
) -> None:
|
|
165
|
-
"""Log API heartbeat request details for debugging."""
|
|
166
|
-
|
|
167
|
-
service_id = heartbeat_context.get("service_id", "unknown")
|
|
168
|
-
fastapi_app = heartbeat_context.get("fastapi_app")
|
|
169
|
-
display_config = heartbeat_context.get("display_config", {})
|
|
170
|
-
|
|
171
|
-
# Extract app information for logging
|
|
172
|
-
app_info = {}
|
|
173
|
-
if fastapi_app:
|
|
174
|
-
app_info = {
|
|
175
|
-
"title": getattr(fastapi_app, "title", "Unknown API"),
|
|
176
|
-
"version": getattr(fastapi_app, "version", "1.0.0"),
|
|
177
|
-
"routes_count": len(getattr(fastapi_app, "routes", [])),
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
# Log heartbeat details
|
|
181
|
-
self.logger.trace(
|
|
182
|
-
f"🔍 API Heartbeat #{heartbeat_count} for '{service_id}': "
|
|
183
|
-
f"app={app_info}, display={display_config}"
|
|
184
|
-
)
|
|
185
|
-
|
|
186
|
-
def _process_api_heartbeat_result(
|
|
187
|
-
self, result: Any, service_id: str, heartbeat_count: int
|
|
188
|
-
) -> bool:
|
|
189
|
-
"""Process API heartbeat pipeline result and log appropriately."""
|
|
190
|
-
|
|
191
|
-
if result.is_success():
|
|
192
|
-
# Check for heartbeat response in result context
|
|
193
|
-
heartbeat_response = result.context.get("heartbeat_response")
|
|
194
|
-
heartbeat_success = result.context.get("heartbeat_success", False)
|
|
195
|
-
|
|
196
|
-
self.logger.trace(f"API heartbeat result - success: {heartbeat_success}")
|
|
197
|
-
|
|
198
|
-
# Check if heartbeat was skipped due to optimization
|
|
199
|
-
heartbeat_skipped = result.context.get("heartbeat_skipped", False)
|
|
200
|
-
skip_reason = result.context.get("skip_reason")
|
|
201
|
-
|
|
202
|
-
if heartbeat_success and heartbeat_response:
|
|
203
|
-
# Log response details for tracing
|
|
204
|
-
try:
|
|
205
|
-
response_json = json.dumps(
|
|
206
|
-
heartbeat_response, indent=2, default=str
|
|
207
|
-
)
|
|
208
|
-
self.logger.trace(
|
|
209
|
-
f"🔍 API heartbeat response #{heartbeat_count}:\n{response_json}"
|
|
210
|
-
)
|
|
211
|
-
except Exception as e:
|
|
212
|
-
self.logger.trace(
|
|
213
|
-
f"🔍 API heartbeat response #{heartbeat_count}: {heartbeat_response} "
|
|
214
|
-
f"(json serialization failed: {e})"
|
|
215
|
-
)
|
|
216
|
-
|
|
217
|
-
self.logger.debug(
|
|
218
|
-
f"🚀 API heartbeat #{heartbeat_count} sent for service '{service_id}'"
|
|
219
|
-
)
|
|
220
|
-
return True
|
|
221
|
-
elif heartbeat_success and heartbeat_skipped:
|
|
222
|
-
# Heartbeat was skipped for optimization - this is success
|
|
223
|
-
self.logger.debug(
|
|
224
|
-
f"🚀 API heartbeat #{heartbeat_count} skipped for service '{service_id}' - {skip_reason}"
|
|
225
|
-
)
|
|
226
|
-
return True
|
|
227
|
-
else:
|
|
228
|
-
self.logger.warning(
|
|
229
|
-
f"💔 [UPDATED] API heartbeat #{heartbeat_count} failed for service '{service_id}' - "
|
|
230
|
-
f"no response or unsuccessful (heartbeat_success={heartbeat_success}, heartbeat_response={heartbeat_response})"
|
|
231
|
-
)
|
|
232
|
-
return False
|
|
233
|
-
else:
|
|
234
|
-
self.logger.warning(
|
|
235
|
-
f"💔 [UPDATED-PIPELINE] API heartbeat #{heartbeat_count} pipeline failed for service '{service_id}': {result.message}"
|
|
236
|
-
)
|
|
237
|
-
|
|
238
|
-
# Log detailed errors
|
|
239
|
-
if hasattr(result, 'errors') and result.errors:
|
|
240
|
-
for error in result.errors:
|
|
241
|
-
self.logger.warning(f" - API heartbeat error: {error}")
|
|
242
|
-
|
|
243
|
-
return False
|
|
@@ -1,311 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
API heartbeat pipeline for FastAPI service health monitoring.
|
|
3
|
-
|
|
4
|
-
Provides structured execution of API service heartbeat operations with proper
|
|
5
|
-
error handling and logging. Runs periodically to maintain registry communication
|
|
6
|
-
and service health status for FastAPI applications using @mesh.route decorators.
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
import logging
|
|
10
|
-
from typing import Any
|
|
11
|
-
|
|
12
|
-
from ...shared.fast_heartbeat_status import FastHeartbeatStatus, FastHeartbeatStatusUtil
|
|
13
|
-
from ..shared.mesh_pipeline import MeshPipeline
|
|
14
|
-
from ..shared.pipeline_types import PipelineStatus
|
|
15
|
-
from .api_dependency_resolution import APIDependencyResolutionStep
|
|
16
|
-
from .api_fast_heartbeat_check import APIFastHeartbeatStep
|
|
17
|
-
from .api_health_check import APIHealthCheckStep
|
|
18
|
-
from .api_heartbeat_send import APIHeartbeatSendStep
|
|
19
|
-
from .api_registry_connection import APIRegistryConnectionStep
|
|
20
|
-
|
|
21
|
-
logger = logging.getLogger(__name__)
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
class APIHeartbeatPipeline(MeshPipeline):
|
|
25
|
-
"""
|
|
26
|
-
Specialized pipeline for API service heartbeat operations with fast optimization.
|
|
27
|
-
|
|
28
|
-
Executes the core API heartbeat steps in sequence:
|
|
29
|
-
1. Registry connection preparation
|
|
30
|
-
2. API health check (validate FastAPI app status)
|
|
31
|
-
3. Fast heartbeat check (HEAD request)
|
|
32
|
-
4. Heartbeat sending (conditional POST request)
|
|
33
|
-
5. Dependency resolution (conditional)
|
|
34
|
-
|
|
35
|
-
Steps 4 and 5 only run if fast heartbeat indicates changes are needed.
|
|
36
|
-
Provides optimization for NO_CHANGES and resilience for error conditions.
|
|
37
|
-
|
|
38
|
-
API services now support:
|
|
39
|
-
- Service availability and health status monitoring
|
|
40
|
-
- Efficient HEAD->conditional POST pattern (like MCP agents)
|
|
41
|
-
- Dynamic dependency resolution and injection
|
|
42
|
-
- Route handler dependency updates from registry responses
|
|
43
|
-
"""
|
|
44
|
-
|
|
45
|
-
def __init__(self, name: str = "api-heartbeat-pipeline"):
|
|
46
|
-
super().__init__(name=name)
|
|
47
|
-
self._setup_api_heartbeat_steps()
|
|
48
|
-
|
|
49
|
-
def _setup_api_heartbeat_steps(self) -> None:
|
|
50
|
-
"""Setup the API heartbeat pipeline steps with fast optimization."""
|
|
51
|
-
# API heartbeat steps with fast optimization pattern
|
|
52
|
-
steps = [
|
|
53
|
-
APIRegistryConnectionStep(), # Prepare registry communication
|
|
54
|
-
APIHealthCheckStep(), # Check FastAPI app health status
|
|
55
|
-
APIFastHeartbeatStep(), # Fast heartbeat check (HEAD request)
|
|
56
|
-
APIHeartbeatSendStep(), # Conditional heartbeat send (POST request)
|
|
57
|
-
APIDependencyResolutionStep(), # Conditional dependency resolution
|
|
58
|
-
]
|
|
59
|
-
|
|
60
|
-
self.add_steps(steps)
|
|
61
|
-
self.logger.trace(f"API heartbeat pipeline configured with {len(steps)} steps")
|
|
62
|
-
|
|
63
|
-
# Log the pipeline strategy
|
|
64
|
-
self.logger.info(
|
|
65
|
-
"🌐 API Heartbeat Pipeline initialized: fast optimization for FastAPI apps"
|
|
66
|
-
)
|
|
67
|
-
self.logger.trace(f"📋 Pipeline steps: {[step.name for step in steps]}")
|
|
68
|
-
|
|
69
|
-
async def execute_api_heartbeat_cycle(
|
|
70
|
-
self, heartbeat_context: dict[str, Any]
|
|
71
|
-
) -> Any:
|
|
72
|
-
"""
|
|
73
|
-
Execute a complete API heartbeat cycle with fast optimization and enhanced error handling.
|
|
74
|
-
|
|
75
|
-
Args:
|
|
76
|
-
heartbeat_context: Context containing registry_wrapper, service_id,
|
|
77
|
-
health_status, fastapi_app, etc.
|
|
78
|
-
|
|
79
|
-
Returns:
|
|
80
|
-
PipelineResult with execution status and any context updates
|
|
81
|
-
"""
|
|
82
|
-
self.logger.trace("Starting API heartbeat pipeline execution")
|
|
83
|
-
|
|
84
|
-
# Initialize pipeline context with heartbeat-specific data
|
|
85
|
-
self.context.clear()
|
|
86
|
-
self.context.update(heartbeat_context)
|
|
87
|
-
|
|
88
|
-
try:
|
|
89
|
-
# Execute the pipeline with conditional logic for fast optimization
|
|
90
|
-
result = await self._execute_with_conditional_logic()
|
|
91
|
-
|
|
92
|
-
if result.is_success():
|
|
93
|
-
self.logger.trace("✅ API heartbeat pipeline completed successfully")
|
|
94
|
-
elif result.status == PipelineStatus.PARTIAL:
|
|
95
|
-
self.logger.warning(
|
|
96
|
-
f"⚠️ API heartbeat pipeline completed partially: {result.message}"
|
|
97
|
-
)
|
|
98
|
-
# Log which steps failed
|
|
99
|
-
if result.errors:
|
|
100
|
-
for error in result.errors:
|
|
101
|
-
self.logger.warning(f" - Step error: {error}")
|
|
102
|
-
else:
|
|
103
|
-
self.logger.error(f"❌ API heartbeat pipeline failed: {result.message}")
|
|
104
|
-
# Log detailed error information
|
|
105
|
-
if result.errors:
|
|
106
|
-
for error in result.errors:
|
|
107
|
-
self.logger.error(f" - Pipeline error: {error}")
|
|
108
|
-
|
|
109
|
-
return result
|
|
110
|
-
|
|
111
|
-
except Exception as e:
|
|
112
|
-
# Log detailed error information for debugging
|
|
113
|
-
import traceback
|
|
114
|
-
|
|
115
|
-
self.logger.error(
|
|
116
|
-
f"❌ API heartbeat pipeline failed with exception: {e}\n"
|
|
117
|
-
f"Context keys: {list(self.context.keys())}\n"
|
|
118
|
-
f"Traceback: {traceback.format_exc()}"
|
|
119
|
-
)
|
|
120
|
-
|
|
121
|
-
# Create failure result with detailed context
|
|
122
|
-
from ..shared.pipeline_types import PipelineResult
|
|
123
|
-
|
|
124
|
-
failure_result = PipelineResult(
|
|
125
|
-
status=PipelineStatus.FAILED,
|
|
126
|
-
message=f"API heartbeat pipeline exception: {str(e)[:200]}...",
|
|
127
|
-
context=self.context,
|
|
128
|
-
)
|
|
129
|
-
failure_result.add_error(str(e))
|
|
130
|
-
|
|
131
|
-
return failure_result
|
|
132
|
-
|
|
133
|
-
async def _execute_with_conditional_logic(self) -> "PipelineResult":
|
|
134
|
-
"""
|
|
135
|
-
Execute API pipeline with conditional logic based on fast heartbeat status.
|
|
136
|
-
|
|
137
|
-
Always executes:
|
|
138
|
-
- APIRegistryConnectionStep
|
|
139
|
-
- APIHealthCheckStep
|
|
140
|
-
- APIFastHeartbeatStep
|
|
141
|
-
|
|
142
|
-
Conditionally executes based on fast heartbeat status:
|
|
143
|
-
- NO_CHANGES: Skip remaining steps (optimization)
|
|
144
|
-
- TOPOLOGY_CHANGED, AGENT_UNKNOWN: Execute all remaining steps
|
|
145
|
-
- REGISTRY_ERROR, NETWORK_ERROR: Skip remaining steps (resilience)
|
|
146
|
-
|
|
147
|
-
Returns:
|
|
148
|
-
PipelineResult with execution status and context
|
|
149
|
-
"""
|
|
150
|
-
from ..shared.pipeline_types import PipelineResult
|
|
151
|
-
|
|
152
|
-
overall_result = PipelineResult(
|
|
153
|
-
message="API heartbeat pipeline execution completed"
|
|
154
|
-
)
|
|
155
|
-
|
|
156
|
-
# Track which steps were executed for logging
|
|
157
|
-
executed_steps = []
|
|
158
|
-
skipped_steps = []
|
|
159
|
-
|
|
160
|
-
try:
|
|
161
|
-
# Always execute registry connection, health check, and fast heartbeat steps
|
|
162
|
-
mandatory_steps = self.steps[
|
|
163
|
-
:3
|
|
164
|
-
] # APIRegistryConnectionStep, APIHealthCheckStep, APIFastHeartbeatStep
|
|
165
|
-
conditional_steps = self.steps[
|
|
166
|
-
3:
|
|
167
|
-
] # APIHeartbeatSendStep, APIDependencyResolutionStep
|
|
168
|
-
|
|
169
|
-
# Execute mandatory steps
|
|
170
|
-
for step in mandatory_steps:
|
|
171
|
-
self.logger.trace(f"Executing mandatory step: {step.name}")
|
|
172
|
-
|
|
173
|
-
step_result = await step.execute(self.context)
|
|
174
|
-
executed_steps.append(step.name)
|
|
175
|
-
|
|
176
|
-
# Merge step context into pipeline context
|
|
177
|
-
self.context.update(step_result.context)
|
|
178
|
-
|
|
179
|
-
# If step fails, handle accordingly
|
|
180
|
-
if not step_result.is_success():
|
|
181
|
-
overall_result.status = PipelineStatus.FAILED
|
|
182
|
-
overall_result.message = (
|
|
183
|
-
f"Mandatory step '{step.name}' failed: {step_result.message}"
|
|
184
|
-
)
|
|
185
|
-
overall_result.add_error(
|
|
186
|
-
f"Step '{step.name}': {step_result.message}"
|
|
187
|
-
)
|
|
188
|
-
|
|
189
|
-
if step.required:
|
|
190
|
-
# Stop execution if required step fails
|
|
191
|
-
for key, value in self.context.items():
|
|
192
|
-
overall_result.add_context(key, value)
|
|
193
|
-
return overall_result
|
|
194
|
-
|
|
195
|
-
# Check fast heartbeat status for conditional execution
|
|
196
|
-
fast_heartbeat_status = self.context.get("fast_heartbeat_status")
|
|
197
|
-
|
|
198
|
-
if fast_heartbeat_status is None:
|
|
199
|
-
# Fast heartbeat step failed to set status - fallback to full execution
|
|
200
|
-
self.logger.warning(
|
|
201
|
-
"⚠️ API fast heartbeat status not found - falling back to full execution"
|
|
202
|
-
)
|
|
203
|
-
should_execute_remaining = True
|
|
204
|
-
reason = "fallback (missing status)"
|
|
205
|
-
elif FastHeartbeatStatusUtil.should_skip_for_optimization(
|
|
206
|
-
fast_heartbeat_status
|
|
207
|
-
):
|
|
208
|
-
# NO_CHANGES - skip for optimization
|
|
209
|
-
should_execute_remaining = False
|
|
210
|
-
reason = "optimization (no changes detected)"
|
|
211
|
-
self.logger.trace(
|
|
212
|
-
f"🚀 API heartbeat: Skipping remaining steps for optimization: {reason}"
|
|
213
|
-
)
|
|
214
|
-
elif FastHeartbeatStatusUtil.should_skip_for_resilience(
|
|
215
|
-
fast_heartbeat_status
|
|
216
|
-
):
|
|
217
|
-
# REGISTRY_ERROR, NETWORK_ERROR - skip for resilience
|
|
218
|
-
should_execute_remaining = False
|
|
219
|
-
reason = "resilience (preserve existing state)"
|
|
220
|
-
self.logger.warning(
|
|
221
|
-
f"⚠️ API heartbeat: Skipping remaining steps for resilience: {reason}"
|
|
222
|
-
)
|
|
223
|
-
elif FastHeartbeatStatusUtil.requires_full_heartbeat(fast_heartbeat_status):
|
|
224
|
-
# TOPOLOGY_CHANGED, AGENT_UNKNOWN - execute full pipeline
|
|
225
|
-
should_execute_remaining = True
|
|
226
|
-
reason = "changes detected or re-registration needed"
|
|
227
|
-
self.logger.info(
|
|
228
|
-
f"🔄 API heartbeat: Executing remaining steps: {reason}"
|
|
229
|
-
)
|
|
230
|
-
else:
|
|
231
|
-
# Unknown status - fallback to full execution
|
|
232
|
-
self.logger.warning(
|
|
233
|
-
f"⚠️ Unknown API fast heartbeat status '{fast_heartbeat_status}' - falling back to full execution"
|
|
234
|
-
)
|
|
235
|
-
should_execute_remaining = True
|
|
236
|
-
reason = "fallback (unknown status)"
|
|
237
|
-
|
|
238
|
-
# Execute or skip conditional steps based on decision
|
|
239
|
-
if should_execute_remaining:
|
|
240
|
-
for step in conditional_steps:
|
|
241
|
-
self.logger.trace(f"Executing conditional step: {step.name}")
|
|
242
|
-
|
|
243
|
-
step_result = await step.execute(self.context)
|
|
244
|
-
executed_steps.append(step.name)
|
|
245
|
-
|
|
246
|
-
# Merge step context into pipeline context
|
|
247
|
-
self.context.update(step_result.context)
|
|
248
|
-
|
|
249
|
-
# Handle step failure
|
|
250
|
-
if not step_result.is_success():
|
|
251
|
-
if step.required:
|
|
252
|
-
overall_result.status = PipelineStatus.FAILED
|
|
253
|
-
overall_result.message = f"Required step '{step.name}' failed: {step_result.message}"
|
|
254
|
-
overall_result.add_error(
|
|
255
|
-
f"Step '{step.name}': {step_result.message}"
|
|
256
|
-
)
|
|
257
|
-
break
|
|
258
|
-
else:
|
|
259
|
-
# Optional step failed - mark as partial success
|
|
260
|
-
if overall_result.status == PipelineStatus.SUCCESS:
|
|
261
|
-
overall_result.status = PipelineStatus.PARTIAL
|
|
262
|
-
overall_result.add_error(
|
|
263
|
-
f"Optional step '{step.name}': {step_result.message}"
|
|
264
|
-
)
|
|
265
|
-
self.logger.warning(
|
|
266
|
-
f"⚠️ Optional step '{step.name}' failed: {step_result.message}"
|
|
267
|
-
)
|
|
268
|
-
else:
|
|
269
|
-
# Mark skipped steps
|
|
270
|
-
for step in conditional_steps:
|
|
271
|
-
skipped_steps.append(step.name)
|
|
272
|
-
|
|
273
|
-
# For skipped heartbeat due to NO_CHANGES, set success context
|
|
274
|
-
if fast_heartbeat_status == FastHeartbeatStatus.NO_CHANGES:
|
|
275
|
-
overall_result.add_context("heartbeat_success", True)
|
|
276
|
-
overall_result.add_context("heartbeat_skipped", True)
|
|
277
|
-
overall_result.add_context("skip_reason", "no_changes_optimization")
|
|
278
|
-
|
|
279
|
-
# Set final result message
|
|
280
|
-
if executed_steps and skipped_steps:
|
|
281
|
-
overall_result.message = (
|
|
282
|
-
f"API pipeline completed with conditional execution - "
|
|
283
|
-
f"executed: {executed_steps}, skipped: {skipped_steps} ({reason})"
|
|
284
|
-
)
|
|
285
|
-
elif executed_steps:
|
|
286
|
-
overall_result.message = (
|
|
287
|
-
f"API pipeline completed - executed: {executed_steps} ({reason})"
|
|
288
|
-
)
|
|
289
|
-
else:
|
|
290
|
-
overall_result.message = (
|
|
291
|
-
f"API pipeline completed - all steps skipped ({reason})"
|
|
292
|
-
)
|
|
293
|
-
|
|
294
|
-
# Add final context
|
|
295
|
-
for key, value in self.context.items():
|
|
296
|
-
overall_result.add_context(key, value)
|
|
297
|
-
|
|
298
|
-
return overall_result
|
|
299
|
-
|
|
300
|
-
except Exception as e:
|
|
301
|
-
# Handle unexpected exceptions
|
|
302
|
-
overall_result.status = PipelineStatus.FAILED
|
|
303
|
-
overall_result.message = (
|
|
304
|
-
f"API pipeline execution failed with exception: {e}"
|
|
305
|
-
)
|
|
306
|
-
overall_result.add_error(str(e))
|
|
307
|
-
for key, value in self.context.items():
|
|
308
|
-
overall_result.add_context(key, value)
|
|
309
|
-
|
|
310
|
-
self.logger.error(f"❌ API conditional pipeline execution failed: {e}")
|
|
311
|
-
return overall_result
|