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.
- isa_model/client.py +466 -43
- isa_model/core/cache/redis_cache.py +12 -3
- isa_model/core/config/config_manager.py +230 -3
- isa_model/core/config.py +90 -0
- isa_model/core/database/direct_db_client.py +114 -0
- isa_model/core/database/migration_manager.py +563 -0
- isa_model/core/database/migrations.py +21 -1
- isa_model/core/database/supabase_client.py +154 -19
- isa_model/core/dependencies.py +316 -0
- isa_model/core/discovery/__init__.py +19 -0
- isa_model/core/discovery/consul_discovery.py +190 -0
- isa_model/core/logging/__init__.py +54 -0
- isa_model/core/logging/influx_logger.py +523 -0
- isa_model/core/logging/loki_logger.py +160 -0
- isa_model/core/models/__init__.py +27 -18
- isa_model/core/models/config_models.py +625 -0
- isa_model/core/models/deployment_billing_tracker.py +430 -0
- isa_model/core/models/model_manager.py +40 -17
- isa_model/core/models/model_metadata.py +690 -0
- isa_model/core/models/model_repo.py +174 -18
- isa_model/core/models/system_models.py +857 -0
- isa_model/core/repositories/__init__.py +9 -0
- isa_model/core/repositories/config_repository.py +912 -0
- isa_model/core/services/intelligent_model_selector.py +399 -21
- isa_model/core/storage/hf_storage.py +1 -1
- isa_model/core/types.py +1 -0
- isa_model/deployment/__init__.py +5 -48
- isa_model/deployment/core/__init__.py +2 -31
- isa_model/deployment/core/deployment_manager.py +1278 -370
- isa_model/deployment/local/__init__.py +31 -0
- isa_model/deployment/local/config.py +248 -0
- isa_model/deployment/local/gpu_gateway.py +607 -0
- isa_model/deployment/local/health_checker.py +428 -0
- isa_model/deployment/local/provider.py +586 -0
- isa_model/deployment/local/tensorrt_service.py +621 -0
- isa_model/deployment/local/transformers_service.py +644 -0
- isa_model/deployment/local/vllm_service.py +527 -0
- isa_model/deployment/modal/__init__.py +8 -0
- isa_model/deployment/modal/config.py +136 -0
- isa_model/deployment/{services/auto_hf_modal_deployer.py → modal/deployer.py} +1 -1
- isa_model/deployment/modal/services/__init__.py +3 -0
- isa_model/deployment/modal/services/audio/__init__.py +1 -0
- isa_model/deployment/modal/services/embedding/__init__.py +1 -0
- isa_model/deployment/modal/services/llm/__init__.py +1 -0
- isa_model/deployment/modal/services/llm/isa_llm_service.py +424 -0
- isa_model/deployment/modal/services/video/__init__.py +1 -0
- isa_model/deployment/modal/services/vision/__init__.py +1 -0
- isa_model/deployment/models/org-org-acme-corp-tenant-a-service-llm-20250825-225822/tenant-a-service_modal_service.py +48 -0
- isa_model/deployment/models/org-test-org-123-prefix-test-service-llm-20250825-225822/prefix-test-service_modal_service.py +48 -0
- isa_model/deployment/models/test-llm-service-llm-20250825-204442/test-llm-service_modal_service.py +48 -0
- isa_model/deployment/models/test-monitoring-gpt2-llm-20250825-212906/test-monitoring-gpt2_modal_service.py +48 -0
- isa_model/deployment/models/test-monitoring-gpt2-llm-20250825-213009/test-monitoring-gpt2_modal_service.py +48 -0
- isa_model/deployment/storage/__init__.py +5 -0
- isa_model/deployment/storage/deployment_repository.py +824 -0
- isa_model/deployment/triton/__init__.py +10 -0
- isa_model/deployment/triton/config.py +196 -0
- isa_model/deployment/triton/configs/__init__.py +1 -0
- isa_model/deployment/triton/provider.py +512 -0
- isa_model/deployment/triton/scripts/__init__.py +1 -0
- isa_model/deployment/triton/templates/__init__.py +1 -0
- isa_model/inference/__init__.py +47 -1
- isa_model/inference/ai_factory.py +137 -10
- isa_model/inference/legacy_services/__init__.py +21 -0
- isa_model/inference/legacy_services/model_evaluation.py +637 -0
- isa_model/inference/legacy_services/model_service.py +573 -0
- isa_model/inference/legacy_services/model_serving.py +717 -0
- isa_model/inference/legacy_services/model_training.py +561 -0
- isa_model/inference/models/__init__.py +21 -0
- isa_model/inference/models/inference_config.py +551 -0
- isa_model/inference/models/inference_record.py +675 -0
- isa_model/inference/models/performance_models.py +714 -0
- isa_model/inference/repositories/__init__.py +9 -0
- isa_model/inference/repositories/inference_repository.py +828 -0
- isa_model/inference/services/audio/base_stt_service.py +184 -11
- isa_model/inference/services/audio/openai_stt_service.py +22 -6
- isa_model/inference/services/custom_model_manager.py +277 -0
- isa_model/inference/services/embedding/ollama_embed_service.py +15 -3
- isa_model/inference/services/embedding/resilient_embed_service.py +285 -0
- isa_model/inference/services/llm/__init__.py +10 -2
- isa_model/inference/services/llm/base_llm_service.py +335 -24
- isa_model/inference/services/llm/cerebras_llm_service.py +628 -0
- isa_model/inference/services/llm/helpers/llm_adapter.py +9 -4
- isa_model/inference/services/llm/helpers/llm_prompts.py +342 -0
- isa_model/inference/services/llm/helpers/llm_utils.py +321 -23
- isa_model/inference/services/llm/huggingface_llm_service.py +581 -0
- isa_model/inference/services/llm/local_llm_service.py +747 -0
- isa_model/inference/services/llm/ollama_llm_service.py +9 -2
- isa_model/inference/services/llm/openai_llm_service.py +33 -16
- isa_model/inference/services/llm/yyds_llm_service.py +8 -2
- isa_model/inference/services/vision/__init__.py +22 -1
- isa_model/inference/services/vision/blip_vision_service.py +359 -0
- isa_model/inference/services/vision/helpers/image_utils.py +8 -5
- isa_model/inference/services/vision/isa_vision_service.py +65 -4
- isa_model/inference/services/vision/openai_vision_service.py +19 -10
- isa_model/inference/services/vision/vgg16_vision_service.py +257 -0
- isa_model/serving/api/cache_manager.py +245 -0
- isa_model/serving/api/dependencies/__init__.py +1 -0
- isa_model/serving/api/dependencies/auth.py +194 -0
- isa_model/serving/api/dependencies/database.py +139 -0
- isa_model/serving/api/error_handlers.py +284 -0
- isa_model/serving/api/fastapi_server.py +172 -22
- isa_model/serving/api/middleware/auth.py +8 -2
- isa_model/serving/api/middleware/security.py +23 -33
- isa_model/serving/api/middleware/tenant_context.py +414 -0
- isa_model/serving/api/routes/analytics.py +4 -1
- isa_model/serving/api/routes/config.py +645 -0
- isa_model/serving/api/routes/deployment_billing.py +315 -0
- isa_model/serving/api/routes/deployments.py +138 -2
- isa_model/serving/api/routes/gpu_gateway.py +440 -0
- isa_model/serving/api/routes/health.py +32 -12
- isa_model/serving/api/routes/inference_monitoring.py +486 -0
- isa_model/serving/api/routes/local_deployments.py +448 -0
- isa_model/serving/api/routes/tenants.py +575 -0
- isa_model/serving/api/routes/unified.py +680 -18
- isa_model/serving/api/routes/webhooks.py +479 -0
- isa_model/serving/api/startup.py +68 -54
- isa_model/utils/gpu_utils.py +311 -0
- {isa_model-0.4.0.dist-info → isa_model-0.4.3.dist-info}/METADATA +66 -24
- isa_model-0.4.3.dist-info/RECORD +193 -0
- isa_model/core/storage/minio_storage.py +0 -0
- isa_model/deployment/cloud/__init__.py +0 -9
- isa_model/deployment/cloud/modal/__init__.py +0 -10
- isa_model/deployment/core/deployment_config.py +0 -356
- isa_model/deployment/core/isa_deployment_service.py +0 -401
- isa_model/deployment/gpu_int8_ds8/app/server.py +0 -66
- isa_model/deployment/gpu_int8_ds8/scripts/test_client.py +0 -43
- isa_model/deployment/gpu_int8_ds8/scripts/test_client_os.py +0 -35
- isa_model/deployment/runtime/deployed_service.py +0 -338
- isa_model/deployment/services/__init__.py +0 -9
- isa_model/deployment/services/auto_deploy_vision_service.py +0 -538
- isa_model/deployment/services/model_service.py +0 -332
- isa_model/deployment/services/service_monitor.py +0 -356
- isa_model/deployment/services/service_registry.py +0 -527
- isa_model/eval/__init__.py +0 -92
- isa_model/eval/benchmarks/__init__.py +0 -27
- isa_model/eval/benchmarks/multimodal_datasets.py +0 -460
- isa_model/eval/benchmarks.py +0 -701
- isa_model/eval/config/__init__.py +0 -10
- isa_model/eval/config/evaluation_config.py +0 -108
- isa_model/eval/evaluators/__init__.py +0 -24
- isa_model/eval/evaluators/audio_evaluator.py +0 -727
- isa_model/eval/evaluators/base_evaluator.py +0 -503
- isa_model/eval/evaluators/embedding_evaluator.py +0 -742
- isa_model/eval/evaluators/llm_evaluator.py +0 -472
- isa_model/eval/evaluators/vision_evaluator.py +0 -564
- isa_model/eval/example_evaluation.py +0 -395
- isa_model/eval/factory.py +0 -798
- isa_model/eval/infrastructure/__init__.py +0 -24
- isa_model/eval/infrastructure/experiment_tracker.py +0 -466
- isa_model/eval/isa_benchmarks.py +0 -700
- isa_model/eval/isa_integration.py +0 -582
- isa_model/eval/metrics.py +0 -951
- isa_model/eval/tests/unit/test_basic.py +0 -396
- isa_model/serving/api/routes/evaluations.py +0 -579
- isa_model/training/__init__.py +0 -168
- isa_model/training/annotation/annotation_schema.py +0 -47
- isa_model/training/annotation/processors/annotation_processor.py +0 -126
- isa_model/training/annotation/storage/dataset_manager.py +0 -131
- isa_model/training/annotation/storage/dataset_schema.py +0 -44
- isa_model/training/annotation/tests/test_annotation_flow.py +0 -109
- isa_model/training/annotation/tests/test_minio copy.py +0 -113
- isa_model/training/annotation/tests/test_minio_upload.py +0 -43
- isa_model/training/annotation/views/annotation_controller.py +0 -158
- isa_model/training/cloud/__init__.py +0 -22
- isa_model/training/cloud/job_orchestrator.py +0 -402
- isa_model/training/cloud/runpod_trainer.py +0 -454
- isa_model/training/cloud/storage_manager.py +0 -482
- isa_model/training/core/__init__.py +0 -26
- isa_model/training/core/config.py +0 -181
- isa_model/training/core/dataset.py +0 -222
- isa_model/training/core/trainer.py +0 -720
- isa_model/training/core/utils.py +0 -213
- isa_model/training/examples/intelligent_training_example.py +0 -281
- isa_model/training/factory.py +0 -424
- isa_model/training/intelligent/__init__.py +0 -25
- isa_model/training/intelligent/decision_engine.py +0 -643
- isa_model/training/intelligent/intelligent_factory.py +0 -888
- isa_model/training/intelligent/knowledge_base.py +0 -751
- isa_model/training/intelligent/resource_optimizer.py +0 -839
- isa_model/training/intelligent/task_classifier.py +0 -576
- isa_model/training/storage/__init__.py +0 -24
- isa_model/training/storage/core_integration.py +0 -439
- isa_model/training/storage/training_repository.py +0 -552
- isa_model/training/storage/training_storage.py +0 -628
- isa_model-0.4.0.dist-info/RECORD +0 -182
- /isa_model/deployment/{cloud/modal → modal/services/audio}/isa_audio_chatTTS_service.py +0 -0
- /isa_model/deployment/{cloud/modal → modal/services/audio}/isa_audio_fish_service.py +0 -0
- /isa_model/deployment/{cloud/modal → modal/services/audio}/isa_audio_openvoice_service.py +0 -0
- /isa_model/deployment/{cloud/modal → modal/services/audio}/isa_audio_service_v2.py +0 -0
- /isa_model/deployment/{cloud/modal → modal/services/embedding}/isa_embed_rerank_service.py +0 -0
- /isa_model/deployment/{cloud/modal → modal/services/video}/isa_video_hunyuan_service.py +0 -0
- /isa_model/deployment/{cloud/modal → modal/services/vision}/isa_vision_ocr_service.py +0 -0
- /isa_model/deployment/{cloud/modal → modal/services/vision}/isa_vision_qwen25_service.py +0 -0
- /isa_model/deployment/{cloud/modal → modal/services/vision}/isa_vision_table_service.py +0 -0
- /isa_model/deployment/{cloud/modal → modal/services/vision}/isa_vision_ui_service.py +0 -0
- /isa_model/deployment/{cloud/modal → modal/services/vision}/isa_vision_ui_service_optimized.py +0 -0
- /isa_model/deployment/{services → modal/services/vision}/simple_auto_deploy_vision_service.py +0 -0
- {isa_model-0.4.0.dist-info → isa_model-0.4.3.dist-info}/WHEEL +0 -0
- {isa_model-0.4.0.dist-info → isa_model-0.4.3.dist-info}/top_level.txt +0 -0
@@ -1,13 +1,172 @@
|
|
1
1
|
from abc import ABC, abstractmethod
|
2
2
|
from typing import Dict, Any, List, Union, Optional, BinaryIO
|
3
|
+
import aiohttp
|
4
|
+
import asyncio
|
5
|
+
import tempfile
|
6
|
+
import os
|
7
|
+
import logging
|
8
|
+
from io import BytesIO
|
3
9
|
from isa_model.inference.services.base_service import BaseService
|
4
10
|
|
11
|
+
logger = logging.getLogger(__name__)
|
12
|
+
|
5
13
|
class BaseSTTService(BaseService):
|
6
|
-
"""Base class for Speech-to-Text services with unified task dispatch"""
|
14
|
+
"""Base class for Speech-to-Text services with unified task dispatch and URL support"""
|
15
|
+
|
16
|
+
async def _prepare_audio_input(self, audio_input: Union[str, BinaryIO, bytes]) -> Union[str, BinaryIO]:
|
17
|
+
"""
|
18
|
+
Prepare audio input by handling URLs, file paths, bytes data, and file objects
|
19
|
+
|
20
|
+
Args:
|
21
|
+
audio_input: Audio input (URL, file path, bytes data, or file object)
|
22
|
+
|
23
|
+
Returns:
|
24
|
+
Prepared audio input (local file path or file object)
|
25
|
+
"""
|
26
|
+
if isinstance(audio_input, bytes):
|
27
|
+
# Handle bytes data from API uploads
|
28
|
+
logger.info(f"Converting bytes data to temporary file ({len(audio_input)} bytes)")
|
29
|
+
return await self._save_bytes_to_temp_file(audio_input)
|
30
|
+
elif isinstance(audio_input, str):
|
31
|
+
# Check if it's a URL
|
32
|
+
if audio_input.startswith(('http://', 'https://')):
|
33
|
+
logger.info(f"Downloading audio from URL: {audio_input}")
|
34
|
+
return await self._download_audio_url(audio_input)
|
35
|
+
else:
|
36
|
+
# Regular file path or base64 string
|
37
|
+
return audio_input
|
38
|
+
else:
|
39
|
+
# Already a file object
|
40
|
+
return audio_input
|
41
|
+
|
42
|
+
async def _prepare_audio_input_with_context(self, audio_input: Union[str, BinaryIO, bytes], context: Dict[str, Any]) -> Union[str, BinaryIO]:
|
43
|
+
"""
|
44
|
+
Prepare audio input with additional context from kwargs
|
45
|
+
|
46
|
+
Args:
|
47
|
+
audio_input: Audio input (URL, file path, bytes data, or file object)
|
48
|
+
context: Additional context including filename, content_type
|
49
|
+
|
50
|
+
Returns:
|
51
|
+
Prepared audio input (local file path or file object)
|
52
|
+
"""
|
53
|
+
if isinstance(audio_input, bytes):
|
54
|
+
# Handle bytes data from API uploads
|
55
|
+
filename = context.get('filename')
|
56
|
+
content_type = context.get('content_type')
|
57
|
+
logger.info(f"Converting bytes data to temporary file ({len(audio_input)} bytes), filename={filename}, content_type={content_type}")
|
58
|
+
return await self._save_bytes_to_temp_file(audio_input, filename, content_type)
|
59
|
+
else:
|
60
|
+
return await self._prepare_audio_input(audio_input)
|
61
|
+
|
62
|
+
async def _download_audio_url(self, url: str) -> str:
|
63
|
+
"""
|
64
|
+
Download audio file from URL to temporary file
|
65
|
+
|
66
|
+
Args:
|
67
|
+
url: HTTP/HTTPS URL to audio file
|
68
|
+
|
69
|
+
Returns:
|
70
|
+
Path to downloaded temporary file
|
71
|
+
|
72
|
+
Raises:
|
73
|
+
Exception: If download fails
|
74
|
+
"""
|
75
|
+
try:
|
76
|
+
async with aiohttp.ClientSession() as session:
|
77
|
+
async with session.get(url) as response:
|
78
|
+
if response.status != 200:
|
79
|
+
raise Exception(f"Failed to download audio: HTTP {response.status}")
|
80
|
+
|
81
|
+
# Get content type to determine file extension
|
82
|
+
content_type = response.headers.get('Content-Type', '')
|
83
|
+
file_ext = self._get_file_extension_from_content_type(content_type)
|
84
|
+
|
85
|
+
# Create temporary file
|
86
|
+
temp_file = tempfile.NamedTemporaryFile(
|
87
|
+
delete=False,
|
88
|
+
suffix=file_ext,
|
89
|
+
prefix='audio_download_'
|
90
|
+
)
|
91
|
+
|
92
|
+
# Download and save
|
93
|
+
async for chunk in response.content.iter_chunked(8192):
|
94
|
+
temp_file.write(chunk)
|
95
|
+
|
96
|
+
temp_file.close()
|
97
|
+
logger.info(f"Downloaded audio to temporary file: {temp_file.name}")
|
98
|
+
return temp_file.name
|
99
|
+
|
100
|
+
except Exception as e:
|
101
|
+
logger.error(f"Failed to download audio from URL {url}: {e}")
|
102
|
+
raise Exception(f"Audio URL download failed: {e}") from e
|
103
|
+
|
104
|
+
def _get_file_extension_from_content_type(self, content_type: str) -> str:
|
105
|
+
"""Get appropriate file extension from Content-Type header"""
|
106
|
+
content_type_map = {
|
107
|
+
'audio/mpeg': '.mp3',
|
108
|
+
'audio/mp3': '.mp3',
|
109
|
+
'audio/wav': '.wav',
|
110
|
+
'audio/wave': '.wav',
|
111
|
+
'audio/x-wav': '.wav',
|
112
|
+
'audio/flac': '.flac',
|
113
|
+
'audio/ogg': '.ogg',
|
114
|
+
'audio/m4a': '.m4a',
|
115
|
+
'audio/mp4': '.mp4',
|
116
|
+
'audio/webm': '.webm'
|
117
|
+
}
|
118
|
+
return content_type_map.get(content_type.lower(), '.audio')
|
119
|
+
|
120
|
+
async def _save_bytes_to_temp_file(self, audio_bytes: bytes, filename: Optional[str] = None, content_type: Optional[str] = None) -> str:
|
121
|
+
"""
|
122
|
+
Save audio bytes data to temporary file
|
123
|
+
|
124
|
+
Args:
|
125
|
+
audio_bytes: Audio data as bytes
|
126
|
+
filename: Optional filename to determine extension
|
127
|
+
content_type: Optional content type to determine extension
|
128
|
+
|
129
|
+
Returns:
|
130
|
+
Path to temporary file containing audio data
|
131
|
+
"""
|
132
|
+
try:
|
133
|
+
# Determine file extension from filename or content type
|
134
|
+
suffix = '.mp3' # Default
|
135
|
+
if filename and '.' in filename:
|
136
|
+
suffix = '.' + filename.split('.')[-1]
|
137
|
+
elif content_type:
|
138
|
+
suffix = self._get_file_extension_from_content_type(content_type)
|
139
|
+
|
140
|
+
# Create temporary file with proper audio extension
|
141
|
+
temp_file = tempfile.NamedTemporaryFile(
|
142
|
+
delete=False,
|
143
|
+
suffix=suffix,
|
144
|
+
prefix='audio_bytes_'
|
145
|
+
)
|
146
|
+
|
147
|
+
# Write bytes data
|
148
|
+
temp_file.write(audio_bytes)
|
149
|
+
temp_file.close()
|
150
|
+
|
151
|
+
logger.info(f"Saved {len(audio_bytes)} bytes to temporary file: {temp_file.name}")
|
152
|
+
return temp_file.name
|
153
|
+
|
154
|
+
except Exception as e:
|
155
|
+
logger.error(f"Failed to save audio bytes to temporary file: {e}")
|
156
|
+
raise Exception(f"Audio bytes save failed: {e}") from e
|
157
|
+
|
158
|
+
def _cleanup_temp_file(self, file_path: str):
|
159
|
+
"""Clean up temporary downloaded file"""
|
160
|
+
try:
|
161
|
+
if file_path and file_path.startswith(tempfile.gettempdir()):
|
162
|
+
os.unlink(file_path)
|
163
|
+
logger.debug(f"Cleaned up temporary file: {file_path}")
|
164
|
+
except Exception as e:
|
165
|
+
logger.warning(f"Failed to cleanup temporary file {file_path}: {e}")
|
7
166
|
|
8
167
|
async def invoke(
|
9
168
|
self,
|
10
|
-
audio_input: Union[str, BinaryIO, List[Union[str, BinaryIO]]],
|
169
|
+
audio_input: Union[str, BinaryIO, bytes, List[Union[str, BinaryIO, bytes]]],
|
11
170
|
task: Optional[str] = None,
|
12
171
|
**kwargs
|
13
172
|
) -> Union[Dict[str, Any], List[Dict[str, Any]]]:
|
@@ -30,33 +189,47 @@ class BaseSTTService(BaseService):
|
|
30
189
|
# ==================== 语音转文本类任务 ====================
|
31
190
|
if task == "transcribe":
|
32
191
|
if isinstance(audio_input, list):
|
192
|
+
# Prepare all audio inputs (handle URLs)
|
193
|
+
prepared_inputs = []
|
194
|
+
for audio in audio_input:
|
195
|
+
prepared_input = await self._prepare_audio_input_with_context(audio, kwargs)
|
196
|
+
prepared_inputs.append(prepared_input)
|
33
197
|
return await self.transcribe_batch(
|
34
|
-
|
198
|
+
prepared_inputs,
|
35
199
|
kwargs.get("language"),
|
36
200
|
kwargs.get("prompt")
|
37
201
|
)
|
38
202
|
else:
|
203
|
+
# Prepare single audio input (handle URLs)
|
204
|
+
prepared_input = await self._prepare_audio_input_with_context(audio_input, kwargs)
|
39
205
|
return await self.transcribe(
|
40
|
-
|
206
|
+
prepared_input,
|
41
207
|
kwargs.get("language"),
|
42
208
|
kwargs.get("prompt")
|
43
209
|
)
|
44
210
|
elif task == "translate":
|
45
211
|
if isinstance(audio_input, list):
|
46
212
|
raise ValueError("translate task requires single audio input")
|
47
|
-
|
213
|
+
prepared_input = await self._prepare_audio_input_with_context(audio_input, kwargs)
|
214
|
+
return await self.translate(prepared_input)
|
48
215
|
elif task == "batch_transcribe":
|
49
216
|
if not isinstance(audio_input, list):
|
50
217
|
audio_input = [audio_input]
|
218
|
+
# Prepare all audio inputs (handle URLs)
|
219
|
+
prepared_inputs = []
|
220
|
+
for audio in audio_input:
|
221
|
+
prepared_input = await self._prepare_audio_input_with_context(audio, kwargs)
|
222
|
+
prepared_inputs.append(prepared_input)
|
51
223
|
return await self.transcribe_batch(
|
52
|
-
|
224
|
+
prepared_inputs,
|
53
225
|
kwargs.get("language"),
|
54
226
|
kwargs.get("prompt")
|
55
227
|
)
|
56
228
|
elif task == "detect_language":
|
57
229
|
if isinstance(audio_input, list):
|
58
230
|
raise ValueError("detect_language task requires single audio input")
|
59
|
-
|
231
|
+
prepared_input = await self._prepare_audio_input_with_context(audio_input, kwargs)
|
232
|
+
return await self.detect_language(prepared_input)
|
60
233
|
else:
|
61
234
|
raise NotImplementedError(f"{self.__class__.__name__} does not support task: {task}")
|
62
235
|
|
@@ -72,7 +245,7 @@ class BaseSTTService(BaseService):
|
|
72
245
|
@abstractmethod
|
73
246
|
async def transcribe(
|
74
247
|
self,
|
75
|
-
audio_file: Union[str, BinaryIO],
|
248
|
+
audio_file: Union[str, BinaryIO, bytes],
|
76
249
|
language: Optional[str] = None,
|
77
250
|
prompt: Optional[str] = None
|
78
251
|
) -> Dict[str, Any]:
|
@@ -96,7 +269,7 @@ class BaseSTTService(BaseService):
|
|
96
269
|
@abstractmethod
|
97
270
|
async def translate(
|
98
271
|
self,
|
99
|
-
audio_file: Union[str, BinaryIO]
|
272
|
+
audio_file: Union[str, BinaryIO, bytes]
|
100
273
|
) -> Dict[str, Any]:
|
101
274
|
"""
|
102
275
|
Translate audio file to English text
|
@@ -115,7 +288,7 @@ class BaseSTTService(BaseService):
|
|
115
288
|
@abstractmethod
|
116
289
|
async def transcribe_batch(
|
117
290
|
self,
|
118
|
-
audio_files: List[Union[str, BinaryIO]],
|
291
|
+
audio_files: List[Union[str, BinaryIO, bytes]],
|
119
292
|
language: Optional[str] = None,
|
120
293
|
prompt: Optional[str] = None
|
121
294
|
) -> List[Dict[str, Any]]:
|
@@ -133,7 +306,7 @@ class BaseSTTService(BaseService):
|
|
133
306
|
pass
|
134
307
|
|
135
308
|
@abstractmethod
|
136
|
-
async def detect_language(self, audio_file: Union[str, BinaryIO]) -> Dict[str, Any]:
|
309
|
+
async def detect_language(self, audio_file: Union[str, BinaryIO, bytes]) -> Dict[str, Any]:
|
137
310
|
"""
|
138
311
|
Detect language of audio file
|
139
312
|
|
@@ -47,7 +47,7 @@ class OpenAISTTService(BaseSTTService):
|
|
47
47
|
wait=wait_exponential(multiplier=1, min=4, max=10),
|
48
48
|
reraise=True
|
49
49
|
)
|
50
|
-
async def transcribe(self, audio_file: Union[str, BinaryIO], language: Optional[str] = None, prompt: Optional[str] = None) -> Dict[str, Any]:
|
50
|
+
async def transcribe(self, audio_file: Union[str, BinaryIO, bytes], language: Optional[str] = None, prompt: Optional[str] = None, **kwargs) -> Dict[str, Any]:
|
51
51
|
"""
|
52
52
|
Transcribe audio file to text using OpenAI's Whisper model.
|
53
53
|
|
@@ -73,8 +73,24 @@ class OpenAISTTService(BaseSTTService):
|
|
73
73
|
if prompt:
|
74
74
|
transcription_params["prompt"] = prompt
|
75
75
|
|
76
|
-
# Handle file input - support base64 strings, file paths, and file objects
|
77
|
-
if isinstance(audio_file,
|
76
|
+
# Handle file input - support bytes, base64 strings, file paths, and file objects
|
77
|
+
if isinstance(audio_file, bytes):
|
78
|
+
# Handle bytes data directly
|
79
|
+
logger.info(f"Processing bytes audio data ({len(audio_file)} bytes)")
|
80
|
+
from io import BytesIO
|
81
|
+
audio_buffer = BytesIO(audio_file)
|
82
|
+
|
83
|
+
# Use filename from kwargs if provided, otherwise default to .mp3
|
84
|
+
filename = kwargs.get('filename', 'audio.mp3')
|
85
|
+
if filename and not filename.endswith(('.mp3', '.wav', '.m4a', '.flac', '.ogg', '.webm', '.mp4')):
|
86
|
+
filename += '.mp3' # Add extension if missing
|
87
|
+
audio_buffer.name = filename
|
88
|
+
logger.info(f"Using filename: {filename}")
|
89
|
+
transcription = await self.client.audio.transcriptions.create(
|
90
|
+
file=audio_buffer,
|
91
|
+
**transcription_params
|
92
|
+
)
|
93
|
+
elif isinstance(audio_file, str):
|
78
94
|
# Check if it's a base64 string or file path
|
79
95
|
if len(audio_file) > 100 and not os.path.exists(audio_file):
|
80
96
|
# Likely a base64 string
|
@@ -147,7 +163,7 @@ class OpenAISTTService(BaseSTTService):
|
|
147
163
|
wait=wait_exponential(multiplier=1, min=4, max=10),
|
148
164
|
reraise=True
|
149
165
|
)
|
150
|
-
async def translate(self, audio_file: Union[str, BinaryIO]) -> Dict[str, Any]:
|
166
|
+
async def translate(self, audio_file: Union[str, BinaryIO, bytes]) -> Dict[str, Any]:
|
151
167
|
"""
|
152
168
|
Translate audio file to English text using OpenAI's Whisper model.
|
153
169
|
|
@@ -211,7 +227,7 @@ class OpenAISTTService(BaseSTTService):
|
|
211
227
|
logger.error(f"Translation failed: {e}")
|
212
228
|
raise
|
213
229
|
|
214
|
-
async def transcribe_batch(self, audio_files: List[Union[str, BinaryIO]], language: Optional[str] = None, prompt: Optional[str] = None) -> List[Dict[str, Any]]:
|
230
|
+
async def transcribe_batch(self, audio_files: List[Union[str, BinaryIO, bytes]], language: Optional[str] = None, prompt: Optional[str] = None) -> List[Dict[str, Any]]:
|
215
231
|
"""
|
216
232
|
Transcribe multiple audio files in batch.
|
217
233
|
|
@@ -238,7 +254,7 @@ class OpenAISTTService(BaseSTTService):
|
|
238
254
|
|
239
255
|
return results
|
240
256
|
|
241
|
-
async def detect_language(self, audio_file: Union[str, BinaryIO]) -> Dict[str, Any]:
|
257
|
+
async def detect_language(self, audio_file: Union[str, BinaryIO, bytes]) -> Dict[str, Any]:
|
242
258
|
"""
|
243
259
|
Detect the language of an audio file.
|
244
260
|
|
@@ -0,0 +1,277 @@
|
|
1
|
+
#!/usr/bin/env python
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
|
4
|
+
"""
|
5
|
+
Custom Model Manager - Handles registration and management of custom trained models
|
6
|
+
Provides integration for models trained through ISA Model training pipeline
|
7
|
+
"""
|
8
|
+
|
9
|
+
import logging
|
10
|
+
import json
|
11
|
+
import os
|
12
|
+
from typing import Dict, List, Any, Optional
|
13
|
+
from datetime import datetime
|
14
|
+
from dataclasses import dataclass, asdict
|
15
|
+
|
16
|
+
logger = logging.getLogger(__name__)
|
17
|
+
|
18
|
+
@dataclass
|
19
|
+
class CustomModelInfo:
|
20
|
+
"""Information about a custom model"""
|
21
|
+
model_id: str
|
22
|
+
model_name: str
|
23
|
+
model_type: str # 'text', 'vision', 'audio', etc.
|
24
|
+
provider: str
|
25
|
+
base_model: str # The base model this was fine-tuned from
|
26
|
+
training_date: str
|
27
|
+
model_path: str # Local path or HuggingFace repo
|
28
|
+
metadata: Dict[str, Any]
|
29
|
+
capabilities: List[str]
|
30
|
+
performance_metrics: Optional[Dict[str, float]] = None
|
31
|
+
deployment_config: Optional[Dict[str, Any]] = None
|
32
|
+
|
33
|
+
def to_dict(self) -> Dict[str, Any]:
|
34
|
+
return asdict(self)
|
35
|
+
|
36
|
+
class CustomModelManager:
|
37
|
+
"""
|
38
|
+
Manages custom trained models in the ISA Model ecosystem
|
39
|
+
Handles registration, discovery, and integration of custom models
|
40
|
+
"""
|
41
|
+
|
42
|
+
def __init__(self, models_registry_path: str = None):
|
43
|
+
self.models_registry_path = models_registry_path or os.path.join(
|
44
|
+
os.path.expanduser("~"), ".isa_model", "custom_models.json"
|
45
|
+
)
|
46
|
+
self._models: Dict[str, CustomModelInfo] = {}
|
47
|
+
self._load_models_registry()
|
48
|
+
|
49
|
+
def _load_models_registry(self):
|
50
|
+
"""Load custom models registry from file"""
|
51
|
+
if os.path.exists(self.models_registry_path):
|
52
|
+
try:
|
53
|
+
with open(self.models_registry_path, 'r', encoding='utf-8') as f:
|
54
|
+
models_data = json.load(f)
|
55
|
+
|
56
|
+
for model_data in models_data.get('models', []):
|
57
|
+
model_info = CustomModelInfo(**model_data)
|
58
|
+
self._models[model_info.model_id] = model_info
|
59
|
+
|
60
|
+
logger.info(f"Loaded {len(self._models)} custom models from registry")
|
61
|
+
except Exception as e:
|
62
|
+
logger.warning(f"Failed to load models registry: {e}")
|
63
|
+
self._models = {}
|
64
|
+
else:
|
65
|
+
# Create default registry with some ISA models
|
66
|
+
self._create_default_registry()
|
67
|
+
|
68
|
+
def _create_default_registry(self):
|
69
|
+
"""Create default registry with ISA models"""
|
70
|
+
default_models = [
|
71
|
+
CustomModelInfo(
|
72
|
+
model_id="isa-llm-service",
|
73
|
+
model_name="ISA LLM Service",
|
74
|
+
model_type="text",
|
75
|
+
provider="isa",
|
76
|
+
base_model="DialoGPT-small",
|
77
|
+
training_date="2024-12-19",
|
78
|
+
model_path="modal://isa-llm-inference",
|
79
|
+
metadata={
|
80
|
+
"description": "ISA custom LLM service with fallback support",
|
81
|
+
"parameters": "124M",
|
82
|
+
"context_length": 1024,
|
83
|
+
"languages": ["en", "zh"]
|
84
|
+
},
|
85
|
+
capabilities=["chat", "text_generation", "conversation"],
|
86
|
+
performance_metrics={
|
87
|
+
"perplexity": 3.2,
|
88
|
+
"bleu_score": 0.75,
|
89
|
+
"response_time_ms": 850
|
90
|
+
},
|
91
|
+
deployment_config={
|
92
|
+
"platform": "modal",
|
93
|
+
"gpu_type": "A10G",
|
94
|
+
"memory_gb": 16,
|
95
|
+
"concurrent_requests": 5
|
96
|
+
}
|
97
|
+
),
|
98
|
+
CustomModelInfo(
|
99
|
+
model_id="xenodennis/dialoGPT-small-20241219-v1",
|
100
|
+
model_name="ISA Fine-tuned DialoGPT",
|
101
|
+
model_type="text",
|
102
|
+
provider="huggingface",
|
103
|
+
base_model="microsoft/DialoGPT-small",
|
104
|
+
training_date="2024-12-19",
|
105
|
+
model_path="xenodennis/dialoGPT-small-20241219-v1",
|
106
|
+
metadata={
|
107
|
+
"description": "DialoGPT model fine-tuned with ISA training pipeline",
|
108
|
+
"parameters": "124M",
|
109
|
+
"trainable_parameters": "294K (LoRA)",
|
110
|
+
"training_steps": 1000,
|
111
|
+
"languages": ["en", "zh"]
|
112
|
+
},
|
113
|
+
capabilities=["chat", "text_generation", "dialogue"],
|
114
|
+
performance_metrics={
|
115
|
+
"final_loss": 2.1234,
|
116
|
+
"eval_loss": 2.3456,
|
117
|
+
"training_time_minutes": 15
|
118
|
+
}
|
119
|
+
),
|
120
|
+
CustomModelInfo(
|
121
|
+
model_id="isa-custom-embeddings",
|
122
|
+
model_name="ISA Custom Embeddings",
|
123
|
+
model_type="embedding",
|
124
|
+
provider="isa",
|
125
|
+
base_model="sentence-transformers/all-MiniLM-L6-v2",
|
126
|
+
training_date="2024-12-19",
|
127
|
+
model_path="local://models/isa-embeddings",
|
128
|
+
metadata={
|
129
|
+
"description": "Custom embeddings trained on ISA domain data",
|
130
|
+
"dimensions": 384,
|
131
|
+
"max_sequence_length": 512
|
132
|
+
},
|
133
|
+
capabilities=["embed", "similarity", "clustering"]
|
134
|
+
)
|
135
|
+
]
|
136
|
+
|
137
|
+
for model in default_models:
|
138
|
+
self._models[model.model_id] = model
|
139
|
+
|
140
|
+
self._save_models_registry()
|
141
|
+
logger.info(f"Created default registry with {len(default_models)} models")
|
142
|
+
|
143
|
+
def _save_models_registry(self):
|
144
|
+
"""Save models registry to file"""
|
145
|
+
try:
|
146
|
+
os.makedirs(os.path.dirname(self.models_registry_path), exist_ok=True)
|
147
|
+
|
148
|
+
registry_data = {
|
149
|
+
"version": "1.0",
|
150
|
+
"last_updated": datetime.now().isoformat(),
|
151
|
+
"models": [model.to_dict() for model in self._models.values()]
|
152
|
+
}
|
153
|
+
|
154
|
+
with open(self.models_registry_path, 'w', encoding='utf-8') as f:
|
155
|
+
json.dump(registry_data, f, indent=2, ensure_ascii=False)
|
156
|
+
|
157
|
+
logger.debug(f"Saved models registry to {self.models_registry_path}")
|
158
|
+
except Exception as e:
|
159
|
+
logger.error(f"Failed to save models registry: {e}")
|
160
|
+
|
161
|
+
def register_model(self, model_info: CustomModelInfo) -> bool:
|
162
|
+
"""Register a new custom model"""
|
163
|
+
try:
|
164
|
+
self._models[model_info.model_id] = model_info
|
165
|
+
self._save_models_registry()
|
166
|
+
logger.info(f"Registered custom model: {model_info.model_id}")
|
167
|
+
return True
|
168
|
+
except Exception as e:
|
169
|
+
logger.error(f"Failed to register model {model_info.model_id}: {e}")
|
170
|
+
return False
|
171
|
+
|
172
|
+
def unregister_model(self, model_id: str) -> bool:
|
173
|
+
"""Unregister a custom model"""
|
174
|
+
if model_id in self._models:
|
175
|
+
del self._models[model_id]
|
176
|
+
self._save_models_registry()
|
177
|
+
logger.info(f"Unregistered custom model: {model_id}")
|
178
|
+
return True
|
179
|
+
return False
|
180
|
+
|
181
|
+
def get_model(self, model_id: str) -> Optional[CustomModelInfo]:
|
182
|
+
"""Get custom model information"""
|
183
|
+
return self._models.get(model_id)
|
184
|
+
|
185
|
+
def list_models(self, model_type: str = None, provider: str = None) -> List[CustomModelInfo]:
|
186
|
+
"""List custom models with optional filtering"""
|
187
|
+
models = list(self._models.values())
|
188
|
+
|
189
|
+
if model_type:
|
190
|
+
models = [m for m in models if m.model_type == model_type]
|
191
|
+
|
192
|
+
if provider:
|
193
|
+
models = [m for m in models if m.provider == provider]
|
194
|
+
|
195
|
+
return models
|
196
|
+
|
197
|
+
def get_models_for_api(self) -> List[Dict[str, Any]]:
|
198
|
+
"""Get models in API format for model listing"""
|
199
|
+
api_models = []
|
200
|
+
|
201
|
+
for model in self._models.values():
|
202
|
+
api_model = {
|
203
|
+
"model_id": model.model_id,
|
204
|
+
"service_type": model.model_type,
|
205
|
+
"provider": model.provider,
|
206
|
+
"description": model.metadata.get("description", ""),
|
207
|
+
"capabilities": model.capabilities,
|
208
|
+
"custom": True,
|
209
|
+
"base_model": model.base_model,
|
210
|
+
"training_date": model.training_date
|
211
|
+
}
|
212
|
+
|
213
|
+
# Add performance metrics if available
|
214
|
+
if model.performance_metrics:
|
215
|
+
api_model["performance"] = model.performance_metrics
|
216
|
+
|
217
|
+
api_models.append(api_model)
|
218
|
+
|
219
|
+
return api_models
|
220
|
+
|
221
|
+
def search_models(self, query: str) -> List[CustomModelInfo]:
|
222
|
+
"""Search custom models by query"""
|
223
|
+
query_lower = query.lower()
|
224
|
+
matching_models = []
|
225
|
+
|
226
|
+
for model in self._models.values():
|
227
|
+
# Search in model_id, name, description, and capabilities
|
228
|
+
searchable_text = f"{model.model_id} {model.model_name} {model.metadata.get('description', '')} {' '.join(model.capabilities)}".lower()
|
229
|
+
|
230
|
+
if query_lower in searchable_text:
|
231
|
+
matching_models.append(model)
|
232
|
+
|
233
|
+
return matching_models
|
234
|
+
|
235
|
+
def get_deployment_config(self, model_id: str) -> Optional[Dict[str, Any]]:
|
236
|
+
"""Get deployment configuration for a model"""
|
237
|
+
model = self.get_model(model_id)
|
238
|
+
return model.deployment_config if model else None
|
239
|
+
|
240
|
+
def update_performance_metrics(self, model_id: str, metrics: Dict[str, float]) -> bool:
|
241
|
+
"""Update performance metrics for a model"""
|
242
|
+
model = self.get_model(model_id)
|
243
|
+
if model:
|
244
|
+
model.performance_metrics = metrics
|
245
|
+
self._save_models_registry()
|
246
|
+
return True
|
247
|
+
return False
|
248
|
+
|
249
|
+
def get_provider_models(self, provider: str) -> List[CustomModelInfo]:
|
250
|
+
"""Get all models for a specific provider"""
|
251
|
+
return [model for model in self._models.values() if model.provider == provider]
|
252
|
+
|
253
|
+
def get_stats(self) -> Dict[str, Any]:
|
254
|
+
"""Get statistics about custom models"""
|
255
|
+
models_by_type = {}
|
256
|
+
models_by_provider = {}
|
257
|
+
|
258
|
+
for model in self._models.values():
|
259
|
+
models_by_type[model.model_type] = models_by_type.get(model.model_type, 0) + 1
|
260
|
+
models_by_provider[model.provider] = models_by_provider.get(model.provider, 0) + 1
|
261
|
+
|
262
|
+
return {
|
263
|
+
"total_models": len(self._models),
|
264
|
+
"models_by_type": models_by_type,
|
265
|
+
"models_by_provider": models_by_provider,
|
266
|
+
"registry_path": self.models_registry_path
|
267
|
+
}
|
268
|
+
|
269
|
+
# Global instance
|
270
|
+
_custom_model_manager = None
|
271
|
+
|
272
|
+
def get_custom_model_manager() -> CustomModelManager:
|
273
|
+
"""Get the global custom model manager instance"""
|
274
|
+
global _custom_model_manager
|
275
|
+
if _custom_model_manager is None:
|
276
|
+
_custom_model_manager = CustomModelManager()
|
277
|
+
return _custom_model_manager
|
@@ -4,6 +4,7 @@ import asyncio
|
|
4
4
|
from typing import List, Dict, Any, Optional
|
5
5
|
|
6
6
|
from isa_model.inference.services.embedding.base_embed_service import BaseEmbedService
|
7
|
+
from isa_model.core.config.config_manager import ConfigManager
|
7
8
|
|
8
9
|
logger = logging.getLogger(__name__)
|
9
10
|
|
@@ -21,9 +22,20 @@ class OllamaEmbedService(BaseEmbedService):
|
|
21
22
|
|
22
23
|
# Initialize HTTP client with provider configuration
|
23
24
|
try:
|
24
|
-
|
25
|
-
|
26
|
-
|
25
|
+
config_manager = ConfigManager()
|
26
|
+
# Use Consul discovery with fallback
|
27
|
+
default_base_url = config_manager.get_ollama_url()
|
28
|
+
|
29
|
+
if "base_url" in provider_config:
|
30
|
+
base_url = provider_config["base_url"]
|
31
|
+
else:
|
32
|
+
host = provider_config.get("host", "localhost")
|
33
|
+
port = provider_config.get("port", 11434)
|
34
|
+
base_url = provider_config.get("base_url", f"http://{host}:{port}")
|
35
|
+
|
36
|
+
# Use config manager default (Consul discovery) if still not set
|
37
|
+
if base_url == f"http://localhost:11434":
|
38
|
+
base_url = default_base_url
|
27
39
|
|
28
40
|
self.client = httpx.AsyncClient(base_url=base_url, timeout=30.0)
|
29
41
|
|