isa-model 0.4.0__py3-none-any.whl → 0.4.3__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 (199) hide show
  1. isa_model/client.py +466 -43
  2. isa_model/core/cache/redis_cache.py +12 -3
  3. isa_model/core/config/config_manager.py +230 -3
  4. isa_model/core/config.py +90 -0
  5. isa_model/core/database/direct_db_client.py +114 -0
  6. isa_model/core/database/migration_manager.py +563 -0
  7. isa_model/core/database/migrations.py +21 -1
  8. isa_model/core/database/supabase_client.py +154 -19
  9. isa_model/core/dependencies.py +316 -0
  10. isa_model/core/discovery/__init__.py +19 -0
  11. isa_model/core/discovery/consul_discovery.py +190 -0
  12. isa_model/core/logging/__init__.py +54 -0
  13. isa_model/core/logging/influx_logger.py +523 -0
  14. isa_model/core/logging/loki_logger.py +160 -0
  15. isa_model/core/models/__init__.py +27 -18
  16. isa_model/core/models/config_models.py +625 -0
  17. isa_model/core/models/deployment_billing_tracker.py +430 -0
  18. isa_model/core/models/model_manager.py +40 -17
  19. isa_model/core/models/model_metadata.py +690 -0
  20. isa_model/core/models/model_repo.py +174 -18
  21. isa_model/core/models/system_models.py +857 -0
  22. isa_model/core/repositories/__init__.py +9 -0
  23. isa_model/core/repositories/config_repository.py +912 -0
  24. isa_model/core/services/intelligent_model_selector.py +399 -21
  25. isa_model/core/storage/hf_storage.py +1 -1
  26. isa_model/core/types.py +1 -0
  27. isa_model/deployment/__init__.py +5 -48
  28. isa_model/deployment/core/__init__.py +2 -31
  29. isa_model/deployment/core/deployment_manager.py +1278 -370
  30. isa_model/deployment/local/__init__.py +31 -0
  31. isa_model/deployment/local/config.py +248 -0
  32. isa_model/deployment/local/gpu_gateway.py +607 -0
  33. isa_model/deployment/local/health_checker.py +428 -0
  34. isa_model/deployment/local/provider.py +586 -0
  35. isa_model/deployment/local/tensorrt_service.py +621 -0
  36. isa_model/deployment/local/transformers_service.py +644 -0
  37. isa_model/deployment/local/vllm_service.py +527 -0
  38. isa_model/deployment/modal/__init__.py +8 -0
  39. isa_model/deployment/modal/config.py +136 -0
  40. isa_model/deployment/{services/auto_hf_modal_deployer.py → modal/deployer.py} +1 -1
  41. isa_model/deployment/modal/services/__init__.py +3 -0
  42. isa_model/deployment/modal/services/audio/__init__.py +1 -0
  43. isa_model/deployment/modal/services/embedding/__init__.py +1 -0
  44. isa_model/deployment/modal/services/llm/__init__.py +1 -0
  45. isa_model/deployment/modal/services/llm/isa_llm_service.py +424 -0
  46. isa_model/deployment/modal/services/video/__init__.py +1 -0
  47. isa_model/deployment/modal/services/vision/__init__.py +1 -0
  48. isa_model/deployment/models/org-org-acme-corp-tenant-a-service-llm-20250825-225822/tenant-a-service_modal_service.py +48 -0
  49. isa_model/deployment/models/org-test-org-123-prefix-test-service-llm-20250825-225822/prefix-test-service_modal_service.py +48 -0
  50. isa_model/deployment/models/test-llm-service-llm-20250825-204442/test-llm-service_modal_service.py +48 -0
  51. isa_model/deployment/models/test-monitoring-gpt2-llm-20250825-212906/test-monitoring-gpt2_modal_service.py +48 -0
  52. isa_model/deployment/models/test-monitoring-gpt2-llm-20250825-213009/test-monitoring-gpt2_modal_service.py +48 -0
  53. isa_model/deployment/storage/__init__.py +5 -0
  54. isa_model/deployment/storage/deployment_repository.py +824 -0
  55. isa_model/deployment/triton/__init__.py +10 -0
  56. isa_model/deployment/triton/config.py +196 -0
  57. isa_model/deployment/triton/configs/__init__.py +1 -0
  58. isa_model/deployment/triton/provider.py +512 -0
  59. isa_model/deployment/triton/scripts/__init__.py +1 -0
  60. isa_model/deployment/triton/templates/__init__.py +1 -0
  61. isa_model/inference/__init__.py +47 -1
  62. isa_model/inference/ai_factory.py +137 -10
  63. isa_model/inference/legacy_services/__init__.py +21 -0
  64. isa_model/inference/legacy_services/model_evaluation.py +637 -0
  65. isa_model/inference/legacy_services/model_service.py +573 -0
  66. isa_model/inference/legacy_services/model_serving.py +717 -0
  67. isa_model/inference/legacy_services/model_training.py +561 -0
  68. isa_model/inference/models/__init__.py +21 -0
  69. isa_model/inference/models/inference_config.py +551 -0
  70. isa_model/inference/models/inference_record.py +675 -0
  71. isa_model/inference/models/performance_models.py +714 -0
  72. isa_model/inference/repositories/__init__.py +9 -0
  73. isa_model/inference/repositories/inference_repository.py +828 -0
  74. isa_model/inference/services/audio/base_stt_service.py +184 -11
  75. isa_model/inference/services/audio/openai_stt_service.py +22 -6
  76. isa_model/inference/services/custom_model_manager.py +277 -0
  77. isa_model/inference/services/embedding/ollama_embed_service.py +15 -3
  78. isa_model/inference/services/embedding/resilient_embed_service.py +285 -0
  79. isa_model/inference/services/llm/__init__.py +10 -2
  80. isa_model/inference/services/llm/base_llm_service.py +335 -24
  81. isa_model/inference/services/llm/cerebras_llm_service.py +628 -0
  82. isa_model/inference/services/llm/helpers/llm_adapter.py +9 -4
  83. isa_model/inference/services/llm/helpers/llm_prompts.py +342 -0
  84. isa_model/inference/services/llm/helpers/llm_utils.py +321 -23
  85. isa_model/inference/services/llm/huggingface_llm_service.py +581 -0
  86. isa_model/inference/services/llm/local_llm_service.py +747 -0
  87. isa_model/inference/services/llm/ollama_llm_service.py +9 -2
  88. isa_model/inference/services/llm/openai_llm_service.py +33 -16
  89. isa_model/inference/services/llm/yyds_llm_service.py +8 -2
  90. isa_model/inference/services/vision/__init__.py +22 -1
  91. isa_model/inference/services/vision/blip_vision_service.py +359 -0
  92. isa_model/inference/services/vision/helpers/image_utils.py +8 -5
  93. isa_model/inference/services/vision/isa_vision_service.py +65 -4
  94. isa_model/inference/services/vision/openai_vision_service.py +19 -10
  95. isa_model/inference/services/vision/vgg16_vision_service.py +257 -0
  96. isa_model/serving/api/cache_manager.py +245 -0
  97. isa_model/serving/api/dependencies/__init__.py +1 -0
  98. isa_model/serving/api/dependencies/auth.py +194 -0
  99. isa_model/serving/api/dependencies/database.py +139 -0
  100. isa_model/serving/api/error_handlers.py +284 -0
  101. isa_model/serving/api/fastapi_server.py +172 -22
  102. isa_model/serving/api/middleware/auth.py +8 -2
  103. isa_model/serving/api/middleware/security.py +23 -33
  104. isa_model/serving/api/middleware/tenant_context.py +414 -0
  105. isa_model/serving/api/routes/analytics.py +4 -1
  106. isa_model/serving/api/routes/config.py +645 -0
  107. isa_model/serving/api/routes/deployment_billing.py +315 -0
  108. isa_model/serving/api/routes/deployments.py +138 -2
  109. isa_model/serving/api/routes/gpu_gateway.py +440 -0
  110. isa_model/serving/api/routes/health.py +32 -12
  111. isa_model/serving/api/routes/inference_monitoring.py +486 -0
  112. isa_model/serving/api/routes/local_deployments.py +448 -0
  113. isa_model/serving/api/routes/tenants.py +575 -0
  114. isa_model/serving/api/routes/unified.py +680 -18
  115. isa_model/serving/api/routes/webhooks.py +479 -0
  116. isa_model/serving/api/startup.py +68 -54
  117. isa_model/utils/gpu_utils.py +311 -0
  118. {isa_model-0.4.0.dist-info → isa_model-0.4.3.dist-info}/METADATA +66 -24
  119. isa_model-0.4.3.dist-info/RECORD +193 -0
  120. isa_model/core/storage/minio_storage.py +0 -0
  121. isa_model/deployment/cloud/__init__.py +0 -9
  122. isa_model/deployment/cloud/modal/__init__.py +0 -10
  123. isa_model/deployment/core/deployment_config.py +0 -356
  124. isa_model/deployment/core/isa_deployment_service.py +0 -401
  125. isa_model/deployment/gpu_int8_ds8/app/server.py +0 -66
  126. isa_model/deployment/gpu_int8_ds8/scripts/test_client.py +0 -43
  127. isa_model/deployment/gpu_int8_ds8/scripts/test_client_os.py +0 -35
  128. isa_model/deployment/runtime/deployed_service.py +0 -338
  129. isa_model/deployment/services/__init__.py +0 -9
  130. isa_model/deployment/services/auto_deploy_vision_service.py +0 -538
  131. isa_model/deployment/services/model_service.py +0 -332
  132. isa_model/deployment/services/service_monitor.py +0 -356
  133. isa_model/deployment/services/service_registry.py +0 -527
  134. isa_model/eval/__init__.py +0 -92
  135. isa_model/eval/benchmarks/__init__.py +0 -27
  136. isa_model/eval/benchmarks/multimodal_datasets.py +0 -460
  137. isa_model/eval/benchmarks.py +0 -701
  138. isa_model/eval/config/__init__.py +0 -10
  139. isa_model/eval/config/evaluation_config.py +0 -108
  140. isa_model/eval/evaluators/__init__.py +0 -24
  141. isa_model/eval/evaluators/audio_evaluator.py +0 -727
  142. isa_model/eval/evaluators/base_evaluator.py +0 -503
  143. isa_model/eval/evaluators/embedding_evaluator.py +0 -742
  144. isa_model/eval/evaluators/llm_evaluator.py +0 -472
  145. isa_model/eval/evaluators/vision_evaluator.py +0 -564
  146. isa_model/eval/example_evaluation.py +0 -395
  147. isa_model/eval/factory.py +0 -798
  148. isa_model/eval/infrastructure/__init__.py +0 -24
  149. isa_model/eval/infrastructure/experiment_tracker.py +0 -466
  150. isa_model/eval/isa_benchmarks.py +0 -700
  151. isa_model/eval/isa_integration.py +0 -582
  152. isa_model/eval/metrics.py +0 -951
  153. isa_model/eval/tests/unit/test_basic.py +0 -396
  154. isa_model/serving/api/routes/evaluations.py +0 -579
  155. isa_model/training/__init__.py +0 -168
  156. isa_model/training/annotation/annotation_schema.py +0 -47
  157. isa_model/training/annotation/processors/annotation_processor.py +0 -126
  158. isa_model/training/annotation/storage/dataset_manager.py +0 -131
  159. isa_model/training/annotation/storage/dataset_schema.py +0 -44
  160. isa_model/training/annotation/tests/test_annotation_flow.py +0 -109
  161. isa_model/training/annotation/tests/test_minio copy.py +0 -113
  162. isa_model/training/annotation/tests/test_minio_upload.py +0 -43
  163. isa_model/training/annotation/views/annotation_controller.py +0 -158
  164. isa_model/training/cloud/__init__.py +0 -22
  165. isa_model/training/cloud/job_orchestrator.py +0 -402
  166. isa_model/training/cloud/runpod_trainer.py +0 -454
  167. isa_model/training/cloud/storage_manager.py +0 -482
  168. isa_model/training/core/__init__.py +0 -26
  169. isa_model/training/core/config.py +0 -181
  170. isa_model/training/core/dataset.py +0 -222
  171. isa_model/training/core/trainer.py +0 -720
  172. isa_model/training/core/utils.py +0 -213
  173. isa_model/training/examples/intelligent_training_example.py +0 -281
  174. isa_model/training/factory.py +0 -424
  175. isa_model/training/intelligent/__init__.py +0 -25
  176. isa_model/training/intelligent/decision_engine.py +0 -643
  177. isa_model/training/intelligent/intelligent_factory.py +0 -888
  178. isa_model/training/intelligent/knowledge_base.py +0 -751
  179. isa_model/training/intelligent/resource_optimizer.py +0 -839
  180. isa_model/training/intelligent/task_classifier.py +0 -576
  181. isa_model/training/storage/__init__.py +0 -24
  182. isa_model/training/storage/core_integration.py +0 -439
  183. isa_model/training/storage/training_repository.py +0 -552
  184. isa_model/training/storage/training_storage.py +0 -628
  185. isa_model-0.4.0.dist-info/RECORD +0 -182
  186. /isa_model/deployment/{cloud/modal → modal/services/audio}/isa_audio_chatTTS_service.py +0 -0
  187. /isa_model/deployment/{cloud/modal → modal/services/audio}/isa_audio_fish_service.py +0 -0
  188. /isa_model/deployment/{cloud/modal → modal/services/audio}/isa_audio_openvoice_service.py +0 -0
  189. /isa_model/deployment/{cloud/modal → modal/services/audio}/isa_audio_service_v2.py +0 -0
  190. /isa_model/deployment/{cloud/modal → modal/services/embedding}/isa_embed_rerank_service.py +0 -0
  191. /isa_model/deployment/{cloud/modal → modal/services/video}/isa_video_hunyuan_service.py +0 -0
  192. /isa_model/deployment/{cloud/modal → modal/services/vision}/isa_vision_ocr_service.py +0 -0
  193. /isa_model/deployment/{cloud/modal → modal/services/vision}/isa_vision_qwen25_service.py +0 -0
  194. /isa_model/deployment/{cloud/modal → modal/services/vision}/isa_vision_table_service.py +0 -0
  195. /isa_model/deployment/{cloud/modal → modal/services/vision}/isa_vision_ui_service.py +0 -0
  196. /isa_model/deployment/{cloud/modal → modal/services/vision}/isa_vision_ui_service_optimized.py +0 -0
  197. /isa_model/deployment/{services → modal/services/vision}/simple_auto_deploy_vision_service.py +0 -0
  198. {isa_model-0.4.0.dist-info → isa_model-0.4.3.dist-info}/WHEEL +0 -0
  199. {isa_model-0.4.0.dist-info → isa_model-0.4.3.dist-info}/top_level.txt +0 -0
