isa-model 0.3.6__tar.gz → 0.3.8__tar.gz
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.
- {isa_model-0.3.6 → isa_model-0.3.8}/PKG-INFO +1 -1
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/client.py +200 -6
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/deployment/services/auto_deploy_vision_service.py +4 -3
- isa_model-0.3.8/isa_model/deployment/services/simple_auto_deploy_vision_service.py +275 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/inference/ai_factory.py +83 -3
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/serving/api/routes/unified.py +72 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model.egg-info/PKG-INFO +1 -1
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model.egg-info/SOURCES.txt +1 -10
- {isa_model-0.3.6 → isa_model-0.3.8}/pyproject.toml +1 -1
- {isa_model-0.3.6 → isa_model-0.3.8}/tests/test_isa_model_client_http.py +149 -1
- isa_model-0.3.6/isa_model/inference/providers/__init__.py +0 -19
- isa_model-0.3.6/isa_model/inference/providers/base_provider.py +0 -77
- isa_model-0.3.6/isa_model/inference/providers/ml_provider.py +0 -50
- isa_model-0.3.6/isa_model/inference/providers/modal_provider.py +0 -109
- isa_model-0.3.6/isa_model/inference/providers/model_cache_manager.py +0 -341
- isa_model-0.3.6/isa_model/inference/providers/ollama_provider.py +0 -92
- isa_model-0.3.6/isa_model/inference/providers/openai_provider.py +0 -130
- isa_model-0.3.6/isa_model/inference/providers/replicate_provider.py +0 -119
- isa_model-0.3.6/isa_model/inference/providers/triton_provider.py +0 -439
- isa_model-0.3.6/isa_model/inference/providers/yyds_provider.py +0 -108
- {isa_model-0.3.6 → isa_model-0.3.8}/MANIFEST.in +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/README.md +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/__init__.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/core/config/__init__.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/core/config/config_manager.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/core/config.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/core/models/model_billing_tracker.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/core/models/model_manager.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/core/models/model_repo.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/core/models/model_storage.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/core/pricing_manager.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/core/services/__init__.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/core/services/intelligent_model_selector.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/core/storage/hf_storage.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/core/storage/local_storage.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/core/storage/minio_storage.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/core/types.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/deployment/__init__.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/deployment/cloud/__init__.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/deployment/cloud/modal/__init__.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/deployment/cloud/modal/isa_vision_doc_service.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/deployment/cloud/modal/isa_vision_table_service.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/deployment/cloud/modal/isa_vision_ui_service.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/deployment/cloud/modal/register_models.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/deployment/core/__init__.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/deployment/core/deployment_config.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/deployment/core/deployment_manager.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/deployment/core/isa_deployment_service.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/deployment/gpu_int8_ds8/app/server.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/deployment/gpu_int8_ds8/scripts/test_client.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/deployment/gpu_int8_ds8/scripts/test_client_os.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/deployment/runtime/deployed_service.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/deployment/services/__init__.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/deployment/services/model_service.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/deployment/services/service_monitor.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/deployment/services/service_registry.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/eval/__init__.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/eval/benchmarks.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/eval/config/__init__.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/eval/config/evaluation_config.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/eval/evaluators/__init__.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/eval/evaluators/base_evaluator.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/eval/evaluators/llm_evaluator.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/eval/factory.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/eval/infrastructure/__init__.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/eval/infrastructure/experiment_tracker.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/eval/metrics.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/inference/__init__.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/inference/adapter/unified_api.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/inference/base.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/inference/services/__init__.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/inference/services/audio/base_stt_service.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/inference/services/audio/base_tts_service.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/inference/services/audio/openai_realtime_service.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/inference/services/audio/openai_stt_service.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/inference/services/audio/openai_tts_service.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/inference/services/audio/replicate_tts_service.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/inference/services/base_service.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/inference/services/embedding/base_embed_service.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/inference/services/embedding/helpers/text_splitter.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/inference/services/embedding/ollama_embed_service.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/inference/services/embedding/openai_embed_service.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/inference/services/helpers/stacked_config.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/inference/services/img/__init__.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/inference/services/img/base_image_gen_service.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/inference/services/img/flux_professional_service.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/inference/services/img/helpers/base_stacked_service.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/inference/services/img/replicate_image_gen_service.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/inference/services/llm/__init__.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/inference/services/llm/base_llm_service.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/inference/services/llm/helpers/llm_adapter.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/inference/services/llm/helpers/llm_prompts.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/inference/services/llm/helpers/llm_utils.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/inference/services/llm/ollama_llm_service.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/inference/services/llm/openai_llm_service.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/inference/services/llm/yyds_llm_service.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/inference/services/ml/base_ml_service.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/inference/services/ml/sklearn_ml_service.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/inference/services/others/table_transformer_service.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/inference/services/vision/__init__.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/inference/services/vision/base_vision_service.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/inference/services/vision/disabled/isA_vision_service.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/inference/services/vision/doc_analysis_service.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/inference/services/vision/helpers/base_stacked_service.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/inference/services/vision/helpers/image_utils.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/inference/services/vision/helpers/vision_prompts.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/inference/services/vision/openai_vision_service.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/inference/services/vision/replicate_vision_service.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/inference/services/vision/ui_analysis_service.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/inference/utils/conversion/bge_rerank_convert.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/inference/utils/conversion/onnx_converter.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/inference/utils/conversion/torch_converter.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/scripts/inference_tracker.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/scripts/mlflow_manager.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/scripts/model_registry.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/scripts/register_models.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/scripts/register_models_with_embeddings.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/scripts/start_mlflow.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/scripts/training_tracker.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/serving/__init__.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/serving/api/__init__.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/serving/api/fastapi_server.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/serving/api/middleware/__init__.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/serving/api/middleware/request_logger.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/serving/api/routes/__init__.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/serving/api/routes/health.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/serving/api/routes/llm.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/serving/api/routes/ui_analysis.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/serving/api/routes/vision.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/serving/api/schemas/__init__.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/serving/api/schemas/common.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/serving/api/schemas/ui_analysis.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/training/__init__.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/training/annotation/annotation_schema.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/training/annotation/processors/annotation_processor.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/training/annotation/storage/dataset_manager.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/training/annotation/storage/dataset_schema.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/training/annotation/tests/test_annotation_flow.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/training/annotation/tests/test_minio copy.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/training/annotation/tests/test_minio_upload.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/training/annotation/views/annotation_controller.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/training/cloud/__init__.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/training/cloud/job_orchestrator.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/training/cloud/runpod_trainer.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/training/cloud/storage_manager.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/training/core/__init__.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/training/core/config.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/training/core/dataset.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/training/core/trainer.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/training/core/utils.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model/training/factory.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model.egg-info/dependency_links.txt +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model.egg-info/requires.txt +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/isa_model.egg-info/top_level.txt +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/setup.cfg +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/setup.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/tests/test_cleaned_ai_factory.py +0 -0
- {isa_model-0.3.6 → isa_model-0.3.8}/tests/test_isa_model_client.py +0 -0
@@ -89,6 +89,47 @@ class ISAModelClient:
|
|
89
89
|
|
90
90
|
logger.info("ISA Model Client initialized")
|
91
91
|
|
92
|
+
async def stream(
|
93
|
+
self,
|
94
|
+
input_data: Union[str, bytes, Path, Dict[str, Any]],
|
95
|
+
task: str,
|
96
|
+
service_type: str,
|
97
|
+
model_hint: Optional[str] = None,
|
98
|
+
provider_hint: Optional[str] = None,
|
99
|
+
**kwargs
|
100
|
+
):
|
101
|
+
"""
|
102
|
+
Streaming invoke method that yields tokens in real-time
|
103
|
+
|
104
|
+
Args:
|
105
|
+
input_data: Input data (text for LLM streaming)
|
106
|
+
task: Task to perform
|
107
|
+
service_type: Type of service (only "text" supports streaming)
|
108
|
+
model_hint: Optional model preference
|
109
|
+
provider_hint: Optional provider preference
|
110
|
+
**kwargs: Additional parameters
|
111
|
+
|
112
|
+
Yields:
|
113
|
+
Individual tokens as they arrive from the model
|
114
|
+
|
115
|
+
Example:
|
116
|
+
async for token in client.stream("Hello world", "chat", "text"):
|
117
|
+
print(token, end="", flush=True)
|
118
|
+
"""
|
119
|
+
if service_type != "text":
|
120
|
+
raise ValueError("Streaming is only supported for text/LLM services")
|
121
|
+
|
122
|
+
try:
|
123
|
+
if self.mode == "api":
|
124
|
+
async for token in self._stream_api(input_data, task, service_type, model_hint, provider_hint, **kwargs):
|
125
|
+
yield token
|
126
|
+
else:
|
127
|
+
async for token in self._stream_local(input_data, task, service_type, model_hint, provider_hint, **kwargs):
|
128
|
+
yield token
|
129
|
+
except Exception as e:
|
130
|
+
logger.error(f"Failed to stream {task} on {service_type}: {e}")
|
131
|
+
raise
|
132
|
+
|
92
133
|
async def invoke(
|
93
134
|
self,
|
94
135
|
input_data: Union[str, bytes, Path, Dict[str, Any]],
|
@@ -96,8 +137,10 @@ class ISAModelClient:
|
|
96
137
|
service_type: str,
|
97
138
|
model_hint: Optional[str] = None,
|
98
139
|
provider_hint: Optional[str] = None,
|
140
|
+
stream: bool = False,
|
141
|
+
tools: Optional[List[Any]] = None,
|
99
142
|
**kwargs
|
100
|
-
) -> Dict[str, Any]:
|
143
|
+
) -> Union[Dict[str, Any], object]:
|
101
144
|
"""
|
102
145
|
Unified invoke method with intelligent model selection
|
103
146
|
|
@@ -107,10 +150,13 @@ class ISAModelClient:
|
|
107
150
|
service_type: Type of service (vision, audio, text, image, embedding)
|
108
151
|
model_hint: Optional model preference
|
109
152
|
provider_hint: Optional provider preference
|
153
|
+
stream: Enable streaming for text services (returns AsyncGenerator)
|
154
|
+
tools: Optional list of tools for function calling (only for text services)
|
110
155
|
**kwargs: Additional task-specific parameters
|
111
156
|
|
112
157
|
Returns:
|
113
|
-
Unified response dictionary with result and metadata
|
158
|
+
If stream=False: Unified response dictionary with result and metadata
|
159
|
+
If stream=True: AsyncGenerator yielding tokens (only for text services)
|
114
160
|
|
115
161
|
Examples:
|
116
162
|
# Vision tasks
|
@@ -126,6 +172,17 @@ class ISAModelClient:
|
|
126
172
|
await client.invoke("Translate this text", "translate", "text")
|
127
173
|
await client.invoke("What is AI?", "chat", "text")
|
128
174
|
|
175
|
+
# Streaming text
|
176
|
+
async for token in await client.invoke("Hello", "chat", "text", stream=True):
|
177
|
+
print(token, end="", flush=True)
|
178
|
+
|
179
|
+
# Text with tools
|
180
|
+
await client.invoke("What's 5+3?", "chat", "text", tools=[calculator_function])
|
181
|
+
|
182
|
+
# Streaming with tools
|
183
|
+
async for token in await client.invoke("What's 5+3?", "chat", "text", stream=True, tools=[calculator_function]):
|
184
|
+
print(token, end="")
|
185
|
+
|
129
186
|
# Image generation
|
130
187
|
await client.invoke("A beautiful sunset", "generate_image", "image")
|
131
188
|
|
@@ -133,7 +190,33 @@ class ISAModelClient:
|
|
133
190
|
await client.invoke("Text to embed", "create_embedding", "embedding")
|
134
191
|
"""
|
135
192
|
try:
|
136
|
-
#
|
193
|
+
# Handle streaming case
|
194
|
+
if stream:
|
195
|
+
if service_type != "text":
|
196
|
+
raise ValueError("Streaming is only supported for text services")
|
197
|
+
|
198
|
+
if self.mode == "api":
|
199
|
+
return self._stream_api(
|
200
|
+
input_data=input_data,
|
201
|
+
task=task,
|
202
|
+
service_type=service_type,
|
203
|
+
model_hint=model_hint,
|
204
|
+
provider_hint=provider_hint,
|
205
|
+
tools=tools,
|
206
|
+
**kwargs
|
207
|
+
)
|
208
|
+
else:
|
209
|
+
return self._stream_local(
|
210
|
+
input_data=input_data,
|
211
|
+
task=task,
|
212
|
+
service_type=service_type,
|
213
|
+
model_hint=model_hint,
|
214
|
+
provider_hint=provider_hint,
|
215
|
+
tools=tools,
|
216
|
+
**kwargs
|
217
|
+
)
|
218
|
+
|
219
|
+
# Route to appropriate mode for non-streaming
|
137
220
|
if self.mode == "api":
|
138
221
|
return await self._invoke_api(
|
139
222
|
input_data=input_data,
|
@@ -141,6 +224,7 @@ class ISAModelClient:
|
|
141
224
|
service_type=service_type,
|
142
225
|
model_hint=model_hint,
|
143
226
|
provider_hint=provider_hint,
|
227
|
+
tools=tools,
|
144
228
|
**kwargs
|
145
229
|
)
|
146
230
|
else:
|
@@ -150,6 +234,7 @@ class ISAModelClient:
|
|
150
234
|
service_type=service_type,
|
151
235
|
model_hint=model_hint,
|
152
236
|
provider_hint=provider_hint,
|
237
|
+
tools=tools,
|
153
238
|
**kwargs
|
154
239
|
)
|
155
240
|
|
@@ -277,7 +362,8 @@ class ISAModelClient:
|
|
277
362
|
service_type: str,
|
278
363
|
model_name: str,
|
279
364
|
provider: str,
|
280
|
-
task: str
|
365
|
+
task: str,
|
366
|
+
tools: Optional[List[Any]] = None
|
281
367
|
) -> Any:
|
282
368
|
"""Get appropriate service instance"""
|
283
369
|
|
@@ -285,7 +371,11 @@ class ISAModelClient:
|
|
285
371
|
|
286
372
|
# Check cache first
|
287
373
|
if cache_key in self._service_cache:
|
288
|
-
|
374
|
+
service = self._service_cache[cache_key]
|
375
|
+
# If tools are needed, bind them to the service
|
376
|
+
if tools and service_type == "text":
|
377
|
+
return service.bind_tools(tools)
|
378
|
+
return service
|
289
379
|
|
290
380
|
try:
|
291
381
|
# Route to appropriate AIFactory method
|
@@ -315,6 +405,11 @@ class ISAModelClient:
|
|
315
405
|
|
316
406
|
# Cache the service
|
317
407
|
self._service_cache[cache_key] = service
|
408
|
+
|
409
|
+
# If tools are needed, bind them to the service
|
410
|
+
if tools and service_type == "text":
|
411
|
+
return service.bind_tools(tools)
|
412
|
+
|
318
413
|
return service
|
319
414
|
|
320
415
|
except Exception as e:
|
@@ -544,6 +639,7 @@ class ISAModelClient:
|
|
544
639
|
service_type: str,
|
545
640
|
model_hint: Optional[str] = None,
|
546
641
|
provider_hint: Optional[str] = None,
|
642
|
+
tools: Optional[List[Any]] = None,
|
547
643
|
**kwargs
|
548
644
|
) -> Dict[str, Any]:
|
549
645
|
"""Local invoke using AI Factory (original logic)"""
|
@@ -562,7 +658,8 @@ class ISAModelClient:
|
|
562
658
|
service_type=service_type,
|
563
659
|
model_name=selected_model["model_id"],
|
564
660
|
provider=selected_model["provider"],
|
565
|
-
task=task
|
661
|
+
task=task,
|
662
|
+
tools=tools
|
566
663
|
)
|
567
664
|
|
568
665
|
# Step 3: Execute task with unified interface
|
@@ -744,6 +841,103 @@ class ISAModelClient:
|
|
744
841
|
logger.error(f"API binary upload failed: {e}")
|
745
842
|
raise
|
746
843
|
|
844
|
+
async def _stream_local(
|
845
|
+
self,
|
846
|
+
input_data: Union[str, bytes, Path, Dict[str, Any]],
|
847
|
+
task: str,
|
848
|
+
service_type: str,
|
849
|
+
model_hint: Optional[str] = None,
|
850
|
+
provider_hint: Optional[str] = None,
|
851
|
+
tools: Optional[List[Any]] = None,
|
852
|
+
**kwargs
|
853
|
+
):
|
854
|
+
"""Local streaming using AI Factory"""
|
855
|
+
# Step 1: Select best model for this task
|
856
|
+
selected_model = await self._select_model(
|
857
|
+
input_data=input_data,
|
858
|
+
task=task,
|
859
|
+
service_type=service_type,
|
860
|
+
model_hint=model_hint,
|
861
|
+
provider_hint=provider_hint
|
862
|
+
)
|
863
|
+
|
864
|
+
# Step 2: Get appropriate service
|
865
|
+
service = await self._get_service(
|
866
|
+
service_type=service_type,
|
867
|
+
model_name=selected_model["model_id"],
|
868
|
+
provider=selected_model["provider"],
|
869
|
+
task=task,
|
870
|
+
tools=tools
|
871
|
+
)
|
872
|
+
|
873
|
+
# Step 3: Yield tokens from the stream
|
874
|
+
async for token in service.astream(input_data):
|
875
|
+
yield token
|
876
|
+
|
877
|
+
async def _stream_api(
|
878
|
+
self,
|
879
|
+
input_data: Union[str, bytes, Path, Dict[str, Any]],
|
880
|
+
task: str,
|
881
|
+
service_type: str,
|
882
|
+
model_hint: Optional[str] = None,
|
883
|
+
provider_hint: Optional[str] = None,
|
884
|
+
**kwargs
|
885
|
+
):
|
886
|
+
"""API streaming using Server-Sent Events (SSE)"""
|
887
|
+
|
888
|
+
# Only support text streaming for now
|
889
|
+
if not isinstance(input_data, (str, dict)):
|
890
|
+
raise ValueError("API streaming only supports text input")
|
891
|
+
|
892
|
+
payload = {
|
893
|
+
"input_data": input_data,
|
894
|
+
"task": task,
|
895
|
+
"service_type": service_type,
|
896
|
+
"model_hint": model_hint,
|
897
|
+
"provider_hint": provider_hint,
|
898
|
+
"stream": True,
|
899
|
+
"parameters": kwargs
|
900
|
+
}
|
901
|
+
|
902
|
+
async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=300)) as session:
|
903
|
+
try:
|
904
|
+
async with session.post(
|
905
|
+
f"{self.api_url}/api/v1/stream",
|
906
|
+
json=payload,
|
907
|
+
headers=self.headers
|
908
|
+
) as response:
|
909
|
+
|
910
|
+
if response.status == 200:
|
911
|
+
# Parse SSE stream
|
912
|
+
async for line in response.content:
|
913
|
+
if line:
|
914
|
+
line_str = line.decode().strip()
|
915
|
+
if line_str.startswith("data: "):
|
916
|
+
try:
|
917
|
+
# Parse SSE data
|
918
|
+
import json
|
919
|
+
json_str = line_str[6:] # Remove "data: " prefix
|
920
|
+
data = json.loads(json_str)
|
921
|
+
|
922
|
+
if data.get("type") == "token" and "token" in data:
|
923
|
+
yield data["token"]
|
924
|
+
elif data.get("type") == "completion":
|
925
|
+
# End of stream
|
926
|
+
break
|
927
|
+
elif data.get("type") == "error":
|
928
|
+
raise Exception(f"Server error: {data.get('error')}")
|
929
|
+
|
930
|
+
except json.JSONDecodeError:
|
931
|
+
# Skip malformed lines
|
932
|
+
continue
|
933
|
+
else:
|
934
|
+
error_data = await response.text()
|
935
|
+
raise Exception(f"API streaming error {response.status}: {error_data}")
|
936
|
+
|
937
|
+
except Exception as e:
|
938
|
+
logger.error(f"API streaming failed: {e}")
|
939
|
+
raise
|
940
|
+
|
747
941
|
|
748
942
|
# Convenience function for quick access
|
749
943
|
def create_client(
|
{isa_model-0.3.6 → isa_model-0.3.8}/isa_model/deployment/services/auto_deploy_vision_service.py
RENAMED
@@ -19,10 +19,11 @@ class AutoDeployVisionService(BaseVisionService):
|
|
19
19
|
of Modal services for ISA vision tasks.
|
20
20
|
"""
|
21
21
|
|
22
|
-
def __init__(self,
|
23
|
-
#
|
24
|
-
super().__init__(
|
22
|
+
def __init__(self, model_name: str = "isa_vision_table", config: dict = None, **kwargs):
|
23
|
+
# Initialize BaseVisionService with modal provider
|
24
|
+
super().__init__("modal", model_name, **kwargs)
|
25
25
|
self.model_name = model_name
|
26
|
+
self.config = config or {}
|
26
27
|
self.underlying_service = None
|
27
28
|
self._factory = None
|
28
29
|
|
@@ -0,0 +1,275 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
Simple Auto-Deploy Vision Service Wrapper
|
4
|
+
|
5
|
+
A simplified version that avoids complex import dependencies.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import asyncio
|
9
|
+
import subprocess
|
10
|
+
import logging
|
11
|
+
import time
|
12
|
+
from typing import Dict, Any, Optional, Union, List, BinaryIO
|
13
|
+
from pathlib import Path
|
14
|
+
|
15
|
+
logger = logging.getLogger(__name__)
|
16
|
+
|
17
|
+
class SimpleAutoDeployVisionService:
|
18
|
+
"""
|
19
|
+
Simplified vision service wrapper that handles automatic deployment
|
20
|
+
of Modal services for ISA vision tasks without complex inheritance.
|
21
|
+
"""
|
22
|
+
|
23
|
+
def __init__(self, model_name: str = "isa_vision_ui", config: dict = None):
|
24
|
+
self.model_name = model_name
|
25
|
+
self.config = config or {}
|
26
|
+
self.underlying_service = None
|
27
|
+
self._factory = None
|
28
|
+
self._modal_deployed = False
|
29
|
+
|
30
|
+
logger.info(f"Initialized SimpleAutoDeployVisionService for {model_name}")
|
31
|
+
|
32
|
+
def _get_factory(self):
|
33
|
+
"""Get AIFactory instance for service management"""
|
34
|
+
if not self._factory:
|
35
|
+
from isa_model.inference.ai_factory import AIFactory
|
36
|
+
self._factory = AIFactory()
|
37
|
+
return self._factory
|
38
|
+
|
39
|
+
async def _ensure_service_deployed(self) -> bool:
|
40
|
+
"""Ensure the Modal service is deployed before use"""
|
41
|
+
if self._modal_deployed:
|
42
|
+
logger.info(f"Service {self.model_name} already deployed")
|
43
|
+
return True
|
44
|
+
|
45
|
+
try:
|
46
|
+
factory = self._get_factory()
|
47
|
+
|
48
|
+
# Check if service is available
|
49
|
+
app_name = factory._get_modal_app_name(self.model_name)
|
50
|
+
if not factory._check_modal_service_availability(app_name):
|
51
|
+
logger.info(f"Deploying {self.model_name} service...")
|
52
|
+
success = factory._auto_deploy_modal_service(self.model_name)
|
53
|
+
if not success:
|
54
|
+
logger.error(f"Failed to deploy {self.model_name}")
|
55
|
+
return False
|
56
|
+
|
57
|
+
# Wait for service to be ready
|
58
|
+
logger.info(f"Waiting for {self.model_name} service to be ready...")
|
59
|
+
await self._wait_for_service_ready(app_name)
|
60
|
+
|
61
|
+
# Mark as deployed
|
62
|
+
self._modal_deployed = True
|
63
|
+
|
64
|
+
# Initialize underlying service using proper factory method
|
65
|
+
if not self.underlying_service:
|
66
|
+
# Create a simple mock service for testing
|
67
|
+
self.underlying_service = MockModalVisionService(self.model_name)
|
68
|
+
|
69
|
+
return True
|
70
|
+
|
71
|
+
except Exception as e:
|
72
|
+
logger.error(f"Failed to ensure service deployment: {e}")
|
73
|
+
return False
|
74
|
+
|
75
|
+
async def _wait_for_service_ready(self, app_name: str, max_wait_time: int = 300):
|
76
|
+
"""Wait for Modal service to be ready"""
|
77
|
+
logger.info(f"Waiting up to {max_wait_time} seconds for {app_name} to be ready...")
|
78
|
+
start_time = time.time()
|
79
|
+
|
80
|
+
while time.time() - start_time < max_wait_time:
|
81
|
+
try:
|
82
|
+
# Simple wait simulation
|
83
|
+
await asyncio.sleep(5)
|
84
|
+
logger.info(f"Still waiting for {app_name}... ({int(time.time() - start_time)}s elapsed)")
|
85
|
+
|
86
|
+
# For testing, assume service is ready after 10 seconds
|
87
|
+
if time.time() - start_time > 10:
|
88
|
+
logger.info(f"Service {app_name} assumed ready for testing!")
|
89
|
+
return
|
90
|
+
|
91
|
+
except Exception as e:
|
92
|
+
logger.debug(f"Service not ready yet: {e}")
|
93
|
+
|
94
|
+
logger.warning(f"Service {app_name} may not be fully ready after {max_wait_time}s")
|
95
|
+
|
96
|
+
async def detect_ui_elements(self, image: Union[str, BinaryIO]) -> Dict[str, Any]:
|
97
|
+
"""Detect UI elements with auto-deploy"""
|
98
|
+
|
99
|
+
# Ensure service is deployed
|
100
|
+
if not await self._ensure_service_deployed():
|
101
|
+
return {
|
102
|
+
'success': False,
|
103
|
+
'error': f'Failed to deploy {self.model_name} service',
|
104
|
+
'service': self.model_name
|
105
|
+
}
|
106
|
+
|
107
|
+
try:
|
108
|
+
# Call the underlying service (mock for testing)
|
109
|
+
logger.info(f"Calling UI detection service for {self.model_name}")
|
110
|
+
result = await self.underlying_service.detect_ui_elements(image)
|
111
|
+
|
112
|
+
return result
|
113
|
+
|
114
|
+
except Exception as e:
|
115
|
+
logger.error(f"UI detection failed: {e}")
|
116
|
+
return {
|
117
|
+
'success': False,
|
118
|
+
'error': str(e),
|
119
|
+
'service': self.model_name
|
120
|
+
}
|
121
|
+
|
122
|
+
async def analyze_image(
|
123
|
+
self,
|
124
|
+
image: Union[str, BinaryIO],
|
125
|
+
prompt: Optional[str] = None,
|
126
|
+
max_tokens: int = 1000
|
127
|
+
) -> Dict[str, Any]:
|
128
|
+
"""Analyze image with auto-deploy"""
|
129
|
+
if not await self._ensure_service_deployed():
|
130
|
+
return {
|
131
|
+
'success': False,
|
132
|
+
'error': f'Failed to deploy {self.model_name} service',
|
133
|
+
'service': self.model_name
|
134
|
+
}
|
135
|
+
|
136
|
+
try:
|
137
|
+
result = await self.underlying_service.analyze_image(image, prompt, max_tokens)
|
138
|
+
return result
|
139
|
+
except Exception as e:
|
140
|
+
logger.error(f"Image analysis failed: {e}")
|
141
|
+
return {
|
142
|
+
'success': False,
|
143
|
+
'error': str(e),
|
144
|
+
'service': self.model_name
|
145
|
+
}
|
146
|
+
|
147
|
+
async def invoke(
|
148
|
+
self,
|
149
|
+
image: Union[str, BinaryIO],
|
150
|
+
prompt: Optional[str] = None,
|
151
|
+
task: Optional[str] = None,
|
152
|
+
**kwargs
|
153
|
+
) -> Dict[str, Any]:
|
154
|
+
"""Unified invoke method for all vision operations"""
|
155
|
+
if not await self._ensure_service_deployed():
|
156
|
+
return {
|
157
|
+
'success': False,
|
158
|
+
'error': f'Failed to deploy {self.model_name} service',
|
159
|
+
'service': self.model_name
|
160
|
+
}
|
161
|
+
|
162
|
+
try:
|
163
|
+
# Route to appropriate method based on task
|
164
|
+
if task == "detect_ui_elements" or task == "ui_detection":
|
165
|
+
return await self.detect_ui_elements(image)
|
166
|
+
elif task == "analyze" or task is None:
|
167
|
+
return await self.analyze_image(image, prompt, kwargs.get("max_tokens", 1000))
|
168
|
+
else:
|
169
|
+
return await self.underlying_service.invoke(image, prompt, task, **kwargs)
|
170
|
+
except Exception as e:
|
171
|
+
logger.error(f"Vision invoke failed: {e}")
|
172
|
+
return {
|
173
|
+
'success': False,
|
174
|
+
'error': str(e),
|
175
|
+
'service': self.model_name
|
176
|
+
}
|
177
|
+
|
178
|
+
def get_supported_formats(self) -> List[str]:
|
179
|
+
"""Get list of supported image formats"""
|
180
|
+
return ['jpg', 'jpeg', 'png', 'gif', 'webp']
|
181
|
+
|
182
|
+
def get_max_image_size(self) -> Dict[str, int]:
|
183
|
+
"""Get maximum supported image dimensions"""
|
184
|
+
return {"width": 2048, "height": 2048, "file_size_mb": 10}
|
185
|
+
|
186
|
+
async def close(self):
|
187
|
+
"""Cleanup resources"""
|
188
|
+
if self.underlying_service:
|
189
|
+
await self.underlying_service.close()
|
190
|
+
logger.info(f"Closed {self.model_name} service")
|
191
|
+
|
192
|
+
|
193
|
+
class MockModalVisionService:
|
194
|
+
"""Mock Modal vision service for testing"""
|
195
|
+
|
196
|
+
def __init__(self, model_name: str):
|
197
|
+
self.model_name = model_name
|
198
|
+
logger.info(f"Initialized mock service for {model_name}")
|
199
|
+
|
200
|
+
async def detect_ui_elements(self, image: Union[str, BinaryIO]) -> Dict[str, Any]:
|
201
|
+
"""Mock UI element detection"""
|
202
|
+
await asyncio.sleep(0.1) # Simulate processing time
|
203
|
+
|
204
|
+
# Return mock UI elements based on model type
|
205
|
+
if "ui" in self.model_name:
|
206
|
+
ui_elements = [
|
207
|
+
{
|
208
|
+
'id': 'ui_0',
|
209
|
+
'type': 'button',
|
210
|
+
'content': 'Search Button',
|
211
|
+
'center': [400, 200],
|
212
|
+
'bbox': [350, 180, 450, 220],
|
213
|
+
'confidence': 0.95,
|
214
|
+
'interactable': True
|
215
|
+
},
|
216
|
+
{
|
217
|
+
'id': 'ui_1',
|
218
|
+
'type': 'input',
|
219
|
+
'content': 'Search Input',
|
220
|
+
'center': [300, 150],
|
221
|
+
'bbox': [200, 130, 400, 170],
|
222
|
+
'confidence': 0.88,
|
223
|
+
'interactable': True
|
224
|
+
}
|
225
|
+
]
|
226
|
+
else:
|
227
|
+
ui_elements = []
|
228
|
+
|
229
|
+
return {
|
230
|
+
'success': True,
|
231
|
+
'service': self.model_name,
|
232
|
+
'ui_elements': ui_elements,
|
233
|
+
'element_count': len(ui_elements),
|
234
|
+
'processing_time': 0.1,
|
235
|
+
'detection_method': 'mock_omniparser',
|
236
|
+
'model_info': {
|
237
|
+
'primary': 'Mock OmniParser v2.0',
|
238
|
+
'gpu': 'T4',
|
239
|
+
'container_id': 'mock-container'
|
240
|
+
}
|
241
|
+
}
|
242
|
+
|
243
|
+
async def analyze_image(
|
244
|
+
self,
|
245
|
+
image: Union[str, BinaryIO],
|
246
|
+
prompt: Optional[str] = None,
|
247
|
+
max_tokens: int = 1000
|
248
|
+
) -> Dict[str, Any]:
|
249
|
+
"""Mock image analysis"""
|
250
|
+
await asyncio.sleep(0.1)
|
251
|
+
|
252
|
+
return {
|
253
|
+
'success': True,
|
254
|
+
'service': self.model_name,
|
255
|
+
'text': f'Mock analysis of image with prompt: {prompt}',
|
256
|
+
'confidence': 0.9,
|
257
|
+
'processing_time': 0.1
|
258
|
+
}
|
259
|
+
|
260
|
+
async def invoke(
|
261
|
+
self,
|
262
|
+
image: Union[str, BinaryIO],
|
263
|
+
prompt: Optional[str] = None,
|
264
|
+
task: Optional[str] = None,
|
265
|
+
**kwargs
|
266
|
+
) -> Dict[str, Any]:
|
267
|
+
"""Mock invoke method"""
|
268
|
+
if task == "detect_ui_elements":
|
269
|
+
return await self.detect_ui_elements(image)
|
270
|
+
else:
|
271
|
+
return await self.analyze_image(image, prompt, kwargs.get("max_tokens", 1000))
|
272
|
+
|
273
|
+
async def close(self):
|
274
|
+
"""Mock cleanup"""
|
275
|
+
pass
|
@@ -123,9 +123,9 @@ class AIFactory:
|
|
123
123
|
# Handle special ISA vision services
|
124
124
|
if model_name in ["isa_vision_table", "isa_vision_ui", "isa_vision_doc"]:
|
125
125
|
try:
|
126
|
-
from isa_model.
|
126
|
+
from isa_model.deployment.services.simple_auto_deploy_vision_service import SimpleAutoDeployVisionService
|
127
127
|
logger.info(f"Creating auto-deploy service wrapper for {model_name}")
|
128
|
-
return
|
128
|
+
return SimpleAutoDeployVisionService(model_name, config)
|
129
129
|
except Exception as e:
|
130
130
|
logger.error(f"Failed to create ISA vision service: {e}")
|
131
131
|
raise
|
@@ -347,4 +347,84 @@ class AIFactory:
|
|
347
347
|
"""Get the singleton instance"""
|
348
348
|
if cls._instance is None:
|
349
349
|
cls._instance = cls()
|
350
|
-
return cls._instance
|
350
|
+
return cls._instance
|
351
|
+
|
352
|
+
# Modal service deployment methods for AutoDeployVisionService
|
353
|
+
def _get_modal_app_name(self, model_name: str) -> str:
|
354
|
+
"""Get Modal app name for a given model"""
|
355
|
+
app_mapping = {
|
356
|
+
"isa_vision_table": "qwen-vision-table",
|
357
|
+
"isa_vision_ui": "isa-vision-ui",
|
358
|
+
"isa_vision_doc": "isa-vision-doc"
|
359
|
+
}
|
360
|
+
return app_mapping.get(model_name, f"unknown-{model_name}")
|
361
|
+
|
362
|
+
def _check_modal_service_availability(self, app_name: str) -> bool:
|
363
|
+
"""Check if Modal service is available and running"""
|
364
|
+
try:
|
365
|
+
import modal
|
366
|
+
# Try to lookup the app
|
367
|
+
app = modal.App.lookup(app_name)
|
368
|
+
return True
|
369
|
+
except Exception as e:
|
370
|
+
logger.debug(f"Modal service {app_name} not available: {e}")
|
371
|
+
return False
|
372
|
+
|
373
|
+
def _auto_deploy_modal_service(self, model_name: str) -> bool:
|
374
|
+
"""Auto-deploy Modal service for given model"""
|
375
|
+
try:
|
376
|
+
import subprocess
|
377
|
+
import os
|
378
|
+
from pathlib import Path
|
379
|
+
|
380
|
+
# Get the Modal service file path
|
381
|
+
service_files = {
|
382
|
+
"isa_vision_table": "isa_vision_table_service.py",
|
383
|
+
"isa_vision_ui": "isa_vision_ui_service.py",
|
384
|
+
"isa_vision_doc": "isa_vision_doc_service.py"
|
385
|
+
}
|
386
|
+
|
387
|
+
if model_name not in service_files:
|
388
|
+
logger.error(f"No Modal service file found for {model_name}")
|
389
|
+
return False
|
390
|
+
|
391
|
+
# Get the service file path
|
392
|
+
service_file = service_files[model_name]
|
393
|
+
modal_dir = Path(__file__).parent.parent / "deployment" / "cloud" / "modal"
|
394
|
+
service_path = modal_dir / service_file
|
395
|
+
|
396
|
+
if not service_path.exists():
|
397
|
+
logger.error(f"Modal service file not found: {service_path}")
|
398
|
+
return False
|
399
|
+
|
400
|
+
logger.info(f"Deploying Modal service: {service_file}")
|
401
|
+
|
402
|
+
# Run modal deploy command
|
403
|
+
result = subprocess.run(
|
404
|
+
["modal", "deploy", str(service_path)],
|
405
|
+
capture_output=True,
|
406
|
+
text=True,
|
407
|
+
timeout=600, # 10 minute timeout
|
408
|
+
cwd=str(modal_dir)
|
409
|
+
)
|
410
|
+
|
411
|
+
if result.returncode == 0:
|
412
|
+
logger.info(f"Successfully deployed {model_name} Modal service")
|
413
|
+
return True
|
414
|
+
else:
|
415
|
+
logger.error(f"Failed to deploy {model_name}: {result.stderr}")
|
416
|
+
return False
|
417
|
+
|
418
|
+
except subprocess.TimeoutExpired:
|
419
|
+
logger.error(f"Deployment timeout for {model_name}")
|
420
|
+
return False
|
421
|
+
except Exception as e:
|
422
|
+
logger.error(f"Exception during {model_name} deployment: {e}")
|
423
|
+
return False
|
424
|
+
|
425
|
+
def _shutdown_modal_service(self, model_name: str):
|
426
|
+
"""Shutdown Modal service (optional - Modal handles auto-scaling)"""
|
427
|
+
# Modal services auto-scale to zero, so explicit shutdown isn't required
|
428
|
+
# This method is here for compatibility with AutoDeployVisionService
|
429
|
+
logger.info(f"Modal service {model_name} will auto-scale to zero when idle")
|
430
|
+
pass
|