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.
Files changed (100) hide show
  1. isa_model/__init__.py +30 -1
  2. isa_model/client.py +770 -0
  3. isa_model/core/config/__init__.py +16 -0
  4. isa_model/core/config/config_manager.py +514 -0
  5. isa_model/core/config.py +426 -0
  6. isa_model/core/models/model_billing_tracker.py +476 -0
  7. isa_model/core/models/model_manager.py +399 -0
  8. isa_model/core/models/model_repo.py +343 -0
  9. isa_model/core/pricing_manager.py +426 -0
  10. isa_model/core/services/__init__.py +19 -0
  11. isa_model/core/services/intelligent_model_selector.py +547 -0
  12. isa_model/core/types.py +291 -0
  13. isa_model/deployment/__init__.py +2 -0
  14. isa_model/deployment/cloud/__init__.py +9 -0
  15. isa_model/deployment/cloud/modal/__init__.py +10 -0
  16. isa_model/deployment/cloud/modal/isa_vision_doc_service.py +766 -0
  17. isa_model/deployment/cloud/modal/isa_vision_table_service.py +532 -0
  18. isa_model/deployment/cloud/modal/isa_vision_ui_service.py +406 -0
  19. isa_model/deployment/cloud/modal/register_models.py +321 -0
  20. isa_model/deployment/runtime/deployed_service.py +338 -0
  21. isa_model/deployment/services/__init__.py +9 -0
  22. isa_model/deployment/services/auto_deploy_vision_service.py +537 -0
  23. isa_model/deployment/services/model_service.py +332 -0
  24. isa_model/deployment/services/service_monitor.py +356 -0
  25. isa_model/deployment/services/service_registry.py +527 -0
  26. isa_model/eval/__init__.py +80 -44
  27. isa_model/eval/config/__init__.py +10 -0
  28. isa_model/eval/config/evaluation_config.py +108 -0
  29. isa_model/eval/evaluators/__init__.py +18 -0
  30. isa_model/eval/evaluators/base_evaluator.py +503 -0
  31. isa_model/eval/evaluators/llm_evaluator.py +472 -0
  32. isa_model/eval/factory.py +417 -709
  33. isa_model/eval/infrastructure/__init__.py +24 -0
  34. isa_model/eval/infrastructure/experiment_tracker.py +466 -0
  35. isa_model/eval/metrics.py +191 -21
  36. isa_model/inference/ai_factory.py +187 -387
  37. isa_model/inference/providers/modal_provider.py +109 -0
  38. isa_model/inference/providers/yyds_provider.py +108 -0
  39. isa_model/inference/services/__init__.py +2 -1
  40. isa_model/inference/services/audio/base_stt_service.py +65 -1
  41. isa_model/inference/services/audio/base_tts_service.py +75 -1
  42. isa_model/inference/services/audio/openai_stt_service.py +189 -151
  43. isa_model/inference/services/audio/openai_tts_service.py +12 -10
  44. isa_model/inference/services/audio/replicate_tts_service.py +61 -56
  45. isa_model/inference/services/base_service.py +55 -55
  46. isa_model/inference/services/embedding/base_embed_service.py +65 -1
  47. isa_model/inference/services/embedding/ollama_embed_service.py +103 -43
  48. isa_model/inference/services/embedding/openai_embed_service.py +8 -10
  49. isa_model/inference/services/helpers/stacked_config.py +148 -0
  50. isa_model/inference/services/img/__init__.py +18 -0
  51. isa_model/inference/services/{vision → img}/base_image_gen_service.py +80 -35
  52. isa_model/inference/services/img/flux_professional_service.py +603 -0
  53. isa_model/inference/services/img/helpers/base_stacked_service.py +274 -0
  54. isa_model/inference/services/{vision → img}/replicate_image_gen_service.py +210 -69
  55. isa_model/inference/services/llm/__init__.py +3 -3
  56. isa_model/inference/services/llm/base_llm_service.py +519 -35
  57. isa_model/inference/services/llm/{llm_adapter.py → helpers/llm_adapter.py} +40 -0
  58. isa_model/inference/services/llm/helpers/llm_prompts.py +258 -0
  59. isa_model/inference/services/llm/helpers/llm_utils.py +280 -0
  60. isa_model/inference/services/llm/ollama_llm_service.py +150 -15
  61. isa_model/inference/services/llm/openai_llm_service.py +134 -31
  62. isa_model/inference/services/llm/yyds_llm_service.py +255 -0
  63. isa_model/inference/services/vision/__init__.py +38 -4
  64. isa_model/inference/services/vision/base_vision_service.py +241 -96
  65. isa_model/inference/services/vision/disabled/isA_vision_service.py +500 -0
  66. isa_model/inference/services/vision/doc_analysis_service.py +640 -0
  67. isa_model/inference/services/vision/helpers/base_stacked_service.py +274 -0
  68. isa_model/inference/services/vision/helpers/image_utils.py +272 -3
  69. isa_model/inference/services/vision/helpers/vision_prompts.py +297 -0
  70. isa_model/inference/services/vision/openai_vision_service.py +109 -170
  71. isa_model/inference/services/vision/replicate_vision_service.py +508 -0
  72. isa_model/inference/services/vision/ui_analysis_service.py +823 -0
  73. isa_model/scripts/register_models.py +370 -0
  74. isa_model/scripts/register_models_with_embeddings.py +510 -0
  75. isa_model/serving/__init__.py +19 -0
  76. isa_model/serving/api/__init__.py +10 -0
  77. isa_model/serving/api/fastapi_server.py +89 -0
  78. isa_model/serving/api/middleware/__init__.py +9 -0
  79. isa_model/serving/api/middleware/request_logger.py +88 -0
  80. isa_model/serving/api/routes/__init__.py +5 -0
  81. isa_model/serving/api/routes/health.py +82 -0
  82. isa_model/serving/api/routes/llm.py +19 -0
  83. isa_model/serving/api/routes/ui_analysis.py +223 -0
  84. isa_model/serving/api/routes/unified.py +202 -0
  85. isa_model/serving/api/routes/vision.py +19 -0
  86. isa_model/serving/api/schemas/__init__.py +17 -0
  87. isa_model/serving/api/schemas/common.py +33 -0
  88. isa_model/serving/api/schemas/ui_analysis.py +78 -0
  89. {isa_model-0.3.4.dist-info → isa_model-0.3.6.dist-info}/METADATA +4 -1
  90. isa_model-0.3.6.dist-info/RECORD +147 -0
  91. isa_model/core/model_manager.py +0 -208
  92. isa_model/core/model_registry.py +0 -342
  93. isa_model/inference/billing_tracker.py +0 -406
  94. isa_model/inference/services/llm/triton_llm_service.py +0 -481
  95. isa_model/inference/services/vision/ollama_vision_service.py +0 -194
  96. isa_model-0.3.4.dist-info/RECORD +0 -91
  97. /isa_model/core/{model_storage.py → models/model_storage.py} +0 -0
  98. /isa_model/inference/services/{vision → embedding}/helpers/text_splitter.py +0 -0
  99. {isa_model-0.3.4.dist-info → isa_model-0.3.6.dist-info}/WHEEL +0 -0
  100. {isa_model-0.3.4.dist-info → isa_model-0.3.6.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,109 @@
1
+ """
2
+ Modal Provider
3
+
4
+ Provider for ISA self-hosted Modal services
5
+ No API keys needed since we deploy our own services
6
+ """
7
+
8
+ import os
9
+ import logging
10
+ from typing import Dict, Any, Optional, List
11
+ from .base_provider import BaseProvider
12
+ from isa_model.inference.base import ModelType, Capability
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+ class ModalProvider(BaseProvider):
17
+ """Provider for ISA Modal services"""
18
+
19
+ def __init__(self, config: Optional[Dict[str, Any]] = None):
20
+ super().__init__(config)
21
+ self.name = "modal"
22
+ self.base_url = "https://modal.com" # Not used directly
23
+
24
+ def _load_provider_env_vars(self):
25
+ """Load Modal-specific environment variables"""
26
+ # Modal doesn't need API keys for deployed services
27
+ # But we can load Modal token if available
28
+ modal_token = os.getenv("MODAL_TOKEN_ID") or os.getenv("MODAL_TOKEN_SECRET")
29
+ if modal_token:
30
+ self.config["modal_token"] = modal_token
31
+
32
+ # Set default config
33
+ if "timeout" not in self.config:
34
+ self.config["timeout"] = 300
35
+ if "deployment_region" not in self.config:
36
+ self.config["deployment_region"] = "us-east-1"
37
+ if "gpu_type" not in self.config:
38
+ self.config["gpu_type"] = "T4"
39
+
40
+ def get_api_key(self) -> str:
41
+ """Modal services don't need API keys for deployed apps"""
42
+ return "modal-deployed-service" # Placeholder
43
+
44
+ def get_base_url(self) -> str:
45
+ """Get base URL for Modal services"""
46
+ return self.base_url
47
+
48
+ def validate_credentials(self) -> bool:
49
+ """
50
+ Validate Modal credentials
51
+ For deployed services, we assume they're accessible
52
+ """
53
+ try:
54
+ # Check if Modal is available
55
+ import modal
56
+ return True
57
+ except ImportError:
58
+ logger.warning("Modal package not available")
59
+ return False
60
+
61
+ def get_capabilities(self) -> Dict[ModelType, List[Capability]]:
62
+ """Get Modal provider capabilities"""
63
+ return {
64
+ ModelType.VISION: [
65
+ Capability.OBJECT_DETECTION,
66
+ Capability.IMAGE_ANALYSIS,
67
+ Capability.UI_DETECTION,
68
+ Capability.OCR,
69
+ Capability.DOCUMENT_ANALYSIS
70
+ ]
71
+ }
72
+
73
+ def get_models(self, model_type: ModelType) -> List[str]:
74
+ """Get available models for given type"""
75
+ if model_type == ModelType.VISION:
76
+ return [
77
+ "omniparser-v2.0",
78
+ "table-transformer-detection",
79
+ "table-transformer-structure-v1.1",
80
+ "paddleocr-3.0",
81
+ "yolov8"
82
+ ]
83
+ return []
84
+
85
+ def is_reasoning_model(self, model_name: str) -> bool:
86
+ """Check if the model is optimized for reasoning tasks"""
87
+ # Vision models are not reasoning models
88
+ return False
89
+
90
+ def get_default_config(self) -> Dict[str, Any]:
91
+ """Get default configuration for Modal services"""
92
+ return {
93
+ "timeout": 300, # 5 minutes
94
+ "max_retries": 3,
95
+ "deployment_region": "us-east-1",
96
+ "gpu_type": "T4"
97
+ }
98
+
99
+ def get_billing_info(self) -> Dict[str, Any]:
100
+ """Get billing information for Modal services"""
101
+ return {
102
+ "provider": "modal",
103
+ "billing_model": "compute_usage",
104
+ "cost_per_hour": {
105
+ "T4": 0.60,
106
+ "A100": 4.00
107
+ },
108
+ "note": "Costs depend on actual usage time, scales to zero when not in use"
109
+ }
@@ -0,0 +1,108 @@
1
+ from isa_model.inference.providers.base_provider import BaseProvider
2
+ from isa_model.inference.base import ModelType, Capability
3
+ from typing import Dict, List, Any
4
+ import logging
5
+ import os
6
+
7
+ logger = logging.getLogger(__name__)
8
+
9
+ class YydsProvider(BaseProvider):
10
+ """Provider for YYDS API with proper API key management"""
11
+
12
+ def __init__(self, config=None):
13
+ """Initialize the YYDS Provider with centralized config management"""
14
+ super().__init__(config)
15
+ self.name = "yyds"
16
+
17
+ logger.info(f"Initialized YydsProvider with URL: {self.config.get('base_url', 'https://api.yyds.com/v1')}")
18
+
19
+ if not self.has_valid_credentials():
20
+ logger.warning("YYDS API key not found. Set YYDS_API_KEY environment variable or pass api_key in config.")
21
+
22
+ def _load_provider_env_vars(self):
23
+ """Load YYDS-specific environment variables"""
24
+ # Set defaults first
25
+ defaults = {
26
+ "base_url": "https://api.yyds.com/v1",
27
+ "timeout": 60,
28
+ "temperature": 0.7,
29
+ "top_p": 0.9,
30
+ "max_tokens": 1024
31
+ }
32
+
33
+ # Apply defaults only if not already set
34
+ for key, value in defaults.items():
35
+ if key not in self.config:
36
+ self.config[key] = value
37
+
38
+ # Load from environment variables (override config if present)
39
+ env_mappings = {
40
+ "api_key": "YYDS_API_KEY",
41
+ "base_url": "YYDS_API_BASE",
42
+ "organization": "YYDS_ORGANIZATION"
43
+ }
44
+
45
+ for config_key, env_var in env_mappings.items():
46
+ env_value = os.getenv(env_var)
47
+ if env_value:
48
+ self.config[config_key] = env_value
49
+
50
+ def _validate_config(self):
51
+ """Validate YYDS configuration"""
52
+ if not self.config.get("api_key"):
53
+ logger.debug("YYDS API key not set - some functionality may not work")
54
+
55
+ def get_model_pricing(self, model_name: str) -> Dict[str, float]:
56
+ """Get pricing information for a model - delegated to ModelManager"""
57
+ # Import here to avoid circular imports
58
+ from isa_model.core.model_manager import ModelManager
59
+ model_manager = ModelManager()
60
+ return model_manager.get_model_pricing("yyds", model_name)
61
+
62
+ def calculate_cost(self, model_name: str, input_tokens: int, output_tokens: int) -> float:
63
+ """Calculate cost for a request - delegated to ModelManager"""
64
+ # Import here to avoid circular imports
65
+ from isa_model.core.model_manager import ModelManager
66
+ model_manager = ModelManager()
67
+ return model_manager.calculate_cost("yyds", model_name, input_tokens, output_tokens)
68
+
69
+ def set_api_key(self, api_key: str):
70
+ """Set the API key after initialization"""
71
+ self.config["api_key"] = api_key
72
+ logger.info("YYDS API key updated")
73
+
74
+ def get_capabilities(self) -> Dict[ModelType, List[Capability]]:
75
+ """Get provider capabilities by model type"""
76
+ return {
77
+ ModelType.LLM: [
78
+ Capability.CHAT,
79
+ Capability.COMPLETION
80
+ ]
81
+ }
82
+
83
+ def get_models(self, model_type: ModelType) -> List[str]:
84
+ """Get available models for given type"""
85
+ if model_type == ModelType.LLM:
86
+ return ["claude-sonnet-4-20250514", "claude-3-5-sonnet-20241022"]
87
+ else:
88
+ return []
89
+
90
+ def get_default_model(self, model_type: ModelType) -> str:
91
+ """Get default model for a given type"""
92
+ if model_type == ModelType.LLM:
93
+ return "claude-sonnet-4-20250514"
94
+ else:
95
+ return ""
96
+
97
+ def get_config(self) -> Dict[str, Any]:
98
+ """Get provider configuration"""
99
+ # Return a copy without sensitive information
100
+ config_copy = self.config.copy()
101
+ if "api_key" in config_copy:
102
+ config_copy["api_key"] = "***" if config_copy["api_key"] else ""
103
+ return config_copy
104
+
105
+ def is_reasoning_model(self, model_name: str) -> bool:
106
+ """Check if the model is optimized for reasoning tasks"""
107
+ reasoning_models = ["claude-sonnet-4", "claude-3-5-sonnet"]
108
+ return any(rm in model_name.lower() for rm in reasoning_models)
@@ -5,7 +5,8 @@ File: isa_model/inference/services/__init__.py
5
5
  This module contains service implementations for different AI model types.
6
6
  """
7
7
 
8
- from .base_service import BaseService, BaseLLMService, BaseEmbeddingService
8
+ from .base_service import BaseService, BaseEmbeddingService
9
+ from .llm.base_llm_service import BaseLLMService
9
10
 
10
11
  __all__ = [
11
12
  "BaseService",
@@ -3,7 +3,71 @@ from typing import Dict, Any, List, Union, Optional, BinaryIO
3
3
  from isa_model.inference.services.base_service import BaseService
4
4
 
5
5
  class BaseSTTService(BaseService):
6
- """Base class for Speech-to-Text services"""
6
+ """Base class for Speech-to-Text services with unified task dispatch"""
7
+
8
+ async def invoke(
9
+ self,
10
+ audio_input: Union[str, BinaryIO, List[Union[str, BinaryIO]]],
11
+ task: Optional[str] = None,
12
+ **kwargs
13
+ ) -> Union[Dict[str, Any], List[Dict[str, Any]]]:
14
+ """
15
+ 统一的任务分发方法 - Base类提供通用实现
16
+
17
+ Args:
18
+ audio_input: 音频输入,可以是:
19
+ - str: 音频文件路径
20
+ - BinaryIO: 音频文件对象
21
+ - List: 多个音频文件(批量处理)
22
+ task: 任务类型,支持多种STT任务
23
+ **kwargs: 任务特定的附加参数
24
+
25
+ Returns:
26
+ Dict or List[Dict] containing task results
27
+ """
28
+ task = task or "transcribe"
29
+
30
+ # ==================== 语音转文本类任务 ====================
31
+ if task == "transcribe":
32
+ if isinstance(audio_input, list):
33
+ return await self.transcribe_batch(
34
+ audio_input,
35
+ kwargs.get("language"),
36
+ kwargs.get("prompt")
37
+ )
38
+ else:
39
+ return await self.transcribe(
40
+ audio_input,
41
+ kwargs.get("language"),
42
+ kwargs.get("prompt")
43
+ )
44
+ elif task == "translate":
45
+ if isinstance(audio_input, list):
46
+ raise ValueError("translate task requires single audio input")
47
+ return await self.translate(audio_input)
48
+ elif task == "batch_transcribe":
49
+ if not isinstance(audio_input, list):
50
+ audio_input = [audio_input]
51
+ return await self.transcribe_batch(
52
+ audio_input,
53
+ kwargs.get("language"),
54
+ kwargs.get("prompt")
55
+ )
56
+ elif task == "detect_language":
57
+ if isinstance(audio_input, list):
58
+ raise ValueError("detect_language task requires single audio input")
59
+ return await self.detect_language(audio_input)
60
+ else:
61
+ raise NotImplementedError(f"{self.__class__.__name__} does not support task: {task}")
62
+
63
+ def get_supported_tasks(self) -> List[str]:
64
+ """
65
+ 获取支持的任务列表
66
+
67
+ Returns:
68
+ List of supported task names
69
+ """
70
+ return ["transcribe", "translate", "batch_transcribe", "detect_language"]
7
71
 
8
72
  @abstractmethod
9
73
  async def transcribe(
@@ -3,7 +3,81 @@ from typing import Dict, Any, List, Union, Optional, BinaryIO
3
3
  from isa_model.inference.services.base_service import BaseService
4
4
 
5
5
  class BaseTTSService(BaseService):
6
- """Base class for Text-to-Speech services"""
6
+ """Base class for Text-to-Speech services with unified task dispatch"""
7
+
8
+ async def invoke(
9
+ self,
10
+ text: Union[str, List[str]],
11
+ task: Optional[str] = None,
12
+ **kwargs
13
+ ) -> Union[Dict[str, Any], List[Dict[str, Any]]]:
14
+ """
15
+ 统一的任务分发方法 - Base类提供通用实现
16
+
17
+ Args:
18
+ text: 输入文本,可以是:
19
+ - str: 单个文本
20
+ - List[str]: 多个文本(批量处理)
21
+ task: 任务类型,支持多种TTS任务
22
+ **kwargs: 任务特定的附加参数
23
+
24
+ Returns:
25
+ Dict or List[Dict] containing task results
26
+ """
27
+ task = task or "synthesize"
28
+
29
+ # ==================== 语音合成类任务 ====================
30
+ if task == "synthesize":
31
+ if isinstance(text, list):
32
+ return await self.synthesize_speech_batch(
33
+ text,
34
+ kwargs.get("voice"),
35
+ kwargs.get("speed", 1.0),
36
+ kwargs.get("pitch", 1.0),
37
+ kwargs.get("format", "mp3")
38
+ )
39
+ else:
40
+ return await self.synthesize_speech(
41
+ text,
42
+ kwargs.get("voice"),
43
+ kwargs.get("speed", 1.0),
44
+ kwargs.get("pitch", 1.0),
45
+ kwargs.get("format", "mp3")
46
+ )
47
+ elif task == "synthesize_to_file":
48
+ if not kwargs.get("output_path"):
49
+ raise ValueError("output_path is required for synthesize_to_file task")
50
+ if isinstance(text, list):
51
+ raise ValueError("synthesize_to_file task requires single text input")
52
+ return await self.synthesize_speech_to_file(
53
+ text,
54
+ kwargs["output_path"],
55
+ kwargs.get("voice"),
56
+ kwargs.get("speed", 1.0),
57
+ kwargs.get("pitch", 1.0),
58
+ kwargs.get("format", "mp3")
59
+ )
60
+ elif task == "batch_synthesize":
61
+ if not isinstance(text, list):
62
+ text = [text]
63
+ return await self.synthesize_speech_batch(
64
+ text,
65
+ kwargs.get("voice"),
66
+ kwargs.get("speed", 1.0),
67
+ kwargs.get("pitch", 1.0),
68
+ kwargs.get("format", "mp3")
69
+ )
70
+ else:
71
+ raise NotImplementedError(f"{self.__class__.__name__} does not support task: {task}")
72
+
73
+ def get_supported_tasks(self) -> List[str]:
74
+ """
75
+ 获取支持的任务列表
76
+
77
+ Returns:
78
+ List of supported task names
79
+ """
80
+ return ["synthesize", "synthesize_to_file", "batch_synthesize"]
7
81
 
8
82
  @abstractmethod
9
83
  async def synthesize_speech(