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
@@ -1,21 +1,332 @@
|
|
1
1
|
from abc import ABC, abstractmethod
|
2
2
|
from typing import Dict, Any, List, Union, Optional, AsyncGenerator, Callable
|
3
|
+
import logging
|
4
|
+
|
3
5
|
from isa_model.inference.services.base_service import BaseService
|
4
|
-
from isa_model.inference.services.llm.llm_adapter import AdapterManager
|
6
|
+
from isa_model.inference.services.llm.helpers.llm_adapter import AdapterManager
|
7
|
+
|
8
|
+
logger = logging.getLogger(__name__)
|
5
9
|
|
6
10
|
class BaseLLMService(BaseService):
|
7
|
-
"""Base class for Large Language Model services with unified
|
11
|
+
"""Base class for Large Language Model services with unified task dispatch"""
|
8
12
|
|
9
|
-
def __init__(self,
|
10
|
-
super().__init__(
|
11
|
-
self._bound_tools: List[Any] = []
|
12
|
-
self._tool_mappings: Dict[str, tuple] = {}
|
13
|
+
def __init__(self, provider_name: str, model_name: str, **kwargs):
|
14
|
+
super().__init__(provider_name, model_name, **kwargs)
|
15
|
+
self._bound_tools: List[Any] = []
|
16
|
+
self._tool_mappings: Dict[str, tuple] = {}
|
13
17
|
|
14
18
|
# 初始化适配器管理器
|
15
19
|
self.adapter_manager = AdapterManager()
|
16
20
|
|
17
|
-
# Get
|
18
|
-
|
21
|
+
# Get config from provider
|
22
|
+
provider_config = self.get_provider_config()
|
23
|
+
self.streaming = provider_config.get("streaming", False)
|
24
|
+
self.max_tokens = provider_config.get("max_tokens", 4096)
|
25
|
+
self.temperature = provider_config.get("temperature", 0.7)
|
26
|
+
|
27
|
+
async def invoke(
|
28
|
+
self,
|
29
|
+
input_data: Union[str, List[Dict[str, str]], Any],
|
30
|
+
task: Optional[str] = None,
|
31
|
+
**kwargs
|
32
|
+
) -> Dict[str, Any]:
|
33
|
+
"""
|
34
|
+
统一的任务分发方法 - Base类提供通用实现
|
35
|
+
|
36
|
+
Args:
|
37
|
+
input_data: 输入数据,可以是:
|
38
|
+
- str: 简单文本提示
|
39
|
+
- list: 消息历史 [{"role": "user", "content": "hello"}]
|
40
|
+
- Any: LangChain 消息对象或其他格式
|
41
|
+
task: 任务类型,支持多种LLM任务
|
42
|
+
**kwargs: 任务特定的附加参数
|
43
|
+
|
44
|
+
Returns:
|
45
|
+
Dict containing task results
|
46
|
+
"""
|
47
|
+
task = task or "chat"
|
48
|
+
|
49
|
+
# ==================== 对话类任务 ====================
|
50
|
+
if task == "chat":
|
51
|
+
return await self.chat(input_data, kwargs.get("max_tokens", self.max_tokens))
|
52
|
+
elif task == "complete":
|
53
|
+
return await self.complete_text(input_data, kwargs.get("max_tokens", self.max_tokens))
|
54
|
+
elif task == "instruct":
|
55
|
+
return await self.instruct(input_data, kwargs.get("instruction"), kwargs.get("max_tokens", self.max_tokens))
|
56
|
+
|
57
|
+
# ==================== 文本生成类任务 ====================
|
58
|
+
elif task == "generate":
|
59
|
+
return await self.generate_text(input_data, kwargs.get("max_tokens", self.max_tokens))
|
60
|
+
elif task == "rewrite":
|
61
|
+
return await self.rewrite_text(input_data, kwargs.get("style"), kwargs.get("tone"))
|
62
|
+
elif task == "summarize":
|
63
|
+
return await self.summarize_text(input_data, kwargs.get("max_length"), kwargs.get("style"))
|
64
|
+
elif task == "translate":
|
65
|
+
return await self.translate_text(input_data, kwargs.get("target_language"), kwargs.get("source_language"))
|
66
|
+
|
67
|
+
# ==================== 分析类任务 ====================
|
68
|
+
elif task == "analyze":
|
69
|
+
return await self.analyze_text(input_data, kwargs.get("analysis_type"))
|
70
|
+
elif task == "classify":
|
71
|
+
return await self.classify_text(input_data, kwargs.get("categories"))
|
72
|
+
elif task == "extract":
|
73
|
+
return await self.extract_information(input_data, kwargs.get("extract_type"))
|
74
|
+
elif task == "sentiment":
|
75
|
+
return await self.analyze_sentiment(input_data)
|
76
|
+
|
77
|
+
# ==================== 编程类任务 ====================
|
78
|
+
elif task == "code":
|
79
|
+
return await self.generate_code(input_data, kwargs.get("language"), kwargs.get("style"))
|
80
|
+
elif task == "explain_code":
|
81
|
+
return await self.explain_code(input_data, kwargs.get("language"))
|
82
|
+
elif task == "debug_code":
|
83
|
+
return await self.debug_code(input_data, kwargs.get("language"))
|
84
|
+
elif task == "refactor_code":
|
85
|
+
return await self.refactor_code(input_data, kwargs.get("language"), kwargs.get("improvements"))
|
86
|
+
|
87
|
+
# ==================== 推理类任务 ====================
|
88
|
+
elif task == "reason":
|
89
|
+
return await self.reason_about(input_data, kwargs.get("reasoning_type"))
|
90
|
+
elif task == "solve":
|
91
|
+
return await self.solve_problem(input_data, kwargs.get("problem_type"))
|
92
|
+
elif task == "plan":
|
93
|
+
return await self.create_plan(input_data, kwargs.get("plan_type"))
|
94
|
+
|
95
|
+
# ==================== 工具调用类任务 ====================
|
96
|
+
elif task == "tool_call":
|
97
|
+
return await self.call_tools(input_data, kwargs.get("available_tools"))
|
98
|
+
elif task == "function_call":
|
99
|
+
return await self.call_function(input_data, kwargs.get("function_name"), kwargs.get("parameters"))
|
100
|
+
|
101
|
+
else:
|
102
|
+
raise NotImplementedError(f"{self.__class__.__name__} does not support task: {task}")
|
103
|
+
|
104
|
+
# ==================== 对话类方法 ====================
|
105
|
+
|
106
|
+
async def chat(
|
107
|
+
self,
|
108
|
+
input_data: Union[str, List[Dict[str, str]], Any],
|
109
|
+
max_tokens: Optional[int] = None
|
110
|
+
) -> Dict[str, Any]:
|
111
|
+
"""
|
112
|
+
对话聊天 - Provider必须实现
|
113
|
+
|
114
|
+
Args:
|
115
|
+
input_data: 输入消息
|
116
|
+
max_tokens: 最大生成token数
|
117
|
+
|
118
|
+
Returns:
|
119
|
+
Dict containing chat response
|
120
|
+
"""
|
121
|
+
raise NotImplementedError(f"{self.__class__.__name__} does not support chat task")
|
122
|
+
|
123
|
+
# ==================== 文本生成类方法 ====================
|
124
|
+
|
125
|
+
async def complete_text(
|
126
|
+
self,
|
127
|
+
input_data: Union[str, Any],
|
128
|
+
max_tokens: Optional[int] = None
|
129
|
+
) -> Dict[str, Any]:
|
130
|
+
"""
|
131
|
+
文本补全 - Provider可选实现
|
132
|
+
"""
|
133
|
+
raise NotImplementedError(f"{self.__class__.__name__} does not support complete_text task")
|
134
|
+
|
135
|
+
async def instruct(
|
136
|
+
self,
|
137
|
+
input_data: Union[str, Any],
|
138
|
+
instruction: Optional[str] = None,
|
139
|
+
max_tokens: Optional[int] = None
|
140
|
+
) -> Dict[str, Any]:
|
141
|
+
"""
|
142
|
+
指令跟随 - Provider可选实现
|
143
|
+
"""
|
144
|
+
raise NotImplementedError(f"{self.__class__.__name__} does not support instruct task")
|
145
|
+
|
146
|
+
async def generate_text(
|
147
|
+
self,
|
148
|
+
input_data: Union[str, Any],
|
149
|
+
max_tokens: Optional[int] = None
|
150
|
+
) -> Dict[str, Any]:
|
151
|
+
"""
|
152
|
+
通用文本生成 - Provider可选实现
|
153
|
+
"""
|
154
|
+
raise NotImplementedError(f"{self.__class__.__name__} does not support generate_text task")
|
155
|
+
|
156
|
+
async def rewrite_text(
|
157
|
+
self,
|
158
|
+
input_data: Union[str, Any],
|
159
|
+
style: Optional[str] = None,
|
160
|
+
tone: Optional[str] = None
|
161
|
+
) -> Dict[str, Any]:
|
162
|
+
"""
|
163
|
+
文本重写 - Provider可选实现
|
164
|
+
"""
|
165
|
+
raise NotImplementedError(f"{self.__class__.__name__} does not support rewrite_text task")
|
166
|
+
|
167
|
+
async def summarize_text(
|
168
|
+
self,
|
169
|
+
input_data: Union[str, Any],
|
170
|
+
max_length: Optional[int] = None,
|
171
|
+
style: Optional[str] = None
|
172
|
+
) -> Dict[str, Any]:
|
173
|
+
"""
|
174
|
+
文本摘要 - Provider可选实现
|
175
|
+
"""
|
176
|
+
raise NotImplementedError(f"{self.__class__.__name__} does not support summarize_text task")
|
177
|
+
|
178
|
+
async def translate_text(
|
179
|
+
self,
|
180
|
+
input_data: Union[str, Any],
|
181
|
+
target_language: str,
|
182
|
+
source_language: Optional[str] = None
|
183
|
+
) -> Dict[str, Any]:
|
184
|
+
"""
|
185
|
+
文本翻译 - Provider可选实现
|
186
|
+
"""
|
187
|
+
raise NotImplementedError(f"{self.__class__.__name__} does not support translate_text task")
|
188
|
+
|
189
|
+
# ==================== 分析类方法 ====================
|
190
|
+
|
191
|
+
async def analyze_text(
|
192
|
+
self,
|
193
|
+
input_data: Union[str, Any],
|
194
|
+
analysis_type: Optional[str] = None
|
195
|
+
) -> Dict[str, Any]:
|
196
|
+
"""
|
197
|
+
文本分析 - Provider可选实现
|
198
|
+
"""
|
199
|
+
raise NotImplementedError(f"{self.__class__.__name__} does not support analyze_text task")
|
200
|
+
|
201
|
+
async def classify_text(
|
202
|
+
self,
|
203
|
+
input_data: Union[str, Any],
|
204
|
+
categories: Optional[List[str]] = None
|
205
|
+
) -> Dict[str, Any]:
|
206
|
+
"""
|
207
|
+
文本分类 - Provider可选实现
|
208
|
+
"""
|
209
|
+
raise NotImplementedError(f"{self.__class__.__name__} does not support classify_text task")
|
210
|
+
|
211
|
+
async def extract_information(
|
212
|
+
self,
|
213
|
+
input_data: Union[str, Any],
|
214
|
+
extract_type: Optional[str] = None
|
215
|
+
) -> Dict[str, Any]:
|
216
|
+
"""
|
217
|
+
信息提取 - Provider可选实现
|
218
|
+
"""
|
219
|
+
raise NotImplementedError(f"{self.__class__.__name__} does not support extract_information task")
|
220
|
+
|
221
|
+
async def analyze_sentiment(
|
222
|
+
self,
|
223
|
+
input_data: Union[str, Any]
|
224
|
+
) -> Dict[str, Any]:
|
225
|
+
"""
|
226
|
+
情感分析 - Provider可选实现
|
227
|
+
"""
|
228
|
+
raise NotImplementedError(f"{self.__class__.__name__} does not support analyze_sentiment task")
|
229
|
+
|
230
|
+
# ==================== 编程类方法 ====================
|
231
|
+
|
232
|
+
async def generate_code(
|
233
|
+
self,
|
234
|
+
input_data: Union[str, Any],
|
235
|
+
language: Optional[str] = None,
|
236
|
+
style: Optional[str] = None
|
237
|
+
) -> Dict[str, Any]:
|
238
|
+
"""
|
239
|
+
代码生成 - Provider可选实现
|
240
|
+
"""
|
241
|
+
raise NotImplementedError(f"{self.__class__.__name__} does not support generate_code task")
|
242
|
+
|
243
|
+
async def explain_code(
|
244
|
+
self,
|
245
|
+
input_data: Union[str, Any],
|
246
|
+
language: Optional[str] = None
|
247
|
+
) -> Dict[str, Any]:
|
248
|
+
"""
|
249
|
+
代码解释 - Provider可选实现
|
250
|
+
"""
|
251
|
+
raise NotImplementedError(f"{self.__class__.__name__} does not support explain_code task")
|
252
|
+
|
253
|
+
async def debug_code(
|
254
|
+
self,
|
255
|
+
input_data: Union[str, Any],
|
256
|
+
language: Optional[str] = None
|
257
|
+
) -> Dict[str, Any]:
|
258
|
+
"""
|
259
|
+
代码调试 - Provider可选实现
|
260
|
+
"""
|
261
|
+
raise NotImplementedError(f"{self.__class__.__name__} does not support debug_code task")
|
262
|
+
|
263
|
+
async def refactor_code(
|
264
|
+
self,
|
265
|
+
input_data: Union[str, Any],
|
266
|
+
language: Optional[str] = None,
|
267
|
+
improvements: Optional[List[str]] = None
|
268
|
+
) -> Dict[str, Any]:
|
269
|
+
"""
|
270
|
+
代码重构 - Provider可选实现
|
271
|
+
"""
|
272
|
+
raise NotImplementedError(f"{self.__class__.__name__} does not support refactor_code task")
|
273
|
+
|
274
|
+
# ==================== 推理类方法 ====================
|
275
|
+
|
276
|
+
async def reason_about(
|
277
|
+
self,
|
278
|
+
input_data: Union[str, Any],
|
279
|
+
reasoning_type: Optional[str] = None
|
280
|
+
) -> Dict[str, Any]:
|
281
|
+
"""
|
282
|
+
推理分析 - Provider可选实现
|
283
|
+
"""
|
284
|
+
raise NotImplementedError(f"{self.__class__.__name__} does not support reason_about task")
|
285
|
+
|
286
|
+
async def solve_problem(
|
287
|
+
self,
|
288
|
+
input_data: Union[str, Any],
|
289
|
+
problem_type: Optional[str] = None
|
290
|
+
) -> Dict[str, Any]:
|
291
|
+
"""
|
292
|
+
问题求解 - Provider可选实现
|
293
|
+
"""
|
294
|
+
raise NotImplementedError(f"{self.__class__.__name__} does not support solve_problem task")
|
295
|
+
|
296
|
+
async def create_plan(
|
297
|
+
self,
|
298
|
+
input_data: Union[str, Any],
|
299
|
+
plan_type: Optional[str] = None
|
300
|
+
) -> Dict[str, Any]:
|
301
|
+
"""
|
302
|
+
计划制定 - Provider可选实现
|
303
|
+
"""
|
304
|
+
raise NotImplementedError(f"{self.__class__.__name__} does not support create_plan task")
|
305
|
+
|
306
|
+
# ==================== 工具调用类方法 ====================
|
307
|
+
|
308
|
+
async def call_tools(
|
309
|
+
self,
|
310
|
+
input_data: Union[str, Any],
|
311
|
+
available_tools: Optional[List[Any]] = None
|
312
|
+
) -> Dict[str, Any]:
|
313
|
+
"""
|
314
|
+
工具调用 - Provider可选实现
|
315
|
+
"""
|
316
|
+
raise NotImplementedError(f"{self.__class__.__name__} does not support call_tools task")
|
317
|
+
|
318
|
+
async def call_function(
|
319
|
+
self,
|
320
|
+
input_data: Union[str, Any],
|
321
|
+
function_name: str,
|
322
|
+
parameters: Optional[Dict[str, Any]] = None
|
323
|
+
) -> Dict[str, Any]:
|
324
|
+
"""
|
325
|
+
函数调用 - Provider可选实现
|
326
|
+
"""
|
327
|
+
raise NotImplementedError(f"{self.__class__.__name__} does not support call_function task")
|
328
|
+
|
329
|
+
# ==================== 工具绑定和管理 ====================
|
19
330
|
|
20
331
|
def bind_tools(self, tools: List[Any], **kwargs) -> 'BaseLLMService':
|
21
332
|
"""
|
@@ -51,6 +362,22 @@ class BaseLLMService(BaseService):
|
|
51
362
|
"""使用适配器管理器执行工具调用"""
|
52
363
|
return await self.adapter_manager.execute_tool(tool_name, arguments, self._tool_mappings)
|
53
364
|
|
365
|
+
@abstractmethod
|
366
|
+
async def astream(self, input_data: Union[str, List[Dict[str, str]], Any]) -> AsyncGenerator[str, None]:
|
367
|
+
"""
|
368
|
+
True streaming method that yields tokens one by one as they arrive
|
369
|
+
|
370
|
+
Args:
|
371
|
+
input_data: Can be:
|
372
|
+
- str: Simple text prompt
|
373
|
+
- list: Message history like [{"role": "user", "content": "hello"}]
|
374
|
+
- Any: LangChain message objects or other formats
|
375
|
+
|
376
|
+
Yields:
|
377
|
+
Individual tokens as they arrive from the model
|
378
|
+
"""
|
379
|
+
pass
|
380
|
+
|
54
381
|
@abstractmethod
|
55
382
|
async def ainvoke(self, input_data: Union[str, List[Dict[str, str]], Any]) -> Union[str, Any]:
|
56
383
|
"""
|
@@ -67,28 +394,22 @@ class BaseLLMService(BaseService):
|
|
67
394
|
"""
|
68
395
|
pass
|
69
396
|
|
70
|
-
def
|
397
|
+
def stream(self, input_data: Union[str, List[Dict[str, str]], Any]):
|
71
398
|
"""
|
72
|
-
Synchronous wrapper for
|
399
|
+
Synchronous wrapper for astream - returns the async generator
|
73
400
|
|
74
401
|
Args:
|
75
|
-
input_data: Same as
|
402
|
+
input_data: Same as astream
|
76
403
|
|
77
404
|
Returns:
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
with concurrent.futures.ThreadPoolExecutor() as executor:
|
87
|
-
future = executor.submit(asyncio.run, self.ainvoke(input_data))
|
88
|
-
return future.result()
|
89
|
-
except RuntimeError:
|
90
|
-
# No event loop running, create a new one
|
91
|
-
return asyncio.run(self.ainvoke(input_data))
|
405
|
+
AsyncGenerator that yields tokens
|
406
|
+
|
407
|
+
Usage:
|
408
|
+
async for token in llm.stream("Hello"):
|
409
|
+
print(token, end="", flush=True)
|
410
|
+
"""
|
411
|
+
return self.astream(input_data)
|
412
|
+
|
92
413
|
|
93
414
|
def _has_bound_tools(self) -> bool:
|
94
415
|
"""Check if this service has bound tools"""
|
@@ -122,19 +443,182 @@ class BaseLLMService(BaseService):
|
|
122
443
|
"""Get last request usage with cost information"""
|
123
444
|
usage = self.get_last_token_usage()
|
124
445
|
|
125
|
-
# Calculate cost using
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
else:
|
133
|
-
cost = 0.0
|
446
|
+
# Calculate cost using centralized pricing manager
|
447
|
+
cost = self.model_manager.calculate_cost(
|
448
|
+
provider=self.provider_name,
|
449
|
+
model_name=self.model_name,
|
450
|
+
input_tokens=usage.get("prompt_tokens", 0),
|
451
|
+
output_tokens=usage.get("completion_tokens", 0)
|
452
|
+
)
|
134
453
|
|
135
454
|
return {
|
136
455
|
**usage,
|
137
456
|
"cost_usd": cost,
|
138
457
|
"model": self.model_name,
|
139
|
-
"provider":
|
458
|
+
"provider": self.provider_name
|
459
|
+
}
|
460
|
+
|
461
|
+
async def _track_llm_usage(
|
462
|
+
self,
|
463
|
+
operation: str,
|
464
|
+
input_tokens: Optional[int] = None,
|
465
|
+
output_tokens: Optional[int] = None,
|
466
|
+
metadata: Optional[Dict[str, Any]] = None
|
467
|
+
) -> float:
|
468
|
+
"""
|
469
|
+
Track LLM usage using the unified BaseService billing system
|
470
|
+
|
471
|
+
Returns:
|
472
|
+
Cost in USD
|
473
|
+
"""
|
474
|
+
from isa_model.core.types import ServiceType
|
475
|
+
|
476
|
+
await self._track_usage(
|
477
|
+
service_type=ServiceType.LLM,
|
478
|
+
operation=operation,
|
479
|
+
input_tokens=input_tokens,
|
480
|
+
output_tokens=output_tokens,
|
481
|
+
metadata=metadata
|
482
|
+
)
|
483
|
+
|
484
|
+
# Return calculated cost
|
485
|
+
if input_tokens is not None and output_tokens is not None:
|
486
|
+
return self.model_manager.calculate_cost(
|
487
|
+
provider=self.provider_name,
|
488
|
+
model_name=self.model_name,
|
489
|
+
input_tokens=input_tokens,
|
490
|
+
output_tokens=output_tokens
|
491
|
+
)
|
492
|
+
return 0.0
|
493
|
+
|
494
|
+
# ==================== METADATA AND UTILITY METHODS ====================
|
495
|
+
|
496
|
+
def get_supported_tasks(self) -> List[str]:
|
497
|
+
"""
|
498
|
+
获取provider支持的任务列表
|
499
|
+
|
500
|
+
Returns:
|
501
|
+
List of supported task names
|
502
|
+
"""
|
503
|
+
supported = []
|
504
|
+
|
505
|
+
# 检查各类任务支持情况
|
506
|
+
method_task_map = {
|
507
|
+
# 对话类
|
508
|
+
'chat': 'chat',
|
509
|
+
'complete_text': 'complete',
|
510
|
+
'instruct': 'instruct',
|
511
|
+
# 文本生成类
|
512
|
+
'generate_text': 'generate',
|
513
|
+
'rewrite_text': 'rewrite',
|
514
|
+
'summarize_text': 'summarize',
|
515
|
+
'translate_text': 'translate',
|
516
|
+
# 分析类
|
517
|
+
'analyze_text': 'analyze',
|
518
|
+
'classify_text': 'classify',
|
519
|
+
'extract_information': 'extract',
|
520
|
+
'analyze_sentiment': 'sentiment',
|
521
|
+
# 编程类
|
522
|
+
'generate_code': 'code',
|
523
|
+
'explain_code': 'explain_code',
|
524
|
+
'debug_code': 'debug_code',
|
525
|
+
'refactor_code': 'refactor_code',
|
526
|
+
# 推理类
|
527
|
+
'reason_about': 'reason',
|
528
|
+
'solve_problem': 'solve',
|
529
|
+
'create_plan': 'plan',
|
530
|
+
# 工具调用类
|
531
|
+
'call_tools': 'tool_call',
|
532
|
+
'call_function': 'function_call'
|
533
|
+
}
|
534
|
+
|
535
|
+
for method_name, task_name in method_task_map.items():
|
536
|
+
if hasattr(self, method_name):
|
537
|
+
# 检查是否是默认实现还是provider自己的实现
|
538
|
+
try:
|
539
|
+
import inspect
|
540
|
+
source = inspect.getsource(getattr(self, method_name))
|
541
|
+
if 'NotImplementedError' not in source:
|
542
|
+
supported.append(task_name)
|
543
|
+
except:
|
544
|
+
# 如果无法检查源码,假设支持
|
545
|
+
supported.append(task_name)
|
546
|
+
|
547
|
+
return supported
|
548
|
+
|
549
|
+
def get_supported_languages(self) -> List[str]:
|
550
|
+
"""
|
551
|
+
获取支持的编程语言列表 - Provider应该实现
|
552
|
+
|
553
|
+
Returns:
|
554
|
+
List of supported programming languages
|
555
|
+
"""
|
556
|
+
return [
|
557
|
+
'python', 'javascript', 'typescript', 'java', 'c++', 'c#',
|
558
|
+
'go', 'rust', 'php', 'ruby', 'swift', 'kotlin', 'scala',
|
559
|
+
'r', 'matlab', 'sql', 'html', 'css', 'bash', 'powershell'
|
560
|
+
] # 通用语言支持
|
561
|
+
|
562
|
+
def get_max_context_length(self) -> int:
|
563
|
+
"""
|
564
|
+
获取最大上下文长度 - Provider应该实现
|
565
|
+
|
566
|
+
Returns:
|
567
|
+
Maximum context length in tokens
|
568
|
+
"""
|
569
|
+
return self.max_tokens or 4096 # 默认值
|
570
|
+
|
571
|
+
def get_supported_formats(self) -> List[str]:
|
572
|
+
"""
|
573
|
+
获取支持的输入格式 - Provider应该实现
|
574
|
+
|
575
|
+
Returns:
|
576
|
+
List of supported input formats
|
577
|
+
"""
|
578
|
+
return ['text', 'json', 'markdown', 'code'] # 通用格式
|
579
|
+
|
580
|
+
def supports_streaming(self) -> bool:
|
581
|
+
"""
|
582
|
+
检查是否支持流式输出
|
583
|
+
|
584
|
+
Returns:
|
585
|
+
True if streaming is supported
|
586
|
+
"""
|
587
|
+
return self.streaming
|
588
|
+
|
589
|
+
def supports_function_calling(self) -> bool:
|
590
|
+
"""
|
591
|
+
检查是否支持函数调用
|
592
|
+
|
593
|
+
Returns:
|
594
|
+
True if function calling is supported
|
595
|
+
"""
|
596
|
+
return hasattr(self, 'call_tools') or hasattr(self, 'call_function')
|
597
|
+
|
598
|
+
def get_temperature_range(self) -> Dict[str, float]:
|
599
|
+
"""
|
600
|
+
获取温度参数范围
|
601
|
+
|
602
|
+
Returns:
|
603
|
+
Dict with min and max temperature values
|
604
|
+
"""
|
605
|
+
return {"min": 0.0, "max": 2.0, "default": self.temperature}
|
606
|
+
|
607
|
+
def get_provider_info(self) -> Dict[str, Any]:
|
608
|
+
"""
|
609
|
+
获取provider信息
|
610
|
+
|
611
|
+
Returns:
|
612
|
+
Dict containing provider information
|
613
|
+
"""
|
614
|
+
return {
|
615
|
+
"provider": self.provider_name,
|
616
|
+
"model": self.model_name,
|
617
|
+
"max_tokens": self.max_tokens,
|
618
|
+
"temperature": self.temperature,
|
619
|
+
"streaming": self.streaming,
|
620
|
+
"supports_tools": self.supports_function_calling(),
|
621
|
+
"supported_tasks": self.get_supported_tasks(),
|
622
|
+
"supported_languages": self.get_supported_languages(),
|
623
|
+
"max_context_length": self.get_max_context_length()
|
140
624
|
}
|
@@ -120,7 +120,12 @@ class LangChainMessageAdapter:
|
|
120
120
|
msg_dict["role"] = "tool"
|
121
121
|
if hasattr(msg, 'tool_call_id'):
|
122
122
|
msg_dict["tool_call_id"] = msg.tool_call_id
|
123
|
+
elif msg.type == "function": # Legacy function message
|
124
|
+
msg_dict["role"] = "function"
|
125
|
+
if hasattr(msg, 'name'):
|
126
|
+
msg_dict["name"] = msg.name
|
123
127
|
else:
|
128
|
+
# Unknown message type, default to user
|
124
129
|
msg_dict["role"] = "user"
|
125
130
|
|
126
131
|
converted_messages.append(msg_dict)
|
@@ -274,6 +279,40 @@ class DictToolAdapter:
|
|
274
279
|
return f"Error: Cannot execute dict tool {tool_name} directly. Requires external executor."
|
275
280
|
|
276
281
|
|
282
|
+
# ============= MCP 工具适配器 =============
|
283
|
+
|
284
|
+
class MCPToolAdapter:
|
285
|
+
"""MCP 工具适配器 - 处理 MCP 协议的工具格式"""
|
286
|
+
|
287
|
+
def __init__(self):
|
288
|
+
self.adapter_name = "mcp_tool"
|
289
|
+
self.priority = 7 # 高优先级,在 LangChain 和 Dict 之间
|
290
|
+
|
291
|
+
def can_handle(self, tool: Any) -> bool:
|
292
|
+
"""检查是否是 MCP 工具格式"""
|
293
|
+
return (isinstance(tool, dict) and
|
294
|
+
"name" in tool and
|
295
|
+
"description" in tool and
|
296
|
+
"inputSchema" in tool and
|
297
|
+
isinstance(tool["inputSchema"], dict))
|
298
|
+
|
299
|
+
def to_openai_schema(self, tool: Any) -> Dict[str, Any]:
|
300
|
+
"""转换 MCP 工具为 OpenAI schema"""
|
301
|
+
return {
|
302
|
+
"type": "function",
|
303
|
+
"function": {
|
304
|
+
"name": tool["name"],
|
305
|
+
"description": tool["description"],
|
306
|
+
"parameters": tool.get("inputSchema", {"type": "object", "properties": {}})
|
307
|
+
}
|
308
|
+
}
|
309
|
+
|
310
|
+
async def execute_tool(self, tool: Any, arguments: Dict[str, Any]) -> Any:
|
311
|
+
"""MCP 工具执行由外部处理,这里返回指示信息"""
|
312
|
+
tool_name = tool["name"]
|
313
|
+
return f"MCP tool {tool_name} execution should be handled externally by MCP client"
|
314
|
+
|
315
|
+
|
277
316
|
# ============= Python 函数适配器 =============
|
278
317
|
|
279
318
|
class PythonFunctionAdapter:
|
@@ -424,6 +463,7 @@ class AdapterManager:
|
|
424
463
|
self.tool_adapters = [
|
425
464
|
DictToolAdapter(), # 最高优先级 - OpenAI格式工具
|
426
465
|
LangChainToolAdapter(), # 中等优先级 - LangChain工具
|
466
|
+
MCPToolAdapter(), # 高优先级 - MCP工具
|
427
467
|
PythonFunctionAdapter() # 最低优先级 - Python函数
|
428
468
|
]
|
429
469
|
|