isa-model 0.3.5__py3-none-any.whl → 0.3.7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. isa_model/__init__.py +30 -1
  2. isa_model/client.py +937 -0
  3. isa_model/core/config/__init__.py +16 -0
  4. isa_model/core/config/config_manager.py +514 -0
  5. isa_model/core/config.py +426 -0
  6. isa_model/core/models/model_billing_tracker.py +476 -0
  7. isa_model/core/models/model_manager.py +399 -0
  8. isa_model/core/{storage/supabase_storage.py → models/model_repo.py} +72 -73
  9. isa_model/core/pricing_manager.py +426 -0
  10. isa_model/core/services/__init__.py +19 -0
  11. isa_model/core/services/intelligent_model_selector.py +547 -0
  12. isa_model/core/types.py +291 -0
  13. isa_model/deployment/__init__.py +2 -0
  14. isa_model/deployment/cloud/modal/isa_vision_doc_service.py +157 -3
  15. isa_model/deployment/cloud/modal/isa_vision_table_service.py +532 -0
  16. isa_model/deployment/cloud/modal/isa_vision_ui_service.py +104 -3
  17. isa_model/deployment/cloud/modal/register_models.py +321 -0
  18. isa_model/deployment/runtime/deployed_service.py +338 -0
  19. isa_model/deployment/services/__init__.py +9 -0
  20. isa_model/deployment/services/auto_deploy_vision_service.py +538 -0
  21. isa_model/deployment/services/model_service.py +332 -0
  22. isa_model/deployment/services/service_monitor.py +356 -0
  23. isa_model/deployment/services/service_registry.py +527 -0
  24. isa_model/deployment/services/simple_auto_deploy_vision_service.py +275 -0
  25. isa_model/eval/__init__.py +80 -44
  26. isa_model/eval/config/__init__.py +10 -0
  27. isa_model/eval/config/evaluation_config.py +108 -0
  28. isa_model/eval/evaluators/__init__.py +18 -0
  29. isa_model/eval/evaluators/base_evaluator.py +503 -0
  30. isa_model/eval/evaluators/llm_evaluator.py +472 -0
  31. isa_model/eval/factory.py +417 -709
  32. isa_model/eval/infrastructure/__init__.py +24 -0
  33. isa_model/eval/infrastructure/experiment_tracker.py +466 -0
  34. isa_model/eval/metrics.py +191 -21
  35. isa_model/inference/ai_factory.py +257 -601
  36. isa_model/inference/services/audio/base_stt_service.py +65 -1
  37. isa_model/inference/services/audio/base_tts_service.py +75 -1
  38. isa_model/inference/services/audio/openai_stt_service.py +189 -151
  39. isa_model/inference/services/audio/openai_tts_service.py +12 -10
  40. isa_model/inference/services/audio/replicate_tts_service.py +61 -56
  41. isa_model/inference/services/base_service.py +55 -17
  42. isa_model/inference/services/embedding/base_embed_service.py +65 -1
  43. isa_model/inference/services/embedding/ollama_embed_service.py +103 -43
  44. isa_model/inference/services/embedding/openai_embed_service.py +8 -10
  45. isa_model/inference/services/helpers/stacked_config.py +148 -0
  46. isa_model/inference/services/img/__init__.py +18 -0
  47. isa_model/inference/services/{vision → img}/base_image_gen_service.py +80 -1
  48. isa_model/inference/services/{stacked → img}/flux_professional_service.py +25 -1
  49. isa_model/inference/services/{stacked → img/helpers}/base_stacked_service.py +40 -35
  50. isa_model/inference/services/{vision → img}/replicate_image_gen_service.py +44 -31
  51. isa_model/inference/services/llm/__init__.py +3 -3
  52. isa_model/inference/services/llm/base_llm_service.py +492 -40
  53. isa_model/inference/services/llm/helpers/llm_prompts.py +258 -0
  54. isa_model/inference/services/llm/helpers/llm_utils.py +280 -0
  55. isa_model/inference/services/llm/ollama_llm_service.py +51 -17
  56. isa_model/inference/services/llm/openai_llm_service.py +70 -19
  57. isa_model/inference/services/llm/yyds_llm_service.py +24 -23
  58. isa_model/inference/services/vision/__init__.py +38 -4
  59. isa_model/inference/services/vision/base_vision_service.py +218 -117
  60. isa_model/inference/services/vision/{isA_vision_service.py → disabled/isA_vision_service.py} +98 -0
  61. isa_model/inference/services/{stacked → vision}/doc_analysis_service.py +1 -1
  62. isa_model/inference/services/vision/helpers/base_stacked_service.py +274 -0
  63. isa_model/inference/services/vision/helpers/image_utils.py +272 -3
  64. isa_model/inference/services/vision/helpers/vision_prompts.py +297 -0
  65. isa_model/inference/services/vision/openai_vision_service.py +104 -307
  66. isa_model/inference/services/vision/replicate_vision_service.py +140 -325
  67. isa_model/inference/services/{stacked → vision}/ui_analysis_service.py +2 -498
  68. isa_model/scripts/register_models.py +370 -0
  69. isa_model/scripts/register_models_with_embeddings.py +510 -0
  70. isa_model/serving/api/fastapi_server.py +6 -1
  71. isa_model/serving/api/routes/unified.py +274 -0
  72. {isa_model-0.3.5.dist-info → isa_model-0.3.7.dist-info}/METADATA +4 -1
  73. {isa_model-0.3.5.dist-info → isa_model-0.3.7.dist-info}/RECORD +78 -53
  74. isa_model/config/__init__.py +0 -9
  75. isa_model/config/config_manager.py +0 -213
  76. isa_model/core/model_manager.py +0 -213
  77. isa_model/core/model_registry.py +0 -375
  78. isa_model/core/vision_models_init.py +0 -116
  79. isa_model/inference/billing_tracker.py +0 -406
  80. isa_model/inference/services/llm/triton_llm_service.py +0 -481
  81. isa_model/inference/services/stacked/__init__.py +0 -26
  82. isa_model/inference/services/stacked/config.py +0 -426
  83. isa_model/inference/services/vision/ollama_vision_service.py +0 -194
  84. /isa_model/core/{model_storage.py → models/model_storage.py} +0 -0
  85. /isa_model/inference/services/{vision → embedding}/helpers/text_splitter.py +0 -0
  86. /isa_model/inference/services/llm/{llm_adapter.py → helpers/llm_adapter.py} +0 -0
  87. {isa_model-0.3.5.dist-info → isa_model-0.3.7.dist-info}/WHEEL +0 -0
  88. {isa_model-0.3.5.dist-info → isa_model-0.3.7.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,527 @@
1
+ """
2
+ ServiceRegistry - Enhanced registry for managing deployed model services
3
+
4
+ This registry extends the basic ModelRegistry to provide full service lifecycle management
5
+ including service discovery, health monitoring, and deployment tracking.
6
+ """
7
+
8
+ from typing import Dict, List, Optional, Any, Tuple
9
+ import logging
10
+ from datetime import datetime, timezone
11
+ import asyncio
12
+ import json
13
+
14
+ from .deployment_service import (
15
+ DeployedService, ServiceStatus, ServiceType, DeploymentPlatform,
16
+ HealthMetrics, ServiceMetrics, ResourceRequirements
17
+ )
18
+ # Backward compatibility
19
+ ModelService = DeployedService
20
+ UsageMetrics = ServiceMetrics
21
+ from .model_repo import ModelRegistry, ModelType, ModelCapability
22
+
23
+ logger = logging.getLogger(__name__)
24
+
25
+ class ServiceRegistry:
26
+ """
27
+ Enhanced registry for managing deployed model services in the MaaS platform
28
+
29
+ This registry provides:
30
+ - Service registration and discovery
31
+ - Health monitoring and status tracking
32
+ - Deployment management
33
+ - Usage metrics collection
34
+ - Integration with existing ModelRegistry
35
+ """
36
+
37
+ def __init__(self, model_registry: Optional[ModelRegistry] = None):
38
+ self.model_registry = model_registry or ModelRegistry()
39
+ self._service_cache: Dict[str, ModelService] = {}
40
+ self._last_cache_update: Optional[datetime] = None
41
+ self._cache_ttl_seconds = 300 # 5 minutes cache TTL
42
+
43
+ logger.info("ServiceRegistry initialized with Supabase backend")
44
+
45
+ # Service Registration and Management
46
+
47
+ async def register_service(self, service: DeployedService) -> bool:
48
+ """
49
+ Register a new service in the platform
50
+
51
+ Args:
52
+ service: ModelService instance to register
53
+
54
+ Returns:
55
+ True if registration successful, False otherwise
56
+ """
57
+ try:
58
+ # First ensure the underlying model is registered
59
+ if service.model_id:
60
+ await self._ensure_model_registered(service)
61
+
62
+ # Check if using Supabase backend
63
+ if hasattr(self.model_registry, 'use_supabase') and self.model_registry.use_supabase:
64
+ return await self._register_service_supabase(service)
65
+ else:
66
+ return await self._register_service_sqlite(service)
67
+
68
+ except Exception as e:
69
+ logger.error(f"Failed to register service {service.service_id}: {e}")
70
+ return False
71
+
72
+ async def _register_service_supabase(self, service: DeployedService) -> bool:
73
+ """Register service using Supabase backend"""
74
+ try:
75
+ backend = self.model_registry.backend
76
+
77
+ # Prepare service data
78
+ service_data = {
79
+ 'service_id': service.deployment_id, # Updated field name
80
+ 'service_name': service.service_name,
81
+ 'model_id': service.model_id,
82
+ 'deployment_platform': service.deployment_platform.value,
83
+ 'service_type': service.service_type.value,
84
+ 'status': service.status.value,
85
+ 'inference_endpoint': service.inference_endpoint,
86
+ 'health_endpoint': service.health_endpoint,
87
+ 'config': json.dumps(service.config),
88
+ 'gpu_type': service.resource_requirements.gpu_type,
89
+ 'memory_mb': service.resource_requirements.memory_mb,
90
+ 'cpu_cores': service.resource_requirements.cpu_cores,
91
+ 'metadata': json.dumps(service.metadata),
92
+ }
93
+
94
+ # Insert service
95
+ result = backend.supabase.table('services').upsert(service_data).execute()
96
+
97
+ if not result.data:
98
+ logger.error(f"Failed to insert service {service.service_id}")
99
+ return False
100
+
101
+ # Insert service capabilities
102
+ if service.capabilities:
103
+ capability_data = [
104
+ {
105
+ 'service_id': service.service_id,
106
+ 'capability': capability
107
+ }
108
+ for capability in service.capabilities
109
+ ]
110
+
111
+ # Delete existing capabilities first
112
+ backend.supabase.table('service_capabilities').delete().eq('service_id', service.service_id).execute()
113
+
114
+ # Insert new capabilities
115
+ cap_result = backend.supabase.table('service_capabilities').insert(capability_data).execute()
116
+
117
+ if not cap_result.data:
118
+ logger.warning(f"Failed to insert capabilities for service {service.service_id}")
119
+
120
+ # Update cache
121
+ self._service_cache[service.service_id] = service
122
+
123
+ logger.info(f"Successfully registered service {service.service_id} in Supabase")
124
+ return True
125
+
126
+ except Exception as e:
127
+ logger.error(f"Supabase service registration failed: {e}")
128
+ return False
129
+
130
+ async def _register_service_sqlite(self, service: ModelService) -> bool:
131
+ """Register service using SQLite backend (for development/testing)"""
132
+ # For SQLite, we'll store services in the models table with a special marker
133
+ try:
134
+ success = self.model_registry.register_model(
135
+ model_id=service.service_id,
136
+ model_type=ModelType.VISION, # Default type
137
+ capabilities=[ModelCapability(cap) for cap in service.capabilities if hasattr(ModelCapability, cap.upper())],
138
+ metadata={
139
+ **service.metadata,
140
+ 'is_service': True,
141
+ 'service_name': service.service_name,
142
+ 'deployment_platform': service.deployment_platform.value,
143
+ 'service_type': service.service_type.value,
144
+ 'inference_endpoint': service.inference_endpoint,
145
+ 'health_endpoint': service.health_endpoint,
146
+ 'status': service.status.value,
147
+ }
148
+ )
149
+
150
+ if success:
151
+ self._service_cache[service.service_id] = service
152
+ logger.info(f"Successfully registered service {service.service_id} in SQLite")
153
+
154
+ return success
155
+
156
+ except Exception as e:
157
+ logger.error(f"SQLite service registration failed: {e}")
158
+ return False
159
+
160
+ async def unregister_service(self, service_id: str) -> bool:
161
+ """
162
+ Unregister a service from the platform
163
+
164
+ Args:
165
+ service_id: ID of the service to unregister
166
+
167
+ Returns:
168
+ True if unregistration successful, False otherwise
169
+ """
170
+ try:
171
+ if hasattr(self.model_registry, 'use_supabase') and self.model_registry.use_supabase:
172
+ backend = self.model_registry.backend
173
+ result = backend.supabase.table('services').delete().eq('service_id', service_id).execute()
174
+ success = bool(result.data)
175
+ else:
176
+ success = self.model_registry.unregister_model(service_id)
177
+
178
+ if success and service_id in self._service_cache:
179
+ del self._service_cache[service_id]
180
+ logger.info(f"Unregistered service {service_id}")
181
+
182
+ return success
183
+
184
+ except Exception as e:
185
+ logger.error(f"Failed to unregister service {service_id}: {e}")
186
+ return False
187
+
188
+ # Service Discovery
189
+
190
+ async def get_service(self, service_id: str) -> Optional[DeployedService]:
191
+ """Get a specific service by ID"""
192
+ try:
193
+ # Check cache first
194
+ if service_id in self._service_cache:
195
+ return self._service_cache[service_id]
196
+
197
+ if hasattr(self.model_registry, 'use_supabase') and self.model_registry.use_supabase:
198
+ return await self._get_service_supabase(service_id)
199
+ else:
200
+ return await self._get_service_sqlite(service_id)
201
+
202
+ except Exception as e:
203
+ logger.error(f"Failed to get service {service_id}: {e}")
204
+ return None
205
+
206
+ async def _get_service_supabase(self, service_id: str) -> Optional[ModelService]:
207
+ """Get service from Supabase backend"""
208
+ try:
209
+ backend = self.model_registry.backend
210
+
211
+ # Get service data
212
+ result = backend.supabase.table('services').select('*').eq('service_id', service_id).execute()
213
+
214
+ if not result.data:
215
+ return None
216
+
217
+ service_data = result.data[0]
218
+
219
+ # Get service capabilities
220
+ cap_result = backend.supabase.table('service_capabilities').select('capability').eq('service_id', service_id).execute()
221
+ capabilities = [cap['capability'] for cap in cap_result.data]
222
+
223
+ # Create ModelService instance
224
+ service = self._create_service_from_data(service_data, capabilities)
225
+
226
+ # Cache the service
227
+ self._service_cache[service_id] = service
228
+
229
+ return service
230
+
231
+ except Exception as e:
232
+ logger.error(f"Failed to get service from Supabase: {e}")
233
+ return None
234
+
235
+ async def _get_service_sqlite(self, service_id: str) -> Optional[ModelService]:
236
+ """Get service from SQLite backend"""
237
+ try:
238
+ model_info = self.model_registry.get_model_info(service_id)
239
+ if not model_info or not model_info.get('metadata', {}).get('is_service'):
240
+ return None
241
+
242
+ # Convert model info to service
243
+ metadata = model_info.get('metadata', {})
244
+
245
+ # Create basic service from stored metadata
246
+ service = ModelService(
247
+ service_id=service_id,
248
+ service_name=metadata.get('service_name', service_id),
249
+ model_id=metadata.get('model_id'),
250
+ deployment_platform=DeploymentPlatform(metadata.get('deployment_platform', 'modal')),
251
+ service_type=ServiceType(metadata.get('service_type', 'vision')),
252
+ status=ServiceStatus(metadata.get('status', 'healthy')),
253
+ inference_endpoint=metadata.get('inference_endpoint'),
254
+ health_endpoint=metadata.get('health_endpoint'),
255
+ capabilities=model_info.get('capabilities', []),
256
+ metadata=metadata,
257
+ )
258
+
259
+ self._service_cache[service_id] = service
260
+ return service
261
+
262
+ except Exception as e:
263
+ logger.error(f"Failed to get service from SQLite: {e}")
264
+ return None
265
+
266
+ async def get_services_by_name(self, service_name: str) -> List[ModelService]:
267
+ """Get all services with a specific name (multiple deployments)"""
268
+ try:
269
+ if hasattr(self.model_registry, 'use_supabase') and self.model_registry.use_supabase:
270
+ backend = self.model_registry.backend
271
+ result = backend.supabase.rpc('get_healthy_services_by_name', {'name_pattern': service_name}).execute()
272
+
273
+ services = []
274
+ for row in result.data or []:
275
+ service = ModelService(
276
+ service_id=row['service_id'],
277
+ service_name=row['service_name'],
278
+ model_id=row['model_id'],
279
+ deployment_platform=DeploymentPlatform(row['deployment_platform']),
280
+ service_type=ServiceType.VISION, # Default, should be in data
281
+ inference_endpoint=row['inference_endpoint'],
282
+ health_endpoint=row['health_endpoint'],
283
+ status=ServiceStatus.HEALTHY, # From healthy services query
284
+ )
285
+ services.append(service)
286
+ self._service_cache[service.service_id] = service
287
+
288
+ return services
289
+ else:
290
+ # SQLite fallback - search in model registry
291
+ models = self.model_registry.search_models(service_name)
292
+ services = []
293
+
294
+ for model_id, model_info in models.items():
295
+ metadata = model_info.get('metadata', {})
296
+ if metadata.get('is_service') and metadata.get('service_name') == service_name:
297
+ service = await self._get_service_sqlite(model_id)
298
+ if service:
299
+ services.append(service)
300
+
301
+ return services
302
+
303
+ except Exception as e:
304
+ logger.error(f"Failed to get services by name {service_name}: {e}")
305
+ return []
306
+
307
+ async def get_services_by_capability(self, capability: str) -> List[ModelService]:
308
+ """Get all services that provide a specific capability"""
309
+ try:
310
+ if hasattr(self.model_registry, 'use_supabase') and self.model_registry.use_supabase:
311
+ backend = self.model_registry.backend
312
+ result = backend.supabase.rpc('get_services_by_capability', {'capability_name': capability}).execute()
313
+
314
+ services = []
315
+ for row in result.data or []:
316
+ service = self._create_service_from_supabase_row(row)
317
+ services.append(service)
318
+ self._service_cache[service.service_id] = service
319
+
320
+ return services
321
+ else:
322
+ # SQLite fallback
323
+ models = self.model_registry.get_models_by_capability(ModelCapability(capability))
324
+ services = []
325
+
326
+ for model_id, model_info in models.items():
327
+ if model_info.get('metadata', {}).get('is_service'):
328
+ service = await self._get_service_sqlite(model_id)
329
+ if service:
330
+ services.append(service)
331
+
332
+ return services
333
+
334
+ except Exception as e:
335
+ logger.error(f"Failed to get services by capability {capability}: {e}")
336
+ return []
337
+
338
+ async def get_active_service(self, service_name: str) -> Optional[ModelService]:
339
+ """
340
+ Get the best active service for a given service name
341
+
342
+ Returns the healthiest service with the most recent health check
343
+ """
344
+ services = await self.get_services_by_name(service_name)
345
+
346
+ if not services:
347
+ return None
348
+
349
+ # Filter to only healthy services
350
+ healthy_services = [s for s in services if s.is_healthy()]
351
+
352
+ if not healthy_services:
353
+ logger.warning(f"No healthy services found for {service_name}")
354
+ return None
355
+
356
+ # Return the first healthy service (could add more sophisticated selection logic)
357
+ return healthy_services[0]
358
+
359
+ # Health Monitoring
360
+
361
+ async def update_service_health(
362
+ self,
363
+ service_id: str,
364
+ health_metrics: HealthMetrics
365
+ ) -> bool:
366
+ """Update health metrics for a service"""
367
+ try:
368
+ if hasattr(self.model_registry, 'use_supabase') and self.model_registry.use_supabase:
369
+ backend = self.model_registry.backend
370
+
371
+ # Insert health check record
372
+ health_data = {
373
+ 'service_id': service_id,
374
+ 'is_healthy': health_metrics.is_healthy,
375
+ 'response_time_ms': health_metrics.response_time_ms,
376
+ 'status_code': health_metrics.status_code,
377
+ 'cpu_usage_percent': health_metrics.cpu_usage_percent,
378
+ 'memory_usage_mb': health_metrics.memory_usage_mb,
379
+ 'gpu_usage_percent': health_metrics.gpu_usage_percent,
380
+ 'error_message': health_metrics.error_message,
381
+ 'checked_at': health_metrics.checked_at.isoformat() if health_metrics.checked_at else datetime.now(timezone.utc).isoformat(),
382
+ }
383
+
384
+ result = backend.supabase.table('service_health').insert(health_data).execute()
385
+
386
+ # Update service status
387
+ new_status = ServiceStatus.HEALTHY if health_metrics.is_healthy else ServiceStatus.UNHEALTHY
388
+ backend.supabase.table('services').update({'status': new_status.value}).eq('service_id', service_id).execute()
389
+
390
+ # Update cached service
391
+ if service_id in self._service_cache:
392
+ self._service_cache[service_id].update_health_metrics(health_metrics)
393
+
394
+ return bool(result.data)
395
+ else:
396
+ # For SQLite, just update cached service
397
+ if service_id in self._service_cache:
398
+ self._service_cache[service_id].update_health_metrics(health_metrics)
399
+ return True
400
+
401
+ except Exception as e:
402
+ logger.error(f"Failed to update health for service {service_id}: {e}")
403
+ return False
404
+
405
+ # Statistics and Monitoring
406
+
407
+ async def get_service_statistics(self) -> Dict[str, Any]:
408
+ """Get platform-wide service statistics"""
409
+ try:
410
+ if hasattr(self.model_registry, 'use_supabase') and self.model_registry.use_supabase:
411
+ backend = self.model_registry.backend
412
+ result = backend.supabase.rpc('get_service_statistics').execute()
413
+
414
+ if result.data:
415
+ return result.data[0]
416
+
417
+ return {
418
+ "total_services": 0,
419
+ "healthy_services": 0,
420
+ "platforms": {},
421
+ "service_types": {}
422
+ }
423
+
424
+ except Exception as e:
425
+ logger.error(f"Failed to get service statistics: {e}")
426
+ return {}
427
+
428
+ # Helper Methods
429
+
430
+ def _create_service_from_data(self, service_data: Dict[str, Any], capabilities: List[str]) -> ModelService:
431
+ """Create ModelService instance from database row data"""
432
+ # Parse JSON fields
433
+ config = json.loads(service_data.get('config', '{}')) if service_data.get('config') else {}
434
+ metadata = json.loads(service_data.get('metadata', '{}')) if service_data.get('metadata') else {}
435
+
436
+ # Create resource requirements
437
+ resources = ResourceRequirements(
438
+ gpu_type=service_data.get('gpu_type'),
439
+ memory_mb=service_data.get('memory_mb'),
440
+ cpu_cores=service_data.get('cpu_cores'),
441
+ )
442
+
443
+ # Create service
444
+ service = ModelService(
445
+ service_id=service_data['service_id'],
446
+ service_name=service_data['service_name'],
447
+ model_id=service_data.get('model_id'),
448
+ deployment_platform=DeploymentPlatform(service_data['deployment_platform']),
449
+ service_type=ServiceType(service_data['service_type']),
450
+ status=ServiceStatus(service_data.get('status', 'pending')),
451
+ inference_endpoint=service_data.get('inference_endpoint'),
452
+ health_endpoint=service_data.get('health_endpoint'),
453
+ capabilities=capabilities,
454
+ config=config,
455
+ resource_requirements=resources,
456
+ metadata=metadata,
457
+ )
458
+
459
+ # Set timestamps
460
+ if service_data.get('created_at'):
461
+ service.created_at = datetime.fromisoformat(service_data['created_at'].replace('Z', '+00:00'))
462
+ if service_data.get('updated_at'):
463
+ service.updated_at = datetime.fromisoformat(service_data['updated_at'].replace('Z', '+00:00'))
464
+
465
+ return service
466
+
467
+ def _create_service_from_supabase_row(self, row: Dict[str, Any]) -> ModelService:
468
+ """Create ModelService from Supabase RPC result row"""
469
+ # Parse JSON fields safely
470
+ config = {}
471
+ metadata = {}
472
+
473
+ if row.get('config'):
474
+ try:
475
+ config = json.loads(row['config']) if isinstance(row['config'], str) else row['config']
476
+ except json.JSONDecodeError:
477
+ config = {}
478
+
479
+ if row.get('metadata'):
480
+ try:
481
+ metadata = json.loads(row['metadata']) if isinstance(row['metadata'], str) else row['metadata']
482
+ except json.JSONDecodeError:
483
+ metadata = {}
484
+
485
+ return ModelService(
486
+ service_id=row['service_id'],
487
+ service_name=row['service_name'],
488
+ model_id=row.get('model_id'),
489
+ deployment_platform=DeploymentPlatform(row['deployment_platform']),
490
+ service_type=ServiceType(row['service_type']),
491
+ status=ServiceStatus(row.get('status', 'pending')),
492
+ inference_endpoint=row.get('inference_endpoint'),
493
+ health_endpoint=row.get('health_endpoint'),
494
+ config=config,
495
+ metadata=metadata,
496
+ )
497
+
498
+ async def _ensure_model_registered(self, service: ModelService) -> None:
499
+ """Ensure the underlying model is registered in the model registry"""
500
+ if not service.model_id:
501
+ return
502
+
503
+ # Check if model exists
504
+ model_info = self.model_registry.get_model_info(service.model_id)
505
+
506
+ if not model_info:
507
+ # Register the model
508
+ model_type = ModelType.VISION # Default, could be inferred from service type
509
+ capabilities = [ModelCapability(cap) for cap in service.capabilities if hasattr(ModelCapability, cap.upper())]
510
+
511
+ self.model_registry.register_model(
512
+ model_id=service.model_id,
513
+ model_type=model_type,
514
+ capabilities=capabilities,
515
+ metadata={
516
+ "description": f"Model used by service {service.service_name}",
517
+ "registered_by_service": service.service_id,
518
+ }
519
+ )
520
+
521
+ logger.info(f"Auto-registered model {service.model_id} for service {service.service_id}")
522
+
523
+ def clear_cache(self) -> None:
524
+ """Clear the service cache"""
525
+ self._service_cache.clear()
526
+ self._last_cache_update = None
527
+ logger.info("Service cache cleared")