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
@@ -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
- audio_input,
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
- audio_input,
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
- return await self.translate(audio_input)
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
- audio_input,
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
- return await self.detect_language(audio_input)
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, str):
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
- host = provider_config.get("host", "localhost")
25
- port = provider_config.get("port", 11434)
26
- base_url = f"http://{host}:{port}"
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