isa-model 0.3.5__py3-none-any.whl → 0.3.7__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 +937 -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/{storage/supabase_storage.py → models/model_repo.py} +72 -73
- 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/modal/isa_vision_doc_service.py +157 -3
- isa_model/deployment/cloud/modal/isa_vision_table_service.py +532 -0
- isa_model/deployment/cloud/modal/isa_vision_ui_service.py +104 -3
- 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 +538 -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/deployment/services/simple_auto_deploy_vision_service.py +275 -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 +257 -601
- 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 -17
- 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 -1
- isa_model/inference/services/{stacked → img}/flux_professional_service.py +25 -1
- isa_model/inference/services/{stacked → img/helpers}/base_stacked_service.py +40 -35
- isa_model/inference/services/{vision → img}/replicate_image_gen_service.py +44 -31
- isa_model/inference/services/llm/__init__.py +3 -3
- isa_model/inference/services/llm/base_llm_service.py +492 -40
- 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 +51 -17
- isa_model/inference/services/llm/openai_llm_service.py +70 -19
- isa_model/inference/services/llm/yyds_llm_service.py +24 -23
- isa_model/inference/services/vision/__init__.py +38 -4
- isa_model/inference/services/vision/base_vision_service.py +218 -117
- isa_model/inference/services/vision/{isA_vision_service.py → disabled/isA_vision_service.py} +98 -0
- isa_model/inference/services/{stacked → vision}/doc_analysis_service.py +1 -1
- 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 +104 -307
- isa_model/inference/services/vision/replicate_vision_service.py +140 -325
- isa_model/inference/services/{stacked → vision}/ui_analysis_service.py +2 -498
- isa_model/scripts/register_models.py +370 -0
- isa_model/scripts/register_models_with_embeddings.py +510 -0
- isa_model/serving/api/fastapi_server.py +6 -1
- isa_model/serving/api/routes/unified.py +274 -0
- {isa_model-0.3.5.dist-info → isa_model-0.3.7.dist-info}/METADATA +4 -1
- {isa_model-0.3.5.dist-info → isa_model-0.3.7.dist-info}/RECORD +78 -53
- isa_model/config/__init__.py +0 -9
- isa_model/config/config_manager.py +0 -213
- isa_model/core/model_manager.py +0 -213
- isa_model/core/model_registry.py +0 -375
- isa_model/core/vision_models_init.py +0 -116
- isa_model/inference/billing_tracker.py +0 -406
- isa_model/inference/services/llm/triton_llm_service.py +0 -481
- isa_model/inference/services/stacked/__init__.py +0 -26
- isa_model/inference/services/stacked/config.py +0 -426
- isa_model/inference/services/vision/ollama_vision_service.py +0 -194
- /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/inference/services/llm/{llm_adapter.py → helpers/llm_adapter.py} +0 -0
- {isa_model-0.3.5.dist-info → isa_model-0.3.7.dist-info}/WHEEL +0 -0
- {isa_model-0.3.5.dist-info → isa_model-0.3.7.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,258 @@
|
|
1
|
+
#!/usr/bin/env python
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
|
4
|
+
"""
|
5
|
+
LLM通用提示词模板和工具函数
|
6
|
+
提供标准化的提示词模板,用于不同类型的LLM任务
|
7
|
+
"""
|
8
|
+
|
9
|
+
from typing import Dict, List, Optional, Any
|
10
|
+
import json
|
11
|
+
|
12
|
+
class LLMPrompts:
|
13
|
+
"""LLM提示词模板管理器"""
|
14
|
+
|
15
|
+
# ==================== 对话类提示词 ====================
|
16
|
+
|
17
|
+
@staticmethod
|
18
|
+
def chat_prompt(user_message: str, system_message: Optional[str] = None) -> List[Dict[str, str]]:
|
19
|
+
"""生成标准对话提示词格式"""
|
20
|
+
messages = []
|
21
|
+
if system_message:
|
22
|
+
messages.append({"role": "system", "content": system_message})
|
23
|
+
messages.append({"role": "user", "content": user_message})
|
24
|
+
return messages
|
25
|
+
|
26
|
+
@staticmethod
|
27
|
+
def completion_prompt(text: str, instruction: Optional[str] = None) -> str:
|
28
|
+
"""生成文本补全提示词"""
|
29
|
+
if instruction:
|
30
|
+
return f"{instruction}\n\n{text}"
|
31
|
+
return text
|
32
|
+
|
33
|
+
@staticmethod
|
34
|
+
def instruct_prompt(task: str, content: str, format_instruction: Optional[str] = None) -> str:
|
35
|
+
"""生成指令跟随提示词"""
|
36
|
+
prompt = f"Task: {task}\n\nContent:\n{content}\n\n"
|
37
|
+
if format_instruction:
|
38
|
+
prompt += f"Output Format: {format_instruction}\n\n"
|
39
|
+
prompt += "Response:"
|
40
|
+
return prompt
|
41
|
+
|
42
|
+
# ==================== 文本生成类提示词 ====================
|
43
|
+
|
44
|
+
@staticmethod
|
45
|
+
def rewrite_prompt(text: str, style: Optional[str] = None, tone: Optional[str] = None) -> str:
|
46
|
+
"""生成文本重写提示词"""
|
47
|
+
prompt = f"Please rewrite the following text"
|
48
|
+
if style:
|
49
|
+
prompt += f" in {style} style"
|
50
|
+
if tone:
|
51
|
+
prompt += f" with a {tone} tone"
|
52
|
+
prompt += f":\n\n{text}\n\nRewritten text:"
|
53
|
+
return prompt
|
54
|
+
|
55
|
+
@staticmethod
|
56
|
+
def summarize_prompt(text: str, max_length: Optional[int] = None, style: Optional[str] = None) -> str:
|
57
|
+
"""生成文本摘要提示词"""
|
58
|
+
prompt = f"Please summarize the following text"
|
59
|
+
if max_length:
|
60
|
+
prompt += f" in no more than {max_length} words"
|
61
|
+
if style:
|
62
|
+
prompt += f" in {style} style"
|
63
|
+
prompt += f":\n\n{text}\n\nSummary:"
|
64
|
+
return prompt
|
65
|
+
|
66
|
+
@staticmethod
|
67
|
+
def translate_prompt(text: str, target_language: str, source_language: Optional[str] = None) -> str:
|
68
|
+
"""生成翻译提示词"""
|
69
|
+
prompt = f"Please translate the following text"
|
70
|
+
if source_language:
|
71
|
+
prompt += f" from {source_language}"
|
72
|
+
prompt += f" to {target_language}:\n\n{text}\n\nTranslation:"
|
73
|
+
return prompt
|
74
|
+
|
75
|
+
# ==================== 分析类提示词 ====================
|
76
|
+
|
77
|
+
@staticmethod
|
78
|
+
def analyze_prompt(text: str, analysis_type: Optional[str] = None) -> str:
|
79
|
+
"""生成文本分析提示词"""
|
80
|
+
if analysis_type:
|
81
|
+
prompt = f"Please perform {analysis_type} analysis on the following text:\n\n{text}\n\nAnalysis:"
|
82
|
+
else:
|
83
|
+
prompt = f"Please analyze the following text:\n\n{text}\n\nAnalysis:"
|
84
|
+
return prompt
|
85
|
+
|
86
|
+
@staticmethod
|
87
|
+
def classify_prompt(text: str, categories: Optional[List[str]] = None) -> str:
|
88
|
+
"""生成文本分类提示词"""
|
89
|
+
prompt = f"Please classify the following text"
|
90
|
+
if categories:
|
91
|
+
prompt += f" into one of these categories: {', '.join(categories)}"
|
92
|
+
prompt += f":\n\n{text}\n\nClassification:"
|
93
|
+
return prompt
|
94
|
+
|
95
|
+
@staticmethod
|
96
|
+
def extract_prompt(text: str, extract_type: Optional[str] = None) -> str:
|
97
|
+
"""生成信息提取提示词"""
|
98
|
+
if extract_type:
|
99
|
+
prompt = f"Please extract {extract_type} from the following text:\n\n{text}\n\nExtracted {extract_type}:"
|
100
|
+
else:
|
101
|
+
prompt = f"Please extract key information from the following text:\n\n{text}\n\nExtracted information:"
|
102
|
+
return prompt
|
103
|
+
|
104
|
+
@staticmethod
|
105
|
+
def sentiment_prompt(text: str) -> str:
|
106
|
+
"""生成情感分析提示词"""
|
107
|
+
return f"Please analyze the sentiment of the following text and classify it as positive, negative, or neutral:\n\n{text}\n\nSentiment:"
|
108
|
+
|
109
|
+
# ==================== 编程类提示词 ====================
|
110
|
+
|
111
|
+
@staticmethod
|
112
|
+
def code_generation_prompt(description: str, language: Optional[str] = None, style: Optional[str] = None) -> str:
|
113
|
+
"""生成代码生成提示词"""
|
114
|
+
prompt = f"Please write code"
|
115
|
+
if language:
|
116
|
+
prompt += f" in {language}"
|
117
|
+
prompt += f" for the following requirement:\n\n{description}\n\n"
|
118
|
+
if style:
|
119
|
+
prompt += f"Code style: {style}\n\n"
|
120
|
+
prompt += "Code:"
|
121
|
+
return prompt
|
122
|
+
|
123
|
+
@staticmethod
|
124
|
+
def code_explanation_prompt(code: str, language: Optional[str] = None) -> str:
|
125
|
+
"""生成代码解释提示词"""
|
126
|
+
prompt = f"Please explain the following"
|
127
|
+
if language:
|
128
|
+
prompt += f" {language}"
|
129
|
+
prompt += f" code:\n\n```{language or ''}\n{code}\n```\n\nExplanation:"
|
130
|
+
return prompt
|
131
|
+
|
132
|
+
@staticmethod
|
133
|
+
def code_debug_prompt(code: str, language: Optional[str] = None, error: Optional[str] = None) -> str:
|
134
|
+
"""生成代码调试提示词"""
|
135
|
+
prompt = f"Please debug the following"
|
136
|
+
if language:
|
137
|
+
prompt += f" {language}"
|
138
|
+
prompt += f" code:\n\n```{language or ''}\n{code}\n```\n\n"
|
139
|
+
if error:
|
140
|
+
prompt += f"Error message: {error}\n\n"
|
141
|
+
prompt += "Fixed code:"
|
142
|
+
return prompt
|
143
|
+
|
144
|
+
@staticmethod
|
145
|
+
def code_refactor_prompt(code: str, language: Optional[str] = None, improvements: Optional[List[str]] = None) -> str:
|
146
|
+
"""生成代码重构提示词"""
|
147
|
+
prompt = f"Please refactor the following"
|
148
|
+
if language:
|
149
|
+
prompt += f" {language}"
|
150
|
+
prompt += f" code"
|
151
|
+
if improvements:
|
152
|
+
prompt += f" with these improvements: {', '.join(improvements)}"
|
153
|
+
prompt += f":\n\n```{language or ''}\n{code}\n```\n\nRefactored code:"
|
154
|
+
return prompt
|
155
|
+
|
156
|
+
# ==================== 推理类提示词 ====================
|
157
|
+
|
158
|
+
@staticmethod
|
159
|
+
def reasoning_prompt(question: str, reasoning_type: Optional[str] = None) -> str:
|
160
|
+
"""生成推理分析提示词"""
|
161
|
+
if reasoning_type:
|
162
|
+
prompt = f"Please use {reasoning_type} reasoning to answer the following question:\n\n{question}\n\nReasoning and answer:"
|
163
|
+
else:
|
164
|
+
prompt = f"Please think step by step and answer the following question:\n\n{question}\n\nReasoning and answer:"
|
165
|
+
return prompt
|
166
|
+
|
167
|
+
@staticmethod
|
168
|
+
def problem_solving_prompt(problem: str, problem_type: Optional[str] = None) -> str:
|
169
|
+
"""生成问题求解提示词"""
|
170
|
+
prompt = f"Please solve the following"
|
171
|
+
if problem_type:
|
172
|
+
prompt += f" {problem_type}"
|
173
|
+
prompt += f" problem:\n\n{problem}\n\nSolution:"
|
174
|
+
return prompt
|
175
|
+
|
176
|
+
@staticmethod
|
177
|
+
def planning_prompt(goal: str, plan_type: Optional[str] = None) -> str:
|
178
|
+
"""生成计划制定提示词"""
|
179
|
+
prompt = f"Please create a"
|
180
|
+
if plan_type:
|
181
|
+
prompt += f" {plan_type}"
|
182
|
+
prompt += f" plan for the following goal:\n\n{goal}\n\nPlan:"
|
183
|
+
return prompt
|
184
|
+
|
185
|
+
# ==================== 工具调用类提示词 ====================
|
186
|
+
|
187
|
+
@staticmethod
|
188
|
+
def tool_call_prompt(query: str, available_tools: List[Dict[str, Any]]) -> str:
|
189
|
+
"""生成工具调用提示词"""
|
190
|
+
tools_desc = json.dumps(available_tools, indent=2)
|
191
|
+
prompt = f"You have access to the following tools:\n\n{tools_desc}\n\n"
|
192
|
+
prompt += f"User query: {query}\n\n"
|
193
|
+
prompt += "Please use the appropriate tools to answer the query. Respond with tool calls in JSON format."
|
194
|
+
return prompt
|
195
|
+
|
196
|
+
@staticmethod
|
197
|
+
def function_call_prompt(description: str, function_name: str, parameters: Dict[str, Any]) -> str:
|
198
|
+
"""生成函数调用提示词"""
|
199
|
+
params_desc = json.dumps(parameters, indent=2)
|
200
|
+
prompt = f"Please call the function '{function_name}' with the following parameters to {description}:\n\n"
|
201
|
+
prompt += f"Parameters:\n{params_desc}\n\n"
|
202
|
+
prompt += "Function call result:"
|
203
|
+
return prompt
|
204
|
+
|
205
|
+
class LLMPromptTemplates:
|
206
|
+
"""预定义的提示词模板"""
|
207
|
+
|
208
|
+
# 系统消息模板
|
209
|
+
SYSTEM_MESSAGES = {
|
210
|
+
"assistant": "You are a helpful AI assistant. Please provide accurate and helpful responses.",
|
211
|
+
"code_expert": "You are an expert programmer. Please provide clean, efficient, and well-documented code.",
|
212
|
+
"analyst": "You are a data analyst. Please provide detailed and insightful analysis.",
|
213
|
+
"writer": "You are a professional writer. Please provide clear, engaging, and well-structured content.",
|
214
|
+
"teacher": "You are a patient and knowledgeable teacher. Please explain concepts clearly and provide examples.",
|
215
|
+
"translator": "You are a professional translator. Please provide accurate and natural translations."
|
216
|
+
}
|
217
|
+
|
218
|
+
# 输出格式模板
|
219
|
+
OUTPUT_FORMATS = {
|
220
|
+
"json": "Please format your response as valid JSON.",
|
221
|
+
"markdown": "Please format your response using Markdown syntax.",
|
222
|
+
"bullet_points": "Please format your response as bullet points.",
|
223
|
+
"numbered_list": "Please format your response as a numbered list.",
|
224
|
+
"table": "Please format your response as a table.",
|
225
|
+
"code": "Please format your response as code with appropriate syntax highlighting."
|
226
|
+
}
|
227
|
+
|
228
|
+
# 任务特定模板
|
229
|
+
TASK_TEMPLATES = {
|
230
|
+
"email": "Please write a professional email with the following content:",
|
231
|
+
"report": "Please write a detailed report on the following topic:",
|
232
|
+
"proposal": "Please write a project proposal for the following idea:",
|
233
|
+
"documentation": "Please write clear documentation for the following:",
|
234
|
+
"test_cases": "Please write comprehensive test cases for the following:",
|
235
|
+
"user_story": "Please write user stories for the following feature:"
|
236
|
+
}
|
237
|
+
|
238
|
+
def get_prompt_template(task_type: str, **kwargs) -> str:
|
239
|
+
"""获取指定任务类型的提示词模板"""
|
240
|
+
prompts = LLMPrompts()
|
241
|
+
|
242
|
+
if hasattr(prompts, f"{task_type}_prompt"):
|
243
|
+
method = getattr(prompts, f"{task_type}_prompt")
|
244
|
+
return method(**kwargs)
|
245
|
+
else:
|
246
|
+
raise ValueError(f"Unsupported task type: {task_type}")
|
247
|
+
|
248
|
+
def format_messages(messages: List[Dict[str, str]], system_message: Optional[str] = None) -> List[Dict[str, str]]:
|
249
|
+
"""格式化消息列表,添加系统消息"""
|
250
|
+
formatted = []
|
251
|
+
if system_message:
|
252
|
+
formatted.append({"role": "system", "content": system_message})
|
253
|
+
formatted.extend(messages)
|
254
|
+
return formatted
|
255
|
+
|
256
|
+
def combine_prompts(prompts: List[str], separator: str = "\n\n") -> str:
|
257
|
+
"""组合多个提示词"""
|
258
|
+
return separator.join(prompts)
|
@@ -0,0 +1,280 @@
|
|
1
|
+
#!/usr/bin/env python
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
|
4
|
+
"""
|
5
|
+
LLM工具函数和实用程序
|
6
|
+
提供LLM服务的通用工具函数,包括文本处理、token计算、响应解析等
|
7
|
+
"""
|
8
|
+
|
9
|
+
import re
|
10
|
+
import json
|
11
|
+
import tiktoken
|
12
|
+
from typing import Dict, List, Optional, Any, Union, Tuple
|
13
|
+
import logging
|
14
|
+
|
15
|
+
logger = logging.getLogger(__name__)
|
16
|
+
|
17
|
+
class TokenCounter:
|
18
|
+
"""Token计数器"""
|
19
|
+
|
20
|
+
def __init__(self, model_name: str = "gpt-4"):
|
21
|
+
"""初始化token计数器"""
|
22
|
+
try:
|
23
|
+
self.encoding = tiktoken.encoding_for_model(model_name)
|
24
|
+
except KeyError:
|
25
|
+
# 如果模型不支持,使用默认编码
|
26
|
+
self.encoding = tiktoken.get_encoding("cl100k_base")
|
27
|
+
|
28
|
+
def count_tokens(self, text: str) -> int:
|
29
|
+
"""计算文本的token数量"""
|
30
|
+
return len(self.encoding.encode(text))
|
31
|
+
|
32
|
+
def count_messages_tokens(self, messages: List[Dict[str, str]]) -> int:
|
33
|
+
"""计算消息列表的总token数量"""
|
34
|
+
total_tokens = 0
|
35
|
+
for message in messages:
|
36
|
+
# 每个消息有固定的开销(role等)
|
37
|
+
total_tokens += 4 # 每个消息的基本开销
|
38
|
+
for key, value in message.items():
|
39
|
+
total_tokens += self.count_tokens(str(value))
|
40
|
+
total_tokens += 2 # 对话的基本开销
|
41
|
+
return total_tokens
|
42
|
+
|
43
|
+
def truncate_text(self, text: str, max_tokens: int) -> str:
|
44
|
+
"""截断文本以适应token限制"""
|
45
|
+
tokens = self.encoding.encode(text)
|
46
|
+
if len(tokens) <= max_tokens:
|
47
|
+
return text
|
48
|
+
|
49
|
+
truncated_tokens = tokens[:max_tokens]
|
50
|
+
return self.encoding.decode(truncated_tokens)
|
51
|
+
|
52
|
+
def split_text_by_tokens(self, text: str, chunk_size: int, overlap: int = 0) -> List[str]:
|
53
|
+
"""按token数量分割文本"""
|
54
|
+
tokens = self.encoding.encode(text)
|
55
|
+
chunks = []
|
56
|
+
|
57
|
+
start = 0
|
58
|
+
while start < len(tokens):
|
59
|
+
end = min(start + chunk_size, len(tokens))
|
60
|
+
chunk_tokens = tokens[start:end]
|
61
|
+
chunks.append(self.encoding.decode(chunk_tokens))
|
62
|
+
|
63
|
+
if end >= len(tokens):
|
64
|
+
break
|
65
|
+
|
66
|
+
start = end - overlap
|
67
|
+
|
68
|
+
return chunks
|
69
|
+
|
70
|
+
class TextProcessor:
|
71
|
+
"""文本处理工具"""
|
72
|
+
|
73
|
+
@staticmethod
|
74
|
+
def clean_text(text: str) -> str:
|
75
|
+
"""清理文本,移除多余的空白字符"""
|
76
|
+
# 移除多余的空白字符
|
77
|
+
text = re.sub(r'\s+', ' ', text)
|
78
|
+
# 移除首尾空白
|
79
|
+
text = text.strip()
|
80
|
+
return text
|
81
|
+
|
82
|
+
@staticmethod
|
83
|
+
def extract_code_blocks(text: str) -> List[Dict[str, str]]:
|
84
|
+
"""从文本中提取代码块"""
|
85
|
+
code_pattern = r'```(\w+)?\n(.*?)\n```'
|
86
|
+
matches = re.findall(code_pattern, text, re.DOTALL)
|
87
|
+
|
88
|
+
code_blocks = []
|
89
|
+
for language, code in matches:
|
90
|
+
code_blocks.append({
|
91
|
+
"language": language or "text",
|
92
|
+
"code": code.strip()
|
93
|
+
})
|
94
|
+
|
95
|
+
return code_blocks
|
96
|
+
|
97
|
+
@staticmethod
|
98
|
+
def extract_json_from_text(text: str) -> Optional[Dict[str, Any]]:
|
99
|
+
"""从文本中提取JSON"""
|
100
|
+
# 尝试直接解析
|
101
|
+
try:
|
102
|
+
return json.loads(text)
|
103
|
+
except json.JSONDecodeError:
|
104
|
+
pass
|
105
|
+
|
106
|
+
# 尝试提取JSON代码块
|
107
|
+
json_pattern = r'```json\n(.*?)\n```'
|
108
|
+
match = re.search(json_pattern, text, re.DOTALL)
|
109
|
+
if match:
|
110
|
+
try:
|
111
|
+
return json.loads(match.group(1))
|
112
|
+
except json.JSONDecodeError:
|
113
|
+
pass
|
114
|
+
|
115
|
+
# 尝试提取花括号内容
|
116
|
+
brace_pattern = r'\{.*\}'
|
117
|
+
match = re.search(brace_pattern, text, re.DOTALL)
|
118
|
+
if match:
|
119
|
+
try:
|
120
|
+
return json.loads(match.group(0))
|
121
|
+
except json.JSONDecodeError:
|
122
|
+
pass
|
123
|
+
|
124
|
+
return None
|
125
|
+
|
126
|
+
@staticmethod
|
127
|
+
def split_into_sentences(text: str) -> List[str]:
|
128
|
+
"""将文本分割为句子"""
|
129
|
+
# 简单的句子分割,基于句号、问号、感叹号
|
130
|
+
sentences = re.split(r'[.!?]+', text)
|
131
|
+
return [s.strip() for s in sentences if s.strip()]
|
132
|
+
|
133
|
+
@staticmethod
|
134
|
+
def extract_entities(text: str, entity_types: List[str] = None) -> Dict[str, List[str]]:
|
135
|
+
"""提取文本中的实体(简单版本)"""
|
136
|
+
entities = {}
|
137
|
+
|
138
|
+
if not entity_types:
|
139
|
+
entity_types = ["email", "url", "phone", "date"]
|
140
|
+
|
141
|
+
patterns = {
|
142
|
+
"email": r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b',
|
143
|
+
"url": r'http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\\(\\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+',
|
144
|
+
"phone": r'\b\d{3}[-.]?\d{3}[-.]?\d{4}\b',
|
145
|
+
"date": r'\b\d{1,2}[/-]\d{1,2}[/-]\d{2,4}\b'
|
146
|
+
}
|
147
|
+
|
148
|
+
for entity_type in entity_types:
|
149
|
+
if entity_type in patterns:
|
150
|
+
matches = re.findall(patterns[entity_type], text)
|
151
|
+
entities[entity_type] = matches
|
152
|
+
|
153
|
+
return entities
|
154
|
+
|
155
|
+
class ResponseParser:
|
156
|
+
"""响应解析器"""
|
157
|
+
|
158
|
+
@staticmethod
|
159
|
+
def parse_structured_response(response: str, expected_format: str = "json") -> Any:
|
160
|
+
"""解析结构化响应"""
|
161
|
+
if expected_format == "json":
|
162
|
+
return TextProcessor.extract_json_from_text(response)
|
163
|
+
elif expected_format == "code":
|
164
|
+
return TextProcessor.extract_code_blocks(response)
|
165
|
+
else:
|
166
|
+
return response
|
167
|
+
|
168
|
+
@staticmethod
|
169
|
+
def parse_classification_response(response: str, categories: List[str]) -> Tuple[str, float]:
|
170
|
+
"""解析分类响应,返回类别和置信度"""
|
171
|
+
response_lower = response.lower()
|
172
|
+
|
173
|
+
# 查找匹配的类别
|
174
|
+
for category in categories:
|
175
|
+
if category.lower() in response_lower:
|
176
|
+
# 尝试提取置信度
|
177
|
+
confidence_pattern = r'(\d+(?:\.\d+)?)%|confidence[:\s]*(\d+(?:\.\d+)?)'
|
178
|
+
match = re.search(confidence_pattern, response_lower)
|
179
|
+
confidence = float(match.group(1) or match.group(2)) / 100 if match else 0.8
|
180
|
+
return category, confidence
|
181
|
+
|
182
|
+
# 如果没有找到匹配的类别,返回最可能的一个
|
183
|
+
return categories[0] if categories else "unknown", 0.5
|
184
|
+
|
185
|
+
@staticmethod
|
186
|
+
def parse_sentiment_response(response: str) -> Tuple[str, float]:
|
187
|
+
"""解析情感分析响应"""
|
188
|
+
response_lower = response.lower()
|
189
|
+
|
190
|
+
# 定义情感关键词
|
191
|
+
sentiments = {
|
192
|
+
"positive": ["positive", "good", "great", "excellent", "happy", "pleased"],
|
193
|
+
"negative": ["negative", "bad", "terrible", "awful", "sad", "disappointed"],
|
194
|
+
"neutral": ["neutral", "okay", "average", "mixed", "unclear"]
|
195
|
+
}
|
196
|
+
|
197
|
+
# 查找匹配的情感
|
198
|
+
for sentiment, keywords in sentiments.items():
|
199
|
+
for keyword in keywords:
|
200
|
+
if keyword in response_lower:
|
201
|
+
# 尝试提取置信度
|
202
|
+
confidence_pattern = r'(\d+(?:\.\d+)?)%|confidence[:\s]*(\d+(?:\.\d+)?)'
|
203
|
+
match = re.search(confidence_pattern, response_lower)
|
204
|
+
confidence = float(match.group(1) or match.group(2)) / 100 if match else 0.7
|
205
|
+
return sentiment, confidence
|
206
|
+
|
207
|
+
return "neutral", 0.5
|
208
|
+
|
209
|
+
class LLMMetrics:
|
210
|
+
"""
|
211
|
+
LLM性能指标计算工具
|
212
|
+
|
213
|
+
注意:计费和使用跟踪功能已经统一到BaseService中的_track_usage()方法。
|
214
|
+
LLM服务应该使用BaseLLMService中的_track_llm_usage()方法来跟踪使用情况。
|
215
|
+
"""
|
216
|
+
|
217
|
+
@staticmethod
|
218
|
+
def calculate_latency_metrics(start_time: float, end_time: float, token_count: int) -> Dict[str, float]:
|
219
|
+
"""计算延迟指标"""
|
220
|
+
total_time = end_time - start_time
|
221
|
+
tokens_per_second = token_count / total_time if total_time > 0 else 0
|
222
|
+
|
223
|
+
return {
|
224
|
+
"total_time": total_time,
|
225
|
+
"tokens_per_second": tokens_per_second,
|
226
|
+
"ms_per_token": (total_time * 1000) / token_count if token_count > 0 else 0
|
227
|
+
}
|
228
|
+
|
229
|
+
|
230
|
+
def validate_model_response(response: Any, expected_type: type = str) -> bool:
|
231
|
+
"""验证模型响应是否符合预期类型"""
|
232
|
+
return isinstance(response, expected_type)
|
233
|
+
|
234
|
+
def format_chat_history(messages: List[Dict[str, str]], max_history: int = 10) -> List[Dict[str, str]]:
|
235
|
+
"""格式化聊天历史,保留最近的消息"""
|
236
|
+
if len(messages) <= max_history:
|
237
|
+
return messages
|
238
|
+
|
239
|
+
# 保留系统消息和最近的消息
|
240
|
+
system_messages = [msg for msg in messages if msg.get("role") == "system"]
|
241
|
+
other_messages = [msg for msg in messages if msg.get("role") != "system"]
|
242
|
+
|
243
|
+
recent_messages = other_messages[-max_history+len(system_messages):]
|
244
|
+
return system_messages + recent_messages
|
245
|
+
|
246
|
+
def extract_function_calls(response: str) -> List[Dict[str, Any]]:
|
247
|
+
"""从响应中提取函数调用"""
|
248
|
+
function_calls = []
|
249
|
+
|
250
|
+
# 查找JSON格式的函数调用
|
251
|
+
json_pattern = r'\{[^{}]*"function_name"[^{}]*\}'
|
252
|
+
matches = re.findall(json_pattern, response)
|
253
|
+
|
254
|
+
for match in matches:
|
255
|
+
try:
|
256
|
+
call = json.loads(match)
|
257
|
+
if "function_name" in call:
|
258
|
+
function_calls.append(call)
|
259
|
+
except json.JSONDecodeError:
|
260
|
+
continue
|
261
|
+
|
262
|
+
return function_calls
|
263
|
+
|
264
|
+
def merge_streaming_tokens(tokens: List[str]) -> str:
|
265
|
+
"""合并流式token为完整文本"""
|
266
|
+
return ''.join(tokens)
|
267
|
+
|
268
|
+
def detect_language(text: str) -> str:
|
269
|
+
"""检测文本语言(简单版本)"""
|
270
|
+
# 简单的语言检测,基于字符模式
|
271
|
+
if re.search(r'[\u4e00-\u9fff]', text):
|
272
|
+
return "zh"
|
273
|
+
elif re.search(r'[а-яё]', text, re.IGNORECASE):
|
274
|
+
return "ru"
|
275
|
+
elif re.search(r'[ひらがなカタカナ]', text):
|
276
|
+
return "ja"
|
277
|
+
elif re.search(r'[가-힣]', text):
|
278
|
+
return "ko"
|
279
|
+
else:
|
280
|
+
return "en"
|