@@ -9,6 +9,7 @@ import logging
9
9
  import base64
10
10
  import io
11
11
  import time
12
+ import asyncio
12
13
  from typing import Dict, Any, List, Union, Optional, BinaryIO
13
14
  from PIL import Image
14
15
 
@@ -36,7 +37,7 @@ class ISAVisionService(BaseVisionService):
36
37
  def __init__(self,
37
38
  modal_app_id: str = "ap-VlHUQoiPUdy9cgrHSfG7Fk",
38
39
  modal_app_name: str = "isa-vision-ui-optimized",
39
- timeout: int = 30):
40
+ timeout: int = 60):
40
41
  """
41
42
  初始化ISA Vision服务
42
43
 
@@ -77,6 +78,31 @@ class ISAVisionService(BaseVisionService):
77
78
  self.request_count = 0
78
79
  self.total_cost = 0.0
79
80
 
81
+ # 性能优化 - 预热连接(延迟初始化)
82
+ self._connection_warmed = False
83
+
84
+ # 简单缓存机制(可选)
85
+ self._result_cache = {}
86
+ self._cache_max_size = 100
87
+
88
+ async def _warm_connection(self):
89
+ """预热Modal连接,减少首次调用延迟"""
90
+ if self._connection_warmed or not self.modal_app:
91
+ return
92
+
93
+ try:
94
+ logger.info("Warming up Modal connection...")
95
+ # 尝试获取服务状态来预热连接
96
+ if hasattr(self.modal_app, 'list_functions'):
97
+ await asyncio.wait_for(
98
+ asyncio.to_thread(self.modal_app.list_functions),
99
+ timeout=10
100
+ )
101
+ self._connection_warmed = True
102
+ logger.info("✅ Modal connection warmed up")
103
+ except Exception as e:
104
+ logger.warning(f"Failed to warm up connection: {e}")
105
+
80
106
  async def analyze_image(
81
107
  self,
82
108
  image: Union[str, BinaryIO],
@@ -154,6 +180,9 @@ class ISAVisionService(BaseVisionService):
154
180
  'error': 'Modal app or service not available'
155
181
  }
156
182
 
183
+ # 预热连接以减少延迟
184
+ await self._warm_connection()
185
+
157
186
  # 准备图像数据
158
187
  image_b64 = await self._prepare_image_base64(image)
159
188
 
@@ -208,11 +237,22 @@ class ISAVisionService(BaseVisionService):
208
237
 
209
238
  # 创建实例并调用优化方法(快速模式,无字幕)
210
239
  instance = OptimizedUIDetectionService()
211
- result = instance.detect_ui_elements_fast.remote(image_b64, enable_captions=False)
240
+ # 使用超时控制Modal调用
241
+ result = await asyncio.wait_for(
242
+ instance.detect_ui_elements_fast.remote(image_b64, enable_captions=False),
243
+ timeout=self.timeout
244
+ )
212
245
 
213
246
  logger.info("✅ Modal SDK call successful")
214
247
  return result
215
248
 
249
+ except asyncio.TimeoutError:
250
+ logger.error(f"Modal SDK call timed out after {self.timeout} seconds")
251
+ return {
252
+ 'success': False,
253
+ 'error': f'Modal service timeout after {self.timeout} seconds',
254
+ 'timeout': True
255
+ }
216
256
  except Exception as e:
217
257
  logger.error(f"Modal SDK call failed: {e}")
218
258
  return {
@@ -316,11 +356,22 @@ class ISAVisionService(BaseVisionService):
316
356
 
317
357
  # 创建实例并调用方法
318
358
  instance = SuryaOCRService()
319
- result = instance.extract_text.remote(image_b64, languages)
359
+ # 使用超时控制OCR调用
360
+ result = await asyncio.wait_for(
361
+ instance.extract_text.remote(image_b64, languages),
362
+ timeout=self.timeout
363
+ )
320
364
 
321
365
  logger.info("✅ OCR service call successful")
322
366
  return result
323
367
 
368
+ except asyncio.TimeoutError:
369
+ logger.error(f"OCR service call timed out after {self.timeout} seconds")
370
+ return {
371
+ 'success': False,
372
+ 'error': f'OCR service timeout after {self.timeout} seconds',
373
+ 'timeout': True
374
+ }
324
375
  except Exception as e:
325
376
  logger.error(f"OCR service call failed: {e}")
326
377
  return {
@@ -499,7 +550,7 @@ class ISAVisionService(BaseVisionService):
499
550
  """准备base64编码的图像"""
500
551
  if isinstance(image, str):
501
552
  # Check if it's already base64 encoded
502
- if image.startswith('data:image') or len(image) > 1000:
553
+ if image.startswith('data:image') or (not image.startswith('http') and len(image) > 1000):
503
554
  # Likely already base64
504
555
  if image.startswith('data:image'):
505
556
  # Extract base64 part
@@ -507,6 +558,16 @@ class ISAVisionService(BaseVisionService):
507
558
  else:
508
559
  # Assume it's pure base64
509
560
  return image
561
+ elif image.startswith('http://') or image.startswith('https://'):
562
+ # URL - download the image
563
+ import aiohttp
564
+ async with aiohttp.ClientSession() as session:
565
+ async with session.get(image) as response:
566
+ if response.status == 200:
567
+ image_data = await response.read()
568
+ return base64.b64encode(image_data).decode('utf-8')
569
+ else:
570
+ raise ValueError(f"Failed to download image from URL: {response.status}")
510
571
  else:
511
572
  # File path
512
573
  with open(image, 'rb') as f:
@@ -92,12 +92,21 @@ class OpenAIVisionService(BaseVisionService, VisionPromptMixin):
92
92
  }
93
93
  ]
94
94
 
95
- response = await self._client.chat.completions.create( # type: ignore
96
- model=self.model_name,
97
- messages=messages, # type: ignore
98
- max_tokens=max_tokens,
99
- temperature=self.temperature
100
- )
95
+ # Use max_completion_tokens for newer models like gpt-4o-mini
96
+ completion_params = {
97
+ "model": self.model_name,
98
+ "messages": messages, # type: ignore
99
+ "temperature": self.temperature
100
+ }
101
+
102
+ # Check if model uses new parameter name
103
+ # All newer models (gpt-4o, gpt-4.1, o1, etc.) use max_completion_tokens
104
+ if any(prefix in self.model_name for prefix in ["gpt-4o", "gpt-4.1", "o1"]):
105
+ completion_params["max_completion_tokens"] = max_tokens
106
+ else:
107
+ completion_params["max_tokens"] = max_tokens
108
+
109
+ response = await self._client.chat.completions.create(**completion_params) # type: ignore
101
110
 
102
111
  # Track usage for billing
103
112
  if response.usage:
@@ -162,7 +171,7 @@ class OpenAIVisionService(BaseVisionService, VisionPromptMixin):
162
171
  图像描述 - 使用专门提示词
163
172
  """
164
173
  prompt = self.get_task_prompt("describe", detail_level=detail_level)
165
- return await self.analyze_image(image, prompt)
174
+ return await self.analyze_image(image, prompt, max_tokens=1000)
166
175
 
167
176
  async def extract_text(self, image: Union[str, BinaryIO]) -> Dict[str, Any]:
168
177
  """
@@ -170,7 +179,7 @@ class OpenAIVisionService(BaseVisionService, VisionPromptMixin):
170
179
  """
171
180
  prompt = self.get_task_prompt("extract_text")
172
181
 
173
- return await self.analyze_image(image, prompt)
182
+ return await self.analyze_image(image, prompt, max_tokens=1000)
174
183
 
175
184
  async def detect_objects(
176
185
  self,
@@ -182,7 +191,7 @@ class OpenAIVisionService(BaseVisionService, VisionPromptMixin):
182
191
  """
183
192
  prompt = self.get_task_prompt("detect_objects", confidence_threshold=confidence_threshold)
184
193
 
185
- return await self.analyze_image(image, prompt)
194
+ return await self.analyze_image(image, prompt, max_tokens=1000)
186
195
 
187
196
  async def detect_ui_elements(
188
197
  self,
@@ -195,7 +204,7 @@ class OpenAIVisionService(BaseVisionService, VisionPromptMixin):
195
204
  """
196
205
  prompt = self.get_task_prompt("detect_ui_elements", element_types=element_types, confidence_threshold=confidence_threshold)
197
206
 
198
- return await self.analyze_image(image, prompt)
207
+ return await self.analyze_image(image, prompt, max_tokens=1000)
199
208
 
200
209
  async def detect_document_elements(
201
210
  self,
@@ -0,0 +1,257 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ VGG16 Vision Service
4
+ Computer vision service using VGG16 for image classification
5
+ Based on the aircraft damage detection notebook implementation
6
+ """
7
+
8
+ import os
9
+ import numpy as np
10
+ from typing import Dict, List, Any, Optional, Union, BinaryIO
11
+ import logging
12
+ from PIL import Image
13
+ import io
14
+
15
+ from .base_vision_service import BaseVisionService
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+ def _lazy_import_vgg16_deps():
20
+ """Lazy import VGG16 dependencies"""
21
+ try:
22
+ import tensorflow as tf
23
+ from tensorflow.keras.applications import VGG16
24
+ from tensorflow.keras.layers import Dense, Dropout, Flatten
25
+ from tensorflow.keras.models import Sequential, Model
26
+ from tensorflow.keras.optimizers import Adam
27
+ from tensorflow.keras.preprocessing.image import ImageDataGenerator
28
+
29
+ return {
30
+ 'tf': tf,
31
+ 'VGG16': VGG16,
32
+ 'Dense': Dense,
33
+ 'Dropout': Dropout,
34
+ 'Flatten': Flatten,
35
+ 'Sequential': Sequential,
36
+ 'Model': Model,
37
+ 'Adam': Adam,
38
+ 'ImageDataGenerator': ImageDataGenerator,
39
+ 'available': True
40
+ }
41
+ except ImportError as e:
42
+ logger.warning(f"VGG16 dependencies not available: {e}")
43
+ return {'available': False}
44
+
45
+ class VGG16VisionService(BaseVisionService):
46
+ """
47
+ VGG16-based vision service for image classification
48
+ Provides an alternative implementation to VLM-based classification
49
+ """
50
+
51
+ def __init__(self, model_path: Optional[str] = None, class_names: Optional[List[str]] = None):
52
+ """
53
+ Initialize VGG16 vision service
54
+
55
+ Args:
56
+ model_path: Path to trained VGG16 model
57
+ class_names: List of class names for classification
58
+ """
59
+ super().__init__()
60
+
61
+ self.model_path = model_path
62
+ self.class_names = class_names or ["class_0", "class_1"]
63
+ self.model = None
64
+ self.input_shape = (224, 224, 3)
65
+
66
+ # Lazy load dependencies
67
+ self.vgg16_components = _lazy_import_vgg16_deps()
68
+
69
+ if not self.vgg16_components['available']:
70
+ raise ImportError("TensorFlow and VGG16 dependencies are required")
71
+
72
+ # Load model if path provided
73
+ if model_path and os.path.exists(model_path):
74
+ self._load_model(model_path)
75
+
76
+ def _load_model(self, model_path: str):
77
+ """Load trained VGG16 model"""
78
+ try:
79
+ tf = self.vgg16_components['tf']
80
+ self.model = tf.keras.models.load_model(model_path)
81
+ logger.info(f"VGG16 model loaded from {model_path}")
82
+ except Exception as e:
83
+ logger.error(f"Error loading VGG16 model: {e}")
84
+ raise
85
+
86
+ def _preprocess_image(self, image: Union[str, BinaryIO]) -> np.ndarray:
87
+ """
88
+ Preprocess image for VGG16 input
89
+
90
+ Args:
91
+ image: Image path or binary data
92
+
93
+ Returns:
94
+ Preprocessed image array
95
+ """
96
+ try:
97
+ # Handle different image input types
98
+ if isinstance(image, str):
99
+ # File path
100
+ pil_image = Image.open(image).convert('RGB')
101
+ elif hasattr(image, 'read'):
102
+ # Binary IO
103
+ image_data = image.read()
104
+ pil_image = Image.open(io.BytesIO(image_data)).convert('RGB')
105
+ else:
106
+ raise ValueError("Unsupported image format")
107
+
108
+ # Resize to VGG16 input size
109
+ pil_image = pil_image.resize((self.input_shape[0], self.input_shape[1]))
110
+
111
+ # Convert to array and normalize
112
+ image_array = np.array(pil_image) / 255.0
113
+
114
+ # Add batch dimension
115
+ image_batch = np.expand_dims(image_array, axis=0)
116
+
117
+ return image_batch, image_array
118
+
119
+ except Exception as e:
120
+ logger.error(f"Error preprocessing image: {e}")
121
+ raise
122
+
123
+ async def classify_image(self,
124
+ image: Union[str, BinaryIO],
125
+ categories: Optional[List[str]] = None) -> Dict[str, Any]:
126
+ """
127
+ Classify image using trained VGG16 model
128
+
129
+ Args:
130
+ image: Image path or binary data
131
+ categories: Optional list of categories (uses model's classes if None)
132
+
133
+ Returns:
134
+ Classification results
135
+ """
136
+ try:
137
+ if self.model is None:
138
+ return {
139
+ "error": "No trained model available. Please load a model first.",
140
+ "service": "VGG16VisionService"
141
+ }
142
+
143
+ # Preprocess image
144
+ image_batch, image_array = self._preprocess_image(image)
145
+
146
+ # Make prediction
147
+ predictions = self.model.predict(image_batch, verbose=0)
148
+
149
+ # Use provided categories or default class names
150
+ class_names = categories or self.class_names
151
+
152
+ # Process predictions based on model output
153
+ if len(predictions[0]) == 1: # Binary classification
154
+ predicted_class_idx = int(predictions[0] > 0.5)
155
+ confidence = float(predictions[0][0]) if predicted_class_idx == 1 else float(1 - predictions[0][0])
156
+
157
+ # Create probability distribution
158
+ probabilities = {
159
+ class_names[0]: float(1 - predictions[0][0]),
160
+ class_names[1]: float(predictions[0][0])
161
+ }
162
+ else: # Multiclass classification
163
+ predicted_class_idx = np.argmax(predictions[0])
164
+ confidence = float(predictions[0][predicted_class_idx])
165
+
166
+ # Create probability distribution
167
+ probabilities = {
168
+ class_names[i]: float(predictions[0][i])
169
+ for i in range(min(len(class_names), len(predictions[0])))
170
+ }
171
+
172
+ predicted_class = class_names[predicted_class_idx] if predicted_class_idx < len(class_names) else f"class_{predicted_class_idx}"
173
+
174
+ return {
175
+ "task": "classify",
176
+ "service": "VGG16VisionService",
177
+ "predicted_class": predicted_class,
178
+ "confidence": confidence,
179
+ "probabilities": probabilities,
180
+ "model_type": "VGG16",
181
+ "success": True
182
+ }
183
+
184
+ except Exception as e:
185
+ logger.error(f"Error classifying image: {e}")
186
+ return {
187
+ "error": str(e),
188
+ "service": "VGG16VisionService",
189
+ "success": False
190
+ }
191
+
192
+ async def analyze_image(self,
193
+ image: Union[str, BinaryIO],
194
+ prompt: Optional[str] = None,
195
+ max_tokens: int = 1000) -> Dict[str, Any]:
196
+ """
197
+ Analyze image using VGG16 classification
198
+
199
+ Args:
200
+ image: Image path or binary data
201
+ prompt: Optional prompt (used to guide interpretation)
202
+ max_tokens: Not used for classification
203
+
204
+ Returns:
205
+ Analysis results
206
+ """
207
+ # For VGG16, analysis is essentially classification
208
+ classification_result = await self.classify_image(image)
209
+
210
+ if classification_result.get("success"):
211
+ # Create analysis text based on classification
212
+ predicted_class = classification_result["predicted_class"]
213
+ confidence = classification_result["confidence"]
214
+
215
+ analysis_text = f"The image has been classified as '{predicted_class}' with {confidence:.2%} confidence."
216
+
217
+ if prompt:
218
+ analysis_text += f" Analysis context: {prompt}"
219
+
220
+ return {
221
+ "task": "analyze",
222
+ "service": "VGG16VisionService",
223
+ "text": analysis_text,
224
+ "confidence": confidence,
225
+ "classification": classification_result,
226
+ "success": True
227
+ }
228
+ else:
229
+ return classification_result
230
+
231
+ def set_class_names(self, class_names: List[str]):
232
+ """Set class names for classification"""
233
+ self.class_names = class_names
234
+
235
+ def load_trained_model(self, model_path: str, class_names: Optional[List[str]] = None):
236
+ """
237
+ Load a trained VGG16 model
238
+
239
+ Args:
240
+ model_path: Path to the trained model
241
+ class_names: Optional class names
242
+ """
243
+ self._load_model(model_path)
244
+ if class_names:
245
+ self.set_class_names(class_names)
246
+
247
+ def get_service_info(self) -> Dict[str, Any]:
248
+ """Get service information"""
249
+ return {
250
+ "service_name": "VGG16VisionService",
251
+ "model_type": "VGG16",
252
+ "capabilities": ["classify", "analyze"],
253
+ "model_loaded": self.model is not None,
254
+ "input_shape": self.input_shape,
255
+ "class_names": self.class_names,
256
+ "dependencies_available": self.vgg16_components['available']
257
+ }
@@ -0,0 +1,245 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """
5
+ Simple Cache Manager for ISA Model API
6
+ Provides in-memory caching to improve API performance
7
+ """
8
+
9
+ import time
10
+ import logging
11
+ from typing import Dict, Any, Optional, Callable
12
+ from dataclasses import dataclass
13
+ from threading import RLock
14
+ import asyncio
15
+ import hashlib
16
+ import json
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+ @dataclass
21
+ class CacheEntry:
22
+ """Cache entry with data and metadata"""
23
+ data: Any
24
+ created_at: float
25
+ ttl: float
26
+ access_count: int = 0
27
+ last_accessed: float = None
28
+
29
+ def is_expired(self) -> bool:
30
+ """Check if cache entry is expired"""
31
+ return time.time() - self.created_at > self.ttl
32
+
33
+ def access(self) -> Any:
34
+ """Mark as accessed and return data"""
35
+ self.access_count += 1
36
+ self.last_accessed = time.time()
37
+ return self.data
38
+
39
+ class APICache:
40
+ """
41
+ Simple in-memory cache for API responses
42
+ Thread-safe with automatic expiration
43
+ """
44
+
45
+ def __init__(self, default_ttl: float = 300.0, max_size: int = 1000):
46
+ self.default_ttl = default_ttl # 5 minutes default
47
+ self.max_size = max_size
48
+ self._cache: Dict[str, CacheEntry] = {}
49
+ self._lock = RLock()
50
+ self._stats = {
51
+ "hits": 0,
52
+ "misses": 0,
53
+ "evictions": 0,
54
+ "total_requests": 0
55
+ }
56
+
57
+ def _generate_key(self, *args, **kwargs) -> str:
58
+ """Generate cache key from arguments"""
59
+ # Create a stable key from arguments
60
+ key_data = {
61
+ "args": args,
62
+ "kwargs": sorted(kwargs.items()) if kwargs else {}
63
+ }
64
+ key_string = json.dumps(key_data, sort_keys=True, default=str)
65
+ return hashlib.md5(key_string.encode()).hexdigest()
66
+
67
+ def _cleanup_expired(self):
68
+ """Remove expired entries"""
69
+ with self._lock:
70
+ current_time = time.time()
71
+ expired_keys = [
72
+ key for key, entry in self._cache.items()
73
+ if entry.is_expired()
74
+ ]
75
+
76
+ for key in expired_keys:
77
+ del self._cache[key]
78
+
79
+ if expired_keys:
80
+ logger.debug(f"Cleaned up {len(expired_keys)} expired cache entries")
81
+
82
+ def _evict_lru(self):
83
+ """Evict least recently used entries when cache is full"""
84
+ with self._lock:
85
+ if len(self._cache) >= self.max_size:
86
+ # Sort by last_accessed time (LRU)
87
+ sorted_entries = sorted(
88
+ self._cache.items(),
89
+ key=lambda x: x[1].last_accessed or x[1].created_at
90
+ )
91
+
92
+ # Remove oldest 20% of entries
93
+ num_to_remove = max(1, len(sorted_entries) // 5)
94
+ for key, _ in sorted_entries[:num_to_remove]:
95
+ del self._cache[key]
96
+ self._stats["evictions"] += 1
97
+
98
+ logger.debug(f"Evicted {num_to_remove} LRU cache entries")
99
+
100
+ def get(self, key: str) -> Optional[Any]:
101
+ """Get cached value by key"""
102
+ with self._lock:
103
+ self._stats["total_requests"] += 1
104
+
105
+ if key in self._cache:
106
+ entry = self._cache[key]
107
+ if not entry.is_expired():
108
+ self._stats["hits"] += 1
109
+ return entry.access()
110
+ else:
111
+ # Remove expired entry
112
+ del self._cache[key]
113
+
114
+ self._stats["misses"] += 1
115
+ return None
116
+
117
+ def set(self, key: str, value: Any, ttl: Optional[float] = None) -> None:
118
+ """Set cached value with optional TTL"""
119
+ with self._lock:
120
+ # Cleanup and eviction
121
+ self._cleanup_expired()
122
+ self._evict_lru()
123
+
124
+ entry = CacheEntry(
125
+ data=value,
126
+ created_at=time.time(),
127
+ ttl=ttl or self.default_ttl,
128
+ last_accessed=time.time()
129
+ )
130
+
131
+ self._cache[key] = entry
132
+
133
+ def delete(self, key: str) -> bool:
134
+ """Delete cached value"""
135
+ with self._lock:
136
+ if key in self._cache:
137
+ del self._cache[key]
138
+ return True
139
+ return False
140
+
141
+ def clear(self) -> None:
142
+ """Clear all cached values"""
143
+ with self._lock:
144
+ self._cache.clear()
145
+ logger.info("Cache cleared")
146
+
147
+ def get_stats(self) -> Dict[str, Any]:
148
+ """Get cache statistics"""
149
+ with self._lock:
150
+ hit_rate = (
151
+ self._stats["hits"] / self._stats["total_requests"]
152
+ if self._stats["total_requests"] > 0 else 0
153
+ )
154
+
155
+ return {
156
+ "cache_size": len(self._cache),
157
+ "max_size": self.max_size,
158
+ "default_ttl": self.default_ttl,
159
+ "hit_rate": round(hit_rate * 100, 2),
160
+ **self._stats
161
+ }
162
+
163
+ # Decorator for caching function results
164
+ def cached(ttl: float = 300.0, cache_key_func: Optional[Callable] = None):
165
+ """
166
+ Decorator to cache function results
167
+
168
+ Args:
169
+ ttl: Time to live in seconds
170
+ cache_key_func: Custom function to generate cache key
171
+ """
172
+ def decorator(func):
173
+ async def async_wrapper(*args, **kwargs):
174
+ # Generate cache key
175
+ if cache_key_func:
176
+ cache_key = cache_key_func(*args, **kwargs)
177
+ else:
178
+ cache_key = api_cache._generate_key(func.__name__, *args, **kwargs)
179
+
180
+ # Try to get from cache
181
+ cached_result = api_cache.get(cache_key)
182
+ if cached_result is not None:
183
+ logger.debug(f"Cache hit for {func.__name__}")
184
+ return cached_result
185
+
186
+ # Execute function and cache result
187
+ try:
188
+ result = await func(*args, **kwargs)
189
+ api_cache.set(cache_key, result, ttl)
190
+ logger.debug(f"Cached result for {func.__name__}")
191
+ return result
192
+ except Exception as e:
193
+ logger.error(f"Function {func.__name__} failed: {e}")
194
+ raise
195
+
196
+ def sync_wrapper(*args, **kwargs):
197
+ # Generate cache key
198
+ if cache_key_func:
199
+ cache_key = cache_key_func(*args, **kwargs)
200
+ else:
201
+ cache_key = api_cache._generate_key(func.__name__, *args, **kwargs)
202
+
203
+ # Try to get from cache
204
+ cached_result = api_cache.get(cache_key)
205
+ if cached_result is not None:
206
+ logger.debug(f"Cache hit for {func.__name__}")
207
+ return cached_result
208
+
209
+ # Execute function and cache result
210
+ try:
211
+ result = func(*args, **kwargs)
212
+ api_cache.set(cache_key, result, ttl)
213
+ logger.debug(f"Cached result for {func.__name__}")
214
+ return result
215
+ except Exception as e:
216
+ logger.error(f"Function {func.__name__} failed: {e}")
217
+ raise
218
+
219
+ # Return appropriate wrapper based on function type
220
+ if asyncio.iscoroutinefunction(func):
221
+ return async_wrapper
222
+ else:
223
+ return sync_wrapper
224
+
225
+ return decorator
226
+
227
+ # Global cache instance
228
+ api_cache = APICache(default_ttl=300.0, max_size=1000)
229
+
230
+ def get_api_cache() -> APICache:
231
+ """Get the global API cache instance"""
232
+ return api_cache
233
+
234
+ # Cache key generators for common patterns
235
+ def model_list_cache_key(service_type=None):
236
+ """Generate cache key for model list API"""
237
+ return f"models_list_{service_type or 'all'}"
238
+
239
+ def provider_list_cache_key():
240
+ """Generate cache key for provider list API"""
241
+ return "providers_list"
242
+
243
+ def custom_models_cache_key(model_type=None, provider=None):
244
+ """Generate cache key for custom models API"""
245
+ return f"custom_models_{model_type or 'all'}_{provider or 'all'}"