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
@@ -0,0 +1,399 @@
|
|
1
|
+
from typing import Dict, Optional, List, Any
|
2
|
+
import logging
|
3
|
+
from pathlib import Path
|
4
|
+
from datetime import datetime
|
5
|
+
from huggingface_hub import hf_hub_download, snapshot_download
|
6
|
+
from huggingface_hub.errors import HfHubHTTPError
|
7
|
+
from .model_storage import ModelStorage, LocalModelStorage
|
8
|
+
from .model_repo import ModelRegistry, ModelType, ModelCapability
|
9
|
+
from .model_billing_tracker import ModelBillingTracker, ModelOperationType
|
10
|
+
from ..pricing_manager import PricingManager
|
11
|
+
from ..config import ConfigManager
|
12
|
+
|
13
|
+
logger = logging.getLogger(__name__)
|
14
|
+
|
15
|
+
class ModelManager:
|
16
|
+
"""
|
17
|
+
Model lifecycle management service
|
18
|
+
|
19
|
+
Handles the complete model lifecycle:
|
20
|
+
- Model registration and metadata management
|
21
|
+
- Model downloads, versions, and caching
|
22
|
+
- Cost tracking and billing across all operations
|
23
|
+
- Integration with model training, evaluation, and deployment
|
24
|
+
"""
|
25
|
+
|
26
|
+
|
27
|
+
def __init__(self,
|
28
|
+
storage: Optional[ModelStorage] = None,
|
29
|
+
registry: Optional[ModelRegistry] = None,
|
30
|
+
billing_tracker: Optional[ModelBillingTracker] = None,
|
31
|
+
pricing_manager: Optional[PricingManager] = None,
|
32
|
+
config_manager: Optional[ConfigManager] = None):
|
33
|
+
self.storage = storage or LocalModelStorage()
|
34
|
+
self.registry = registry or ModelRegistry()
|
35
|
+
self.billing_tracker = billing_tracker or ModelBillingTracker(model_registry=self.registry)
|
36
|
+
self.pricing_manager = pricing_manager or PricingManager()
|
37
|
+
self.config_manager = config_manager or ConfigManager()
|
38
|
+
|
39
|
+
def get_model_pricing(self, provider: str, model_name: str) -> Dict[str, float]:
|
40
|
+
"""获取模型定价信息"""
|
41
|
+
pricing = self.pricing_manager.get_model_pricing(provider, model_name)
|
42
|
+
if pricing:
|
43
|
+
return {"input": pricing.input_cost, "output": pricing.output_cost}
|
44
|
+
return {"input": 0.0, "output": 0.0}
|
45
|
+
|
46
|
+
def calculate_cost(self, provider: str, model_name: str, input_tokens: int, output_tokens: int) -> float:
|
47
|
+
"""计算请求成本"""
|
48
|
+
return self.pricing_manager.calculate_cost(
|
49
|
+
provider=provider,
|
50
|
+
model_name=model_name,
|
51
|
+
input_units=input_tokens,
|
52
|
+
output_units=output_tokens
|
53
|
+
)
|
54
|
+
|
55
|
+
def get_cheapest_model(self, provider: str, model_type: str = "llm") -> Optional[str]:
|
56
|
+
"""获取最便宜的模型"""
|
57
|
+
result = self.pricing_manager.get_cheapest_model(
|
58
|
+
provider=provider,
|
59
|
+
unit_type="token",
|
60
|
+
min_input_units=1000 # Assume 1K tokens for comparison
|
61
|
+
)
|
62
|
+
return result["model_name"] if result else None
|
63
|
+
|
64
|
+
async def get_model(self,
|
65
|
+
model_id: str,
|
66
|
+
repo_id: str,
|
67
|
+
model_type: ModelType,
|
68
|
+
capabilities: List[ModelCapability],
|
69
|
+
revision: Optional[str] = None,
|
70
|
+
force_download: bool = False) -> Optional[Path]:
|
71
|
+
"""
|
72
|
+
Get model files, downloading if necessary
|
73
|
+
|
74
|
+
Args:
|
75
|
+
model_id: Unique identifier for the model
|
76
|
+
repo_id: Hugging Face repository ID
|
77
|
+
model_type: Type of model (LLM, embedding, etc.)
|
78
|
+
capabilities: List of model capabilities
|
79
|
+
revision: Specific model version/tag
|
80
|
+
force_download: Force re-download even if cached
|
81
|
+
|
82
|
+
Returns:
|
83
|
+
Path to the model files or None if failed
|
84
|
+
"""
|
85
|
+
# Check if model is already downloaded
|
86
|
+
if not force_download:
|
87
|
+
model_path = await self.storage.load_model(model_id)
|
88
|
+
if model_path:
|
89
|
+
logger.info(f"Using cached model {model_id}")
|
90
|
+
return model_path
|
91
|
+
|
92
|
+
try:
|
93
|
+
# Download model files
|
94
|
+
logger.info(f"Downloading model {model_id} from {repo_id}")
|
95
|
+
model_dir = Path(f"./models/temp/{model_id}")
|
96
|
+
model_dir.mkdir(parents=True, exist_ok=True)
|
97
|
+
|
98
|
+
snapshot_download(
|
99
|
+
repo_id=repo_id,
|
100
|
+
revision=revision,
|
101
|
+
local_dir=model_dir,
|
102
|
+
local_dir_use_symlinks=False
|
103
|
+
)
|
104
|
+
|
105
|
+
# Save model and metadata
|
106
|
+
metadata = {
|
107
|
+
"repo_id": repo_id,
|
108
|
+
"revision": revision,
|
109
|
+
"downloaded_at": str(Path(model_dir).stat().st_mtime)
|
110
|
+
}
|
111
|
+
|
112
|
+
# Register model
|
113
|
+
self.registry.register_model(
|
114
|
+
model_id=model_id,
|
115
|
+
model_type=model_type,
|
116
|
+
capabilities=capabilities,
|
117
|
+
metadata=metadata
|
118
|
+
)
|
119
|
+
|
120
|
+
# Save model files
|
121
|
+
await self.storage.save_model(model_id, str(model_dir), metadata)
|
122
|
+
|
123
|
+
return await self.storage.load_model(model_id)
|
124
|
+
|
125
|
+
except HfHubHTTPError as e:
|
126
|
+
logger.error(f"Failed to download model {model_id}: {e}")
|
127
|
+
return None
|
128
|
+
except Exception as e:
|
129
|
+
logger.error(f"Unexpected error downloading model {model_id}: {e}")
|
130
|
+
return None
|
131
|
+
|
132
|
+
async def list_models(self) -> List[Dict[str, Any]]:
|
133
|
+
"""List all downloaded models with their metadata"""
|
134
|
+
models = await self.storage.list_models()
|
135
|
+
return [
|
136
|
+
{
|
137
|
+
"model_id": model_id,
|
138
|
+
**metadata,
|
139
|
+
**(self.registry.get_model_info(model_id) or {})
|
140
|
+
}
|
141
|
+
for model_id, metadata in models.items()
|
142
|
+
]
|
143
|
+
|
144
|
+
async def remove_model(self, model_id: str) -> bool:
|
145
|
+
"""Remove a model and its metadata"""
|
146
|
+
try:
|
147
|
+
# Remove from storage
|
148
|
+
storage_success = await self.storage.delete_model(model_id)
|
149
|
+
|
150
|
+
# Unregister from registry
|
151
|
+
registry_success = self.registry.unregister_model(model_id)
|
152
|
+
|
153
|
+
return storage_success and registry_success
|
154
|
+
|
155
|
+
except Exception as e:
|
156
|
+
logger.error(f"Failed to remove model {model_id}: {e}")
|
157
|
+
return False
|
158
|
+
|
159
|
+
async def get_model_info(self, model_id: str) -> Optional[Dict[str, Any]]:
|
160
|
+
"""Get information about a specific model"""
|
161
|
+
storage_info = await self.storage.get_metadata(model_id)
|
162
|
+
registry_info = self.registry.get_model_info(model_id)
|
163
|
+
|
164
|
+
if not storage_info and not registry_info:
|
165
|
+
return None
|
166
|
+
|
167
|
+
return {
|
168
|
+
**(storage_info or {}),
|
169
|
+
**(registry_info or {})
|
170
|
+
}
|
171
|
+
|
172
|
+
async def update_model(self,
|
173
|
+
model_id: str,
|
174
|
+
repo_id: str,
|
175
|
+
model_type: ModelType,
|
176
|
+
capabilities: List[ModelCapability],
|
177
|
+
revision: Optional[str] = None) -> bool:
|
178
|
+
"""Update a model to a new version"""
|
179
|
+
try:
|
180
|
+
return bool(await self.get_model(
|
181
|
+
model_id=model_id,
|
182
|
+
repo_id=repo_id,
|
183
|
+
model_type=model_type,
|
184
|
+
capabilities=capabilities,
|
185
|
+
revision=revision,
|
186
|
+
force_download=True
|
187
|
+
))
|
188
|
+
except Exception as e:
|
189
|
+
logger.error(f"Failed to update model {model_id}: {e}")
|
190
|
+
return False
|
191
|
+
|
192
|
+
# === MODEL LIFECYCLE MANAGEMENT ===
|
193
|
+
|
194
|
+
async def register_model_for_lifecycle(
|
195
|
+
self,
|
196
|
+
model_id: str,
|
197
|
+
model_type: ModelType,
|
198
|
+
capabilities: List[ModelCapability],
|
199
|
+
provider: str = "custom",
|
200
|
+
provider_model_name: Optional[str] = None,
|
201
|
+
metadata: Optional[Dict[str, Any]] = None
|
202
|
+
) -> bool:
|
203
|
+
"""
|
204
|
+
Register a model for lifecycle management
|
205
|
+
|
206
|
+
Args:
|
207
|
+
model_id: Unique identifier for the model
|
208
|
+
model_type: Type of model (LLM, embedding, etc.)
|
209
|
+
capabilities: List of model capabilities
|
210
|
+
provider: Provider name for billing
|
211
|
+
provider_model_name: Provider-specific model name for pricing
|
212
|
+
metadata: Additional metadata
|
213
|
+
|
214
|
+
Returns:
|
215
|
+
True if registration successful
|
216
|
+
"""
|
217
|
+
try:
|
218
|
+
# Prepare metadata with billing info
|
219
|
+
full_metadata = metadata or {}
|
220
|
+
full_metadata.update({
|
221
|
+
"provider": provider,
|
222
|
+
"provider_model_name": provider_model_name or model_id,
|
223
|
+
"registered_for_lifecycle": True,
|
224
|
+
"lifecycle_stage": "registered"
|
225
|
+
})
|
226
|
+
|
227
|
+
# Register in model registry
|
228
|
+
success = self.registry.register_model(
|
229
|
+
model_id=model_id,
|
230
|
+
model_type=model_type,
|
231
|
+
capabilities=capabilities,
|
232
|
+
metadata=full_metadata
|
233
|
+
)
|
234
|
+
|
235
|
+
if success:
|
236
|
+
# Track registration operation
|
237
|
+
self.billing_tracker.track_model_usage(
|
238
|
+
model_id=model_id,
|
239
|
+
operation_type=ModelOperationType.STORAGE,
|
240
|
+
provider=provider,
|
241
|
+
service_type="model_management",
|
242
|
+
operation="register_model",
|
243
|
+
metadata={"stage": "registration"}
|
244
|
+
)
|
245
|
+
|
246
|
+
logger.info(f"Successfully registered model {model_id} for lifecycle management")
|
247
|
+
|
248
|
+
return success
|
249
|
+
|
250
|
+
except Exception as e:
|
251
|
+
logger.error(f"Failed to register model {model_id} for lifecycle: {e}")
|
252
|
+
return False
|
253
|
+
|
254
|
+
def track_model_usage(
|
255
|
+
self,
|
256
|
+
model_id: str,
|
257
|
+
operation_type: ModelOperationType,
|
258
|
+
provider: str,
|
259
|
+
service_type: str,
|
260
|
+
operation: str,
|
261
|
+
input_tokens: Optional[int] = None,
|
262
|
+
output_tokens: Optional[int] = None,
|
263
|
+
input_units: Optional[float] = None,
|
264
|
+
output_units: Optional[float] = None,
|
265
|
+
metadata: Optional[Dict[str, Any]] = None
|
266
|
+
):
|
267
|
+
"""
|
268
|
+
Track model usage and costs
|
269
|
+
|
270
|
+
This method should be called by:
|
271
|
+
- Training services when training a model
|
272
|
+
- Evaluation services when evaluating a model
|
273
|
+
- Deployment services when deploying a model
|
274
|
+
- Inference services when using a model for inference
|
275
|
+
"""
|
276
|
+
return self.billing_tracker.track_model_usage(
|
277
|
+
model_id=model_id,
|
278
|
+
operation_type=operation_type,
|
279
|
+
provider=provider,
|
280
|
+
service_type=service_type,
|
281
|
+
operation=operation,
|
282
|
+
input_tokens=input_tokens,
|
283
|
+
output_tokens=output_tokens,
|
284
|
+
input_units=input_units,
|
285
|
+
output_units=output_units,
|
286
|
+
metadata=metadata
|
287
|
+
)
|
288
|
+
|
289
|
+
async def update_model_stage(
|
290
|
+
self,
|
291
|
+
model_id: str,
|
292
|
+
new_stage: str,
|
293
|
+
metadata: Optional[Dict[str, Any]] = None
|
294
|
+
) -> bool:
|
295
|
+
"""
|
296
|
+
Update model lifecycle stage
|
297
|
+
|
298
|
+
Args:
|
299
|
+
model_id: Model identifier
|
300
|
+
new_stage: New lifecycle stage (training, evaluation, deployment, production, retired)
|
301
|
+
metadata: Additional metadata for this stage
|
302
|
+
|
303
|
+
Returns:
|
304
|
+
True if update successful
|
305
|
+
"""
|
306
|
+
try:
|
307
|
+
# Get current model info
|
308
|
+
model_info = self.registry.get_model_info(model_id)
|
309
|
+
if not model_info:
|
310
|
+
logger.error(f"Model {model_id} not found in registry")
|
311
|
+
return False
|
312
|
+
|
313
|
+
# Update metadata with new stage
|
314
|
+
current_metadata = model_info.get("metadata", {})
|
315
|
+
current_metadata.update({
|
316
|
+
"lifecycle_stage": new_stage,
|
317
|
+
"stage_updated_at": str(datetime.now()),
|
318
|
+
**(metadata or {})
|
319
|
+
})
|
320
|
+
|
321
|
+
# Update in registry
|
322
|
+
success = self.registry.register_model(
|
323
|
+
model_id=model_id,
|
324
|
+
model_type=ModelType(model_info["type"]),
|
325
|
+
capabilities=[ModelCapability(cap) for cap in model_info["capabilities"]],
|
326
|
+
metadata=current_metadata
|
327
|
+
)
|
328
|
+
|
329
|
+
if success:
|
330
|
+
logger.info(f"Updated model {model_id} to stage: {new_stage}")
|
331
|
+
|
332
|
+
return success
|
333
|
+
|
334
|
+
except Exception as e:
|
335
|
+
logger.error(f"Failed to update model {model_id} stage: {e}")
|
336
|
+
return False
|
337
|
+
|
338
|
+
def get_model_lifecycle_summary(self, model_id: str) -> Optional[Dict[str, Any]]:
|
339
|
+
"""
|
340
|
+
Get complete lifecycle summary for a model including costs
|
341
|
+
|
342
|
+
Returns:
|
343
|
+
Dictionary with model info, lifecycle stage, and billing summary
|
344
|
+
"""
|
345
|
+
try:
|
346
|
+
# Get model info from registry
|
347
|
+
model_info = self.registry.get_model_info(model_id)
|
348
|
+
if not model_info:
|
349
|
+
return None
|
350
|
+
|
351
|
+
# Get billing summary from tracker
|
352
|
+
billing_summary = self.billing_tracker.get_model_usage_summary(model_id)
|
353
|
+
|
354
|
+
return {
|
355
|
+
"model_id": model_id,
|
356
|
+
"model_info": model_info,
|
357
|
+
"billing_summary": billing_summary,
|
358
|
+
"current_stage": model_info.get("metadata", {}).get("lifecycle_stage", "unknown")
|
359
|
+
}
|
360
|
+
|
361
|
+
except Exception as e:
|
362
|
+
logger.error(f"Failed to get lifecycle summary for {model_id}: {e}")
|
363
|
+
return None
|
364
|
+
|
365
|
+
def list_models_by_stage(self, stage: str) -> List[Dict[str, Any]]:
|
366
|
+
"""
|
367
|
+
List all models in a specific lifecycle stage
|
368
|
+
|
369
|
+
Args:
|
370
|
+
stage: Lifecycle stage to filter by
|
371
|
+
|
372
|
+
Returns:
|
373
|
+
List of model dictionaries
|
374
|
+
"""
|
375
|
+
try:
|
376
|
+
all_models = self.registry.list_models()
|
377
|
+
stage_models = []
|
378
|
+
|
379
|
+
for model_id, model_info in all_models.items():
|
380
|
+
current_stage = model_info.get("metadata", {}).get("lifecycle_stage")
|
381
|
+
if current_stage == stage:
|
382
|
+
stage_models.append({
|
383
|
+
"model_id": model_id,
|
384
|
+
**model_info
|
385
|
+
})
|
386
|
+
|
387
|
+
return stage_models
|
388
|
+
|
389
|
+
except Exception as e:
|
390
|
+
logger.error(f"Failed to list models by stage {stage}: {e}")
|
391
|
+
return []
|
392
|
+
|
393
|
+
def get_billing_summary_by_operation(self, operation_type: ModelOperationType) -> Dict[str, Any]:
|
394
|
+
"""Get billing summary for a specific operation type"""
|
395
|
+
return self.billing_tracker.get_operation_summary(operation_type)
|
396
|
+
|
397
|
+
def print_model_costs(self, model_id: str):
|
398
|
+
"""Print cost summary for a specific model"""
|
399
|
+
self.billing_tracker.print_model_summary(model_id)
|