isa-model 0.3.4__py3-none-any.whl → 0.3.6__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/__init__.py +30 -1
- isa_model/client.py +770 -0
- isa_model/core/config/__init__.py +16 -0
- isa_model/core/config/config_manager.py +514 -0
- isa_model/core/config.py +426 -0
- isa_model/core/models/model_billing_tracker.py +476 -0
- isa_model/core/models/model_manager.py +399 -0
- isa_model/core/models/model_repo.py +343 -0
- isa_model/core/pricing_manager.py +426 -0
- isa_model/core/services/__init__.py +19 -0
- isa_model/core/services/intelligent_model_selector.py +547 -0
- isa_model/core/types.py +291 -0
- isa_model/deployment/__init__.py +2 -0
- isa_model/deployment/cloud/__init__.py +9 -0
- isa_model/deployment/cloud/modal/__init__.py +10 -0
- isa_model/deployment/cloud/modal/isa_vision_doc_service.py +766 -0
- isa_model/deployment/cloud/modal/isa_vision_table_service.py +532 -0
- isa_model/deployment/cloud/modal/isa_vision_ui_service.py +406 -0
- isa_model/deployment/cloud/modal/register_models.py +321 -0
- isa_model/deployment/runtime/deployed_service.py +338 -0
- isa_model/deployment/services/__init__.py +9 -0
- isa_model/deployment/services/auto_deploy_vision_service.py +537 -0
- isa_model/deployment/services/model_service.py +332 -0
- isa_model/deployment/services/service_monitor.py +356 -0
- isa_model/deployment/services/service_registry.py +527 -0
- isa_model/eval/__init__.py +80 -44
- isa_model/eval/config/__init__.py +10 -0
- isa_model/eval/config/evaluation_config.py +108 -0
- isa_model/eval/evaluators/__init__.py +18 -0
- isa_model/eval/evaluators/base_evaluator.py +503 -0
- isa_model/eval/evaluators/llm_evaluator.py +472 -0
- isa_model/eval/factory.py +417 -709
- isa_model/eval/infrastructure/__init__.py +24 -0
- isa_model/eval/infrastructure/experiment_tracker.py +466 -0
- isa_model/eval/metrics.py +191 -21
- isa_model/inference/ai_factory.py +187 -387
- isa_model/inference/providers/modal_provider.py +109 -0
- isa_model/inference/providers/yyds_provider.py +108 -0
- isa_model/inference/services/__init__.py +2 -1
- isa_model/inference/services/audio/base_stt_service.py +65 -1
- isa_model/inference/services/audio/base_tts_service.py +75 -1
- isa_model/inference/services/audio/openai_stt_service.py +189 -151
- isa_model/inference/services/audio/openai_tts_service.py +12 -10
- isa_model/inference/services/audio/replicate_tts_service.py +61 -56
- isa_model/inference/services/base_service.py +55 -55
- isa_model/inference/services/embedding/base_embed_service.py +65 -1
- isa_model/inference/services/embedding/ollama_embed_service.py +103 -43
- isa_model/inference/services/embedding/openai_embed_service.py +8 -10
- isa_model/inference/services/helpers/stacked_config.py +148 -0
- isa_model/inference/services/img/__init__.py +18 -0
- isa_model/inference/services/{vision → img}/base_image_gen_service.py +80 -35
- isa_model/inference/services/img/flux_professional_service.py +603 -0
- isa_model/inference/services/img/helpers/base_stacked_service.py +274 -0
- isa_model/inference/services/{vision → img}/replicate_image_gen_service.py +210 -69
- isa_model/inference/services/llm/__init__.py +3 -3
- isa_model/inference/services/llm/base_llm_service.py +519 -35
- isa_model/inference/services/llm/{llm_adapter.py → helpers/llm_adapter.py} +40 -0
- isa_model/inference/services/llm/helpers/llm_prompts.py +258 -0
- isa_model/inference/services/llm/helpers/llm_utils.py +280 -0
- isa_model/inference/services/llm/ollama_llm_service.py +150 -15
- isa_model/inference/services/llm/openai_llm_service.py +134 -31
- isa_model/inference/services/llm/yyds_llm_service.py +255 -0
- isa_model/inference/services/vision/__init__.py +38 -4
- isa_model/inference/services/vision/base_vision_service.py +241 -96
- isa_model/inference/services/vision/disabled/isA_vision_service.py +500 -0
- isa_model/inference/services/vision/doc_analysis_service.py +640 -0
- isa_model/inference/services/vision/helpers/base_stacked_service.py +274 -0
- isa_model/inference/services/vision/helpers/image_utils.py +272 -3
- isa_model/inference/services/vision/helpers/vision_prompts.py +297 -0
- isa_model/inference/services/vision/openai_vision_service.py +109 -170
- isa_model/inference/services/vision/replicate_vision_service.py +508 -0
- isa_model/inference/services/vision/ui_analysis_service.py +823 -0
- isa_model/scripts/register_models.py +370 -0
- isa_model/scripts/register_models_with_embeddings.py +510 -0
- isa_model/serving/__init__.py +19 -0
- isa_model/serving/api/__init__.py +10 -0
- isa_model/serving/api/fastapi_server.py +89 -0
- isa_model/serving/api/middleware/__init__.py +9 -0
- isa_model/serving/api/middleware/request_logger.py +88 -0
- isa_model/serving/api/routes/__init__.py +5 -0
- isa_model/serving/api/routes/health.py +82 -0
- isa_model/serving/api/routes/llm.py +19 -0
- isa_model/serving/api/routes/ui_analysis.py +223 -0
- isa_model/serving/api/routes/unified.py +202 -0
- isa_model/serving/api/routes/vision.py +19 -0
- isa_model/serving/api/schemas/__init__.py +17 -0
- isa_model/serving/api/schemas/common.py +33 -0
- isa_model/serving/api/schemas/ui_analysis.py +78 -0
- {isa_model-0.3.4.dist-info → isa_model-0.3.6.dist-info}/METADATA +4 -1
- isa_model-0.3.6.dist-info/RECORD +147 -0
- isa_model/core/model_manager.py +0 -208
- isa_model/core/model_registry.py +0 -342
- isa_model/inference/billing_tracker.py +0 -406
- isa_model/inference/services/llm/triton_llm_service.py +0 -481
- isa_model/inference/services/vision/ollama_vision_service.py +0 -194
- isa_model-0.3.4.dist-info/RECORD +0 -91
- /isa_model/core/{model_storage.py → models/model_storage.py} +0 -0
- /isa_model/inference/services/{vision → embedding}/helpers/text_splitter.py +0 -0
- {isa_model-0.3.4.dist-info → isa_model-0.3.6.dist-info}/WHEEL +0 -0
- {isa_model-0.3.4.dist-info → isa_model-0.3.6.dist-info}/top_level.txt +0 -0
isa_model/client.py
ADDED
@@ -0,0 +1,770 @@
|
|
1
|
+
#!/usr/bin/env python
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
|
4
|
+
"""
|
5
|
+
ISA Model Client - Unified interface for all AI services
|
6
|
+
Provides intelligent model selection and simplified API
|
7
|
+
"""
|
8
|
+
|
9
|
+
import logging
|
10
|
+
import asyncio
|
11
|
+
from typing import Any, Dict, Optional, List, Union
|
12
|
+
from pathlib import Path
|
13
|
+
import aiohttp
|
14
|
+
|
15
|
+
from isa_model.inference.ai_factory import AIFactory
|
16
|
+
|
17
|
+
try:
|
18
|
+
from isa_model.core.services.intelligent_model_selector import IntelligentModelSelector, get_model_selector
|
19
|
+
INTELLIGENT_SELECTOR_AVAILABLE = True
|
20
|
+
except ImportError:
|
21
|
+
IntelligentModelSelector = None
|
22
|
+
get_model_selector = None
|
23
|
+
INTELLIGENT_SELECTOR_AVAILABLE = False
|
24
|
+
|
25
|
+
logger = logging.getLogger(__name__)
|
26
|
+
|
27
|
+
|
28
|
+
class ISAModelClient:
|
29
|
+
"""
|
30
|
+
Unified ISA Model Client with intelligent model selection
|
31
|
+
|
32
|
+
Usage:
|
33
|
+
client = ISAModelClient()
|
34
|
+
response = await client.invoke("image.jpg", "analyze_image", "vision")
|
35
|
+
response = await client.invoke("Hello world", "generate_speech", "audio")
|
36
|
+
response = await client.invoke("audio.mp3", "transcribe", "audio")
|
37
|
+
"""
|
38
|
+
|
39
|
+
def __init__(self,
|
40
|
+
config: Optional[Dict[str, Any]] = None,
|
41
|
+
mode: str = "local",
|
42
|
+
api_url: Optional[str] = None,
|
43
|
+
api_key: Optional[str] = None):
|
44
|
+
"""Initialize ISA Model Client
|
45
|
+
|
46
|
+
Args:
|
47
|
+
config: Optional configuration override
|
48
|
+
mode: "local" for direct AI Factory, "api" for HTTP API calls
|
49
|
+
api_url: API base URL (required if mode="api")
|
50
|
+
api_key: API key for authentication (optional)
|
51
|
+
"""
|
52
|
+
self.config = config or {}
|
53
|
+
self.mode = mode
|
54
|
+
self.api_url = api_url.rstrip('/') if api_url else None
|
55
|
+
self.api_key = api_key
|
56
|
+
|
57
|
+
# Setup HTTP headers for API mode
|
58
|
+
if self.mode == "api":
|
59
|
+
if not self.api_url:
|
60
|
+
raise ValueError("api_url is required when mode='api'")
|
61
|
+
|
62
|
+
self.headers = {
|
63
|
+
"Content-Type": "application/json",
|
64
|
+
"User-Agent": "ISA-Model-Client/1.0.0"
|
65
|
+
}
|
66
|
+
if self.api_key:
|
67
|
+
self.headers["Authorization"] = f"Bearer {self.api_key}"
|
68
|
+
|
69
|
+
# Initialize AI Factory for local mode
|
70
|
+
if self.mode == "local":
|
71
|
+
self.ai_factory = AIFactory.get_instance()
|
72
|
+
else:
|
73
|
+
self.ai_factory = None
|
74
|
+
|
75
|
+
# Initialize intelligent model selector
|
76
|
+
self.model_selector = None
|
77
|
+
if INTELLIGENT_SELECTOR_AVAILABLE:
|
78
|
+
try:
|
79
|
+
# Initialize asynchronously later
|
80
|
+
self._model_selector_task = None
|
81
|
+
logger.info("Intelligent model selector will be initialized on first use")
|
82
|
+
except Exception as e:
|
83
|
+
logger.warning(f"Failed to setup model selector: {e}")
|
84
|
+
else:
|
85
|
+
logger.info("Intelligent model selector not available, using default selection")
|
86
|
+
|
87
|
+
# Cache for frequently used services
|
88
|
+
self._service_cache: Dict[str, Any] = {}
|
89
|
+
|
90
|
+
logger.info("ISA Model Client initialized")
|
91
|
+
|
92
|
+
async def invoke(
|
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
|
+
) -> Dict[str, Any]:
|
101
|
+
"""
|
102
|
+
Unified invoke method with intelligent model selection
|
103
|
+
|
104
|
+
Args:
|
105
|
+
input_data: Input data (image path, text, audio, etc.)
|
106
|
+
task: Task to perform (analyze_image, generate_speech, transcribe, etc.)
|
107
|
+
service_type: Type of service (vision, audio, text, image, embedding)
|
108
|
+
model_hint: Optional model preference
|
109
|
+
provider_hint: Optional provider preference
|
110
|
+
**kwargs: Additional task-specific parameters
|
111
|
+
|
112
|
+
Returns:
|
113
|
+
Unified response dictionary with result and metadata
|
114
|
+
|
115
|
+
Examples:
|
116
|
+
# Vision tasks
|
117
|
+
await client.invoke("image.jpg", "analyze_image", "vision")
|
118
|
+
await client.invoke("screenshot.png", "detect_ui_elements", "vision")
|
119
|
+
await client.invoke("document.pdf", "extract_table", "vision")
|
120
|
+
|
121
|
+
# Audio tasks
|
122
|
+
await client.invoke("Hello world", "generate_speech", "audio")
|
123
|
+
await client.invoke("audio.mp3", "transcribe", "audio")
|
124
|
+
|
125
|
+
# Text tasks
|
126
|
+
await client.invoke("Translate this text", "translate", "text")
|
127
|
+
await client.invoke("What is AI?", "chat", "text")
|
128
|
+
|
129
|
+
# Image generation
|
130
|
+
await client.invoke("A beautiful sunset", "generate_image", "image")
|
131
|
+
|
132
|
+
# Embedding
|
133
|
+
await client.invoke("Text to embed", "create_embedding", "embedding")
|
134
|
+
"""
|
135
|
+
try:
|
136
|
+
# Route to appropriate mode
|
137
|
+
if self.mode == "api":
|
138
|
+
return await self._invoke_api(
|
139
|
+
input_data=input_data,
|
140
|
+
task=task,
|
141
|
+
service_type=service_type,
|
142
|
+
model_hint=model_hint,
|
143
|
+
provider_hint=provider_hint,
|
144
|
+
**kwargs
|
145
|
+
)
|
146
|
+
else:
|
147
|
+
return await self._invoke_local(
|
148
|
+
input_data=input_data,
|
149
|
+
task=task,
|
150
|
+
service_type=service_type,
|
151
|
+
model_hint=model_hint,
|
152
|
+
provider_hint=provider_hint,
|
153
|
+
**kwargs
|
154
|
+
)
|
155
|
+
|
156
|
+
except Exception as e:
|
157
|
+
logger.error(f"Failed to invoke {task} on {service_type}: {e}")
|
158
|
+
return {
|
159
|
+
"success": False,
|
160
|
+
"error": str(e),
|
161
|
+
"metadata": {
|
162
|
+
"task": task,
|
163
|
+
"service_type": service_type,
|
164
|
+
"input_type": type(input_data).__name__
|
165
|
+
}
|
166
|
+
}
|
167
|
+
|
168
|
+
async def _select_model(
|
169
|
+
self,
|
170
|
+
input_data: Any,
|
171
|
+
task: str,
|
172
|
+
service_type: str,
|
173
|
+
model_hint: Optional[str] = None,
|
174
|
+
provider_hint: Optional[str] = None
|
175
|
+
) -> Dict[str, Any]:
|
176
|
+
"""Select the best model for the given task"""
|
177
|
+
|
178
|
+
# If explicit hints provided, use them
|
179
|
+
if model_hint and provider_hint:
|
180
|
+
return {
|
181
|
+
"model_id": model_hint,
|
182
|
+
"provider": provider_hint,
|
183
|
+
"reason": "User specified"
|
184
|
+
}
|
185
|
+
|
186
|
+
# Use intelligent model selector if available
|
187
|
+
if INTELLIGENT_SELECTOR_AVAILABLE:
|
188
|
+
try:
|
189
|
+
# Initialize model selector if not already done
|
190
|
+
if self.model_selector is None:
|
191
|
+
self.model_selector = await get_model_selector(self.config)
|
192
|
+
|
193
|
+
# Create selection request
|
194
|
+
request = f"{task} for {service_type}"
|
195
|
+
if isinstance(input_data, (str, Path)):
|
196
|
+
request += f" with input: {str(input_data)[:100]}"
|
197
|
+
|
198
|
+
selection = await self.model_selector.select_model(
|
199
|
+
request=request,
|
200
|
+
service_type=service_type,
|
201
|
+
context={
|
202
|
+
"task": task,
|
203
|
+
"input_type": type(input_data).__name__,
|
204
|
+
"provider_hint": provider_hint,
|
205
|
+
"model_hint": model_hint
|
206
|
+
}
|
207
|
+
)
|
208
|
+
|
209
|
+
if selection["success"]:
|
210
|
+
return {
|
211
|
+
"model_id": selection["selected_model"]["model_id"],
|
212
|
+
"provider": selection["selected_model"]["provider"],
|
213
|
+
"reason": selection["selection_reason"]
|
214
|
+
}
|
215
|
+
|
216
|
+
except Exception as e:
|
217
|
+
logger.warning(f"Intelligent selection failed: {e}, using defaults")
|
218
|
+
|
219
|
+
# Fallback to default model selection
|
220
|
+
return self._get_default_model(service_type, task, provider_hint)
|
221
|
+
|
222
|
+
def _get_default_model(
|
223
|
+
self,
|
224
|
+
service_type: str,
|
225
|
+
task: str,
|
226
|
+
provider_hint: Optional[str] = None
|
227
|
+
) -> Dict[str, Any]:
|
228
|
+
"""Get default model for service type and task"""
|
229
|
+
|
230
|
+
defaults = {
|
231
|
+
"vision": {
|
232
|
+
"model_id": "gpt-4o-mini",
|
233
|
+
"provider": "openai"
|
234
|
+
},
|
235
|
+
"audio": {
|
236
|
+
"tts": {"model_id": "tts-1", "provider": "openai"},
|
237
|
+
"stt": {"model_id": "whisper-1", "provider": "openai"},
|
238
|
+
"default": {"model_id": "whisper-1", "provider": "openai"}
|
239
|
+
},
|
240
|
+
"text": {
|
241
|
+
"model_id": "gpt-4.1-mini",
|
242
|
+
"provider": "openai"
|
243
|
+
},
|
244
|
+
"image": {
|
245
|
+
"model_id": "black-forest-labs/flux-schnell",
|
246
|
+
"provider": "replicate"
|
247
|
+
},
|
248
|
+
"embedding": {
|
249
|
+
"model_id": "text-embedding-3-small",
|
250
|
+
"provider": "openai"
|
251
|
+
}
|
252
|
+
}
|
253
|
+
|
254
|
+
# Handle audio service type with task-specific models
|
255
|
+
if service_type == "audio":
|
256
|
+
if "speech" in task or "tts" in task:
|
257
|
+
default = defaults["audio"]["tts"]
|
258
|
+
elif "transcribe" in task or "stt" in task:
|
259
|
+
default = defaults["audio"]["stt"]
|
260
|
+
else:
|
261
|
+
default = defaults["audio"]["default"]
|
262
|
+
else:
|
263
|
+
default = defaults.get(service_type, defaults["vision"])
|
264
|
+
|
265
|
+
# Apply provider hint if provided
|
266
|
+
if provider_hint:
|
267
|
+
default = dict(default)
|
268
|
+
default["provider"] = provider_hint
|
269
|
+
|
270
|
+
return {
|
271
|
+
**default,
|
272
|
+
"reason": "Default selection"
|
273
|
+
}
|
274
|
+
|
275
|
+
async def _get_service(
|
276
|
+
self,
|
277
|
+
service_type: str,
|
278
|
+
model_name: str,
|
279
|
+
provider: str,
|
280
|
+
task: str
|
281
|
+
) -> Any:
|
282
|
+
"""Get appropriate service instance"""
|
283
|
+
|
284
|
+
cache_key = f"{service_type}_{provider}_{model_name}"
|
285
|
+
|
286
|
+
# Check cache first
|
287
|
+
if cache_key in self._service_cache:
|
288
|
+
return self._service_cache[cache_key]
|
289
|
+
|
290
|
+
try:
|
291
|
+
# Route to appropriate AIFactory method
|
292
|
+
if service_type == "vision":
|
293
|
+
service = self.ai_factory.get_vision(model_name, provider)
|
294
|
+
|
295
|
+
elif service_type == "audio":
|
296
|
+
if "speech" in task or "tts" in task:
|
297
|
+
service = self.ai_factory.get_tts(model_name, provider)
|
298
|
+
elif "transcribe" in task or "stt" in task:
|
299
|
+
service = self.ai_factory.get_stt(model_name, provider)
|
300
|
+
else:
|
301
|
+
# Default to STT for unknown audio tasks
|
302
|
+
service = self.ai_factory.get_stt(model_name, provider)
|
303
|
+
|
304
|
+
elif service_type == "text":
|
305
|
+
service = self.ai_factory.get_llm(model_name, provider)
|
306
|
+
|
307
|
+
elif service_type == "image":
|
308
|
+
service = self.ai_factory.get_img("t2i", model_name, provider)
|
309
|
+
|
310
|
+
elif service_type == "embedding":
|
311
|
+
service = self.ai_factory.get_embed(model_name, provider)
|
312
|
+
|
313
|
+
else:
|
314
|
+
raise ValueError(f"Unsupported service type: {service_type}")
|
315
|
+
|
316
|
+
# Cache the service
|
317
|
+
self._service_cache[cache_key] = service
|
318
|
+
return service
|
319
|
+
|
320
|
+
except Exception as e:
|
321
|
+
logger.error(f"Failed to get service {service_type}/{provider}/{model_name}: {e}")
|
322
|
+
raise
|
323
|
+
|
324
|
+
async def _execute_task(
|
325
|
+
self,
|
326
|
+
service: Any,
|
327
|
+
input_data: Any,
|
328
|
+
task: str,
|
329
|
+
service_type: str,
|
330
|
+
**kwargs
|
331
|
+
) -> Any:
|
332
|
+
"""Execute the task using the appropriate service"""
|
333
|
+
|
334
|
+
try:
|
335
|
+
if service_type == "vision":
|
336
|
+
return await self._execute_vision_task(service, input_data, task, **kwargs)
|
337
|
+
|
338
|
+
elif service_type == "audio":
|
339
|
+
return await self._execute_audio_task(service, input_data, task, **kwargs)
|
340
|
+
|
341
|
+
elif service_type == "text":
|
342
|
+
return await self._execute_text_task(service, input_data, task, **kwargs)
|
343
|
+
|
344
|
+
elif service_type == "image":
|
345
|
+
return await self._execute_image_task(service, input_data, task, **kwargs)
|
346
|
+
|
347
|
+
elif service_type == "embedding":
|
348
|
+
return await self._execute_embedding_task(service, input_data, task, **kwargs)
|
349
|
+
|
350
|
+
else:
|
351
|
+
raise ValueError(f"Unsupported service type: {service_type}")
|
352
|
+
|
353
|
+
except Exception as e:
|
354
|
+
logger.error(f"Task execution failed: {e}")
|
355
|
+
raise
|
356
|
+
|
357
|
+
async def _execute_vision_task(self, service, input_data, task, **kwargs):
|
358
|
+
"""Execute vision-related tasks using unified invoke method"""
|
359
|
+
|
360
|
+
# Map common task names to unified task names
|
361
|
+
task_mapping = {
|
362
|
+
"analyze_image": "analyze_image",
|
363
|
+
"detect_ui_elements": "detect_ui",
|
364
|
+
"extract_table": "extract_table",
|
365
|
+
"extract_text": "extract_text",
|
366
|
+
"ocr": "extract_text",
|
367
|
+
"describe": "analyze_image"
|
368
|
+
}
|
369
|
+
|
370
|
+
unified_task = task_mapping.get(task, task)
|
371
|
+
|
372
|
+
# Use unified invoke method with proper parameters
|
373
|
+
return await service.invoke(
|
374
|
+
image=input_data,
|
375
|
+
task=unified_task,
|
376
|
+
**kwargs
|
377
|
+
)
|
378
|
+
|
379
|
+
async def _execute_audio_task(self, service, input_data, task, **kwargs):
|
380
|
+
"""Execute audio-related tasks using unified invoke method"""
|
381
|
+
|
382
|
+
# Map common task names to unified task names
|
383
|
+
task_mapping = {
|
384
|
+
"generate_speech": "synthesize",
|
385
|
+
"text_to_speech": "synthesize",
|
386
|
+
"tts": "synthesize",
|
387
|
+
"transcribe": "transcribe",
|
388
|
+
"speech_to_text": "transcribe",
|
389
|
+
"stt": "transcribe",
|
390
|
+
"translate": "translate",
|
391
|
+
"detect_language": "detect_language"
|
392
|
+
}
|
393
|
+
|
394
|
+
unified_task = task_mapping.get(task, task)
|
395
|
+
|
396
|
+
# Use unified invoke method with correct parameter name based on task type
|
397
|
+
if unified_task in ["synthesize", "text_to_speech", "tts"]:
|
398
|
+
# TTS services expect 'text' parameter
|
399
|
+
return await service.invoke(
|
400
|
+
text=input_data,
|
401
|
+
task=unified_task,
|
402
|
+
**kwargs
|
403
|
+
)
|
404
|
+
else:
|
405
|
+
# STT services expect 'audio_input' parameter
|
406
|
+
return await service.invoke(
|
407
|
+
audio_input=input_data,
|
408
|
+
task=unified_task,
|
409
|
+
**kwargs
|
410
|
+
)
|
411
|
+
|
412
|
+
async def _execute_text_task(self, service, input_data, task, **kwargs):
|
413
|
+
"""Execute text-related tasks using unified invoke method"""
|
414
|
+
|
415
|
+
# Map common task names to unified task names
|
416
|
+
task_mapping = {
|
417
|
+
"chat": "chat",
|
418
|
+
"generate": "generate",
|
419
|
+
"complete": "complete",
|
420
|
+
"translate": "translate",
|
421
|
+
"summarize": "summarize",
|
422
|
+
"analyze": "analyze",
|
423
|
+
"extract": "extract",
|
424
|
+
"classify": "classify"
|
425
|
+
}
|
426
|
+
|
427
|
+
unified_task = task_mapping.get(task, task)
|
428
|
+
|
429
|
+
# Use unified invoke method
|
430
|
+
return await service.invoke(
|
431
|
+
input_data=input_data,
|
432
|
+
task=unified_task,
|
433
|
+
**kwargs
|
434
|
+
)
|
435
|
+
|
436
|
+
async def _execute_image_task(self, service, input_data, task, **kwargs):
|
437
|
+
"""Execute image generation tasks using unified invoke method"""
|
438
|
+
|
439
|
+
# Map common task names to unified task names
|
440
|
+
task_mapping = {
|
441
|
+
"generate_image": "generate",
|
442
|
+
"generate": "generate",
|
443
|
+
"img2img": "img2img",
|
444
|
+
"image_to_image": "img2img",
|
445
|
+
"generate_batch": "generate_batch"
|
446
|
+
}
|
447
|
+
|
448
|
+
unified_task = task_mapping.get(task, task)
|
449
|
+
|
450
|
+
# Use unified invoke method
|
451
|
+
return await service.invoke(
|
452
|
+
prompt=input_data,
|
453
|
+
task=unified_task,
|
454
|
+
**kwargs
|
455
|
+
)
|
456
|
+
|
457
|
+
async def _execute_embedding_task(self, service, input_data, task, **kwargs):
|
458
|
+
"""Execute embedding tasks using unified invoke method"""
|
459
|
+
|
460
|
+
# Map common task names to unified task names
|
461
|
+
task_mapping = {
|
462
|
+
"create_embedding": "embed",
|
463
|
+
"embed": "embed",
|
464
|
+
"embed_batch": "embed_batch",
|
465
|
+
"chunk_and_embed": "chunk_and_embed",
|
466
|
+
"similarity": "similarity",
|
467
|
+
"find_similar": "find_similar"
|
468
|
+
}
|
469
|
+
|
470
|
+
unified_task = task_mapping.get(task, task)
|
471
|
+
|
472
|
+
# Use unified invoke method
|
473
|
+
return await service.invoke(
|
474
|
+
input_data=input_data,
|
475
|
+
task=unified_task,
|
476
|
+
**kwargs
|
477
|
+
)
|
478
|
+
|
479
|
+
def clear_cache(self):
|
480
|
+
"""Clear service cache"""
|
481
|
+
self._service_cache.clear()
|
482
|
+
logger.info("Service cache cleared")
|
483
|
+
|
484
|
+
async def get_available_models(self, service_type: Optional[str] = None) -> List[Dict[str, Any]]:
|
485
|
+
"""Get list of available models
|
486
|
+
|
487
|
+
Args:
|
488
|
+
service_type: Optional filter by service type
|
489
|
+
|
490
|
+
Returns:
|
491
|
+
List of available models with metadata
|
492
|
+
"""
|
493
|
+
if INTELLIGENT_SELECTOR_AVAILABLE:
|
494
|
+
try:
|
495
|
+
if self.model_selector is None:
|
496
|
+
self.model_selector = await get_model_selector(self.config)
|
497
|
+
return await self.model_selector.get_available_models(service_type)
|
498
|
+
except Exception as e:
|
499
|
+
logger.error(f"Failed to get available models: {e}")
|
500
|
+
return []
|
501
|
+
else:
|
502
|
+
return []
|
503
|
+
|
504
|
+
async def health_check(self) -> Dict[str, Any]:
|
505
|
+
"""Check health of client and underlying services
|
506
|
+
|
507
|
+
Returns:
|
508
|
+
Health status dictionary
|
509
|
+
"""
|
510
|
+
try:
|
511
|
+
health_status = {
|
512
|
+
"client": "healthy",
|
513
|
+
"ai_factory": "healthy" if self.ai_factory else "unavailable",
|
514
|
+
"model_selector": "healthy" if self.model_selector else "unavailable",
|
515
|
+
"services": {}
|
516
|
+
}
|
517
|
+
|
518
|
+
# Check a few key services
|
519
|
+
test_services = [
|
520
|
+
("vision", "openai", "gpt-4.1-mini"),
|
521
|
+
("audio", "openai", "whisper-1"),
|
522
|
+
("text", "openai", "gpt-4.1-mini")
|
523
|
+
]
|
524
|
+
|
525
|
+
for service_type, provider, model in test_services:
|
526
|
+
try:
|
527
|
+
await self._get_service(service_type, model, provider, "test")
|
528
|
+
health_status["services"][f"{service_type}_{provider}"] = "healthy"
|
529
|
+
except Exception as e:
|
530
|
+
health_status["services"][f"{service_type}_{provider}"] = f"error: {str(e)}"
|
531
|
+
|
532
|
+
return health_status
|
533
|
+
|
534
|
+
except Exception as e:
|
535
|
+
return {
|
536
|
+
"client": "error",
|
537
|
+
"error": str(e)
|
538
|
+
}
|
539
|
+
|
540
|
+
async def _invoke_local(
|
541
|
+
self,
|
542
|
+
input_data: Union[str, bytes, Path, Dict[str, Any]],
|
543
|
+
task: str,
|
544
|
+
service_type: str,
|
545
|
+
model_hint: Optional[str] = None,
|
546
|
+
provider_hint: Optional[str] = None,
|
547
|
+
**kwargs
|
548
|
+
) -> Dict[str, Any]:
|
549
|
+
"""Local invoke using AI Factory (original logic)"""
|
550
|
+
try:
|
551
|
+
# Step 1: Select best model for this task
|
552
|
+
selected_model = await self._select_model(
|
553
|
+
input_data=input_data,
|
554
|
+
task=task,
|
555
|
+
service_type=service_type,
|
556
|
+
model_hint=model_hint,
|
557
|
+
provider_hint=provider_hint
|
558
|
+
)
|
559
|
+
|
560
|
+
# Step 2: Get appropriate service
|
561
|
+
service = await self._get_service(
|
562
|
+
service_type=service_type,
|
563
|
+
model_name=selected_model["model_id"],
|
564
|
+
provider=selected_model["provider"],
|
565
|
+
task=task
|
566
|
+
)
|
567
|
+
|
568
|
+
# Step 3: Execute task with unified interface
|
569
|
+
result = await self._execute_task(
|
570
|
+
service=service,
|
571
|
+
input_data=input_data,
|
572
|
+
task=task,
|
573
|
+
service_type=service_type,
|
574
|
+
**kwargs
|
575
|
+
)
|
576
|
+
|
577
|
+
# Step 4: Return unified response
|
578
|
+
return {
|
579
|
+
"success": True,
|
580
|
+
"result": result,
|
581
|
+
"metadata": {
|
582
|
+
"model_used": selected_model["model_id"],
|
583
|
+
"provider": selected_model["provider"],
|
584
|
+
"task": task,
|
585
|
+
"service_type": service_type,
|
586
|
+
"selection_reason": selected_model.get("reason", "Default selection")
|
587
|
+
}
|
588
|
+
}
|
589
|
+
except Exception as e:
|
590
|
+
logger.error(f"Local invoke failed: {e}")
|
591
|
+
raise
|
592
|
+
|
593
|
+
async def _invoke_api(
|
594
|
+
self,
|
595
|
+
input_data: Union[str, bytes, Path, Dict[str, Any]],
|
596
|
+
task: str,
|
597
|
+
service_type: str,
|
598
|
+
model_hint: Optional[str] = None,
|
599
|
+
provider_hint: Optional[str] = None,
|
600
|
+
**kwargs
|
601
|
+
) -> Dict[str, Any]:
|
602
|
+
"""API invoke using HTTP requests"""
|
603
|
+
|
604
|
+
# Handle file inputs
|
605
|
+
if isinstance(input_data, Path):
|
606
|
+
return await self._invoke_api_file(
|
607
|
+
file_path=input_data,
|
608
|
+
task=task,
|
609
|
+
service_type=service_type,
|
610
|
+
model_hint=model_hint,
|
611
|
+
provider_hint=provider_hint,
|
612
|
+
**kwargs
|
613
|
+
)
|
614
|
+
|
615
|
+
# Handle binary data
|
616
|
+
if isinstance(input_data, bytes):
|
617
|
+
return await self._invoke_api_binary(
|
618
|
+
data=input_data,
|
619
|
+
task=task,
|
620
|
+
service_type=service_type,
|
621
|
+
model_hint=model_hint,
|
622
|
+
provider_hint=provider_hint,
|
623
|
+
**kwargs
|
624
|
+
)
|
625
|
+
|
626
|
+
# Handle text/JSON data
|
627
|
+
payload = {
|
628
|
+
"input_data": input_data,
|
629
|
+
"task": task,
|
630
|
+
"service_type": service_type,
|
631
|
+
"model_hint": model_hint,
|
632
|
+
"provider_hint": provider_hint,
|
633
|
+
"parameters": kwargs
|
634
|
+
}
|
635
|
+
|
636
|
+
async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=300)) as session:
|
637
|
+
try:
|
638
|
+
async with session.post(
|
639
|
+
f"{self.api_url}/api/v1/invoke",
|
640
|
+
json=payload,
|
641
|
+
headers=self.headers
|
642
|
+
) as response:
|
643
|
+
|
644
|
+
if response.status == 200:
|
645
|
+
return await response.json()
|
646
|
+
else:
|
647
|
+
error_data = await response.text()
|
648
|
+
raise Exception(f"API error {response.status}: {error_data}")
|
649
|
+
|
650
|
+
except Exception as e:
|
651
|
+
logger.error(f"API invoke failed: {e}")
|
652
|
+
raise
|
653
|
+
|
654
|
+
async def _invoke_api_file(
|
655
|
+
self,
|
656
|
+
file_path: Path,
|
657
|
+
task: str,
|
658
|
+
service_type: str,
|
659
|
+
model_hint: Optional[str] = None,
|
660
|
+
provider_hint: Optional[str] = None,
|
661
|
+
**kwargs
|
662
|
+
) -> Dict[str, Any]:
|
663
|
+
"""API file upload"""
|
664
|
+
|
665
|
+
if not file_path.exists():
|
666
|
+
raise FileNotFoundError(f"File not found: {file_path}")
|
667
|
+
|
668
|
+
data = aiohttp.FormData()
|
669
|
+
data.add_field('task', task)
|
670
|
+
data.add_field('service_type', service_type)
|
671
|
+
|
672
|
+
if model_hint:
|
673
|
+
data.add_field('model_hint', model_hint)
|
674
|
+
if provider_hint:
|
675
|
+
data.add_field('provider_hint', provider_hint)
|
676
|
+
|
677
|
+
data.add_field('file',
|
678
|
+
open(file_path, 'rb'),
|
679
|
+
filename=file_path.name,
|
680
|
+
content_type='application/octet-stream')
|
681
|
+
|
682
|
+
headers = {k: v for k, v in self.headers.items() if k != "Content-Type"}
|
683
|
+
|
684
|
+
async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=300)) as session:
|
685
|
+
try:
|
686
|
+
async with session.post(
|
687
|
+
f"{self.api_url}/api/v1/invoke-file",
|
688
|
+
data=data,
|
689
|
+
headers=headers
|
690
|
+
) as response:
|
691
|
+
|
692
|
+
if response.status == 200:
|
693
|
+
return await response.json()
|
694
|
+
else:
|
695
|
+
error_data = await response.text()
|
696
|
+
raise Exception(f"API error {response.status}: {error_data}")
|
697
|
+
|
698
|
+
except Exception as e:
|
699
|
+
logger.error(f"API file upload failed: {e}")
|
700
|
+
raise
|
701
|
+
|
702
|
+
async def _invoke_api_binary(
|
703
|
+
self,
|
704
|
+
data: bytes,
|
705
|
+
task: str,
|
706
|
+
service_type: str,
|
707
|
+
model_hint: Optional[str] = None,
|
708
|
+
provider_hint: Optional[str] = None,
|
709
|
+
**kwargs
|
710
|
+
) -> Dict[str, Any]:
|
711
|
+
"""API binary upload"""
|
712
|
+
|
713
|
+
form_data = aiohttp.FormData()
|
714
|
+
form_data.add_field('task', task)
|
715
|
+
form_data.add_field('service_type', service_type)
|
716
|
+
|
717
|
+
if model_hint:
|
718
|
+
form_data.add_field('model_hint', model_hint)
|
719
|
+
if provider_hint:
|
720
|
+
form_data.add_field('provider_hint', provider_hint)
|
721
|
+
|
722
|
+
form_data.add_field('file',
|
723
|
+
data,
|
724
|
+
filename='data.bin',
|
725
|
+
content_type='application/octet-stream')
|
726
|
+
|
727
|
+
headers = {k: v for k, v in self.headers.items() if k != "Content-Type"}
|
728
|
+
|
729
|
+
async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=300)) as session:
|
730
|
+
try:
|
731
|
+
async with session.post(
|
732
|
+
f"{self.api_url}/api/v1/invoke-file",
|
733
|
+
data=form_data,
|
734
|
+
headers=headers
|
735
|
+
) as response:
|
736
|
+
|
737
|
+
if response.status == 200:
|
738
|
+
return await response.json()
|
739
|
+
else:
|
740
|
+
error_data = await response.text()
|
741
|
+
raise Exception(f"API error {response.status}: {error_data}")
|
742
|
+
|
743
|
+
except Exception as e:
|
744
|
+
logger.error(f"API binary upload failed: {e}")
|
745
|
+
raise
|
746
|
+
|
747
|
+
|
748
|
+
# Convenience function for quick access
|
749
|
+
def create_client(
|
750
|
+
config: Optional[Dict[str, Any]] = None,
|
751
|
+
mode: str = "local",
|
752
|
+
api_url: Optional[str] = None,
|
753
|
+
api_key: Optional[str] = None
|
754
|
+
) -> ISAModelClient:
|
755
|
+
"""Create ISA Model Client instance
|
756
|
+
|
757
|
+
Args:
|
758
|
+
config: Optional configuration
|
759
|
+
mode: "local" for direct AI Factory, "api" for HTTP API calls
|
760
|
+
api_url: API base URL (required if mode="api")
|
761
|
+
api_key: API key for authentication (optional)
|
762
|
+
|
763
|
+
Returns:
|
764
|
+
ISAModelClient instance
|
765
|
+
"""
|
766
|
+
return ISAModelClient(config=config, mode=mode, api_url=api_url, api_key=api_key)
|
767
|
+
|
768
|
+
|
769
|
+
# Export for easy import
|
770
|
+
__all__ = ["ISAModelClient", "create_client"]
|