volcengine-python-sdk-aicc 0.0.41__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 (60) hide show
  1. bytedance/__init__.py +4 -0
  2. bytedance/jeddak_secure_channel/__init__.py +28 -0
  3. bytedance/jeddak_secure_channel/client.py +305 -0
  4. bytedance/jeddak_secure_channel/config.py +171 -0
  5. bytedance/jeddak_secure_channel/consts.py +5 -0
  6. bytedance/jeddak_secure_channel/crypto/__init__.py +14 -0
  7. bytedance/jeddak_secure_channel/crypto/aes.py +174 -0
  8. bytedance/jeddak_secure_channel/crypto/envelope.py +395 -0
  9. bytedance/jeddak_secure_channel/crypto/rsa.py +164 -0
  10. bytedance/jeddak_secure_channel/error.py +131 -0
  11. bytedance/jeddak_secure_channel/log/__init__.py +23 -0
  12. bytedance/jeddak_secure_channel/log/config.py +31 -0
  13. bytedance/jeddak_secure_channel/log/logger.py +88 -0
  14. bytedance/jeddak_secure_channel/logger_factory.py +134 -0
  15. bytedance/jeddak_secure_channel/ra.py +772 -0
  16. bytedance/jeddak_secure_channel/server.py +745 -0
  17. bytedance/jeddak_secure_channel/service/__init__.py +16 -0
  18. bytedance/jeddak_secure_channel/service/eps.py +133 -0
  19. bytedance/jeddak_secure_channel/service/eps_models.py +47 -0
  20. bytedance/jeddak_secure_channel/service/ras.py +88 -0
  21. bytedance/jeddak_secure_channel/service/tks.py +51 -0
  22. bytedance/jeddak_secure_channel/service/tks_models.py +334 -0
  23. bytedance/jeddak_secure_channel/utils.py +469 -0
  24. bytedance/jeddak_secure_model/model_automation_tool.py +181 -0
  25. bytedance/jeddak_secure_model/model_encryption.py +416 -0
  26. bytedance/jeddak_secure_model/model_hub_admin.py +116 -0
  27. bytedance/jeddak_secure_model/pcc_utils.py +279 -0
  28. bytedance/jeddak_secure_model/rds_utils.py +63 -0
  29. bytedance/tks/attester.py +41 -0
  30. bytedance/tks/client.py +629 -0
  31. bytedance/tks/consts.py +122 -0
  32. bytedance/tks/crypto/aes.py +47 -0
  33. bytedance/tks/crypto/ec.py +39 -0
  34. bytedance/tks/exceptions.py +22 -0
  35. bytedance/volcengine_aicc_sdk/__init__.py +14 -0
  36. bytedance/volcengine_aicc_sdk/aicc_client.py +277 -0
  37. bytedance/volcengine_aicc_sdk/aicc_inference.py +64 -0
  38. bytedance/volcengine_aicc_sdk/ark_inference_client.py +343 -0
  39. bytedance/volcengine_aicc_sdk/asr_inference_client.py +158 -0
  40. bytedance/volcengine_aicc_sdk/asr_websocket_client.py +705 -0
  41. bytedance/volcengine_aicc_sdk/auc_inference_client.py +305 -0
  42. bytedance/volcengine_aicc_sdk/common/__init__.py +0 -0
  43. bytedance/volcengine_aicc_sdk/common/aes.py +136 -0
  44. bytedance/volcengine_aicc_sdk/common/constant.py +4 -0
  45. bytedance/volcengine_aicc_sdk/common/error.py +21 -0
  46. bytedance/volcengine_aicc_sdk/common/log/__init__.py +24 -0
  47. bytedance/volcengine_aicc_sdk/common/log/config.py +31 -0
  48. bytedance/volcengine_aicc_sdk/common/log/logger.py +88 -0
  49. bytedance/volcengine_aicc_sdk/common/tos_utils.py +341 -0
  50. bytedance/volcengine_aicc_sdk/common/utils.py +45 -0
  51. bytedance/volcengine_aicc_sdk/cu_inference_client.py +292 -0
  52. bytedance/volcengine_aicc_sdk/deepseek_inference_client.py +501 -0
  53. bytedance/volcengine_aicc_sdk/secure_http_client.py +406 -0
  54. bytedance/volcengine_aicc_sdk/seedance_inference_client.py +500 -0
  55. bytedance/volcengine_aicc_sdk/seedream_inference_client.py +213 -0
  56. volcengine_python_sdk_aicc-0.0.41.dist-info/METADATA +79 -0
  57. volcengine_python_sdk_aicc-0.0.41.dist-info/RECORD +60 -0
  58. volcengine_python_sdk_aicc-0.0.41.dist-info/WHEEL +5 -0
  59. volcengine_python_sdk_aicc-0.0.41.dist-info/licenses/LICENSE +202 -0
  60. volcengine_python_sdk_aicc-0.0.41.dist-info/top_level.txt +1 -0
@@ -0,0 +1,341 @@
1
+ __all__ = ["TOSUtils"]
2
+
3
+ from urllib import request
4
+ from .aes import decrypt_file
5
+ import os
6
+ import wget
7
+ import time
8
+ import httpx
9
+ from .error import AICCException
10
+ from .log import logger
11
+
12
+
13
+ class TOSUtils:
14
+ def __init__(self, workdir: str = "./tmp"):
15
+ self.workdir = workdir
16
+ if not os.path.exists(self.workdir):
17
+ os.makedirs(self.workdir, exist_ok=True)
18
+
19
+ self.default_large_file_threshold = 100 * 1024 * 1024
20
+ self.default_download_timeout = 300.0
21
+ self.default_chunk_size = 1024 * 1024
22
+
23
+ self.max_retries = 3
24
+ self.retry_delay = 2 # seconds
25
+
26
+ def _download_small_file_wget(self, tos_url: str, local_file_path: str) -> bool:
27
+ """
28
+ Download small files using Python wget module
29
+
30
+ Args:
31
+ tos_url: TOS文件路径
32
+ local_file_path: 本地文件路径
33
+
34
+ Returns:
35
+ bool: 是否下载成功
36
+ """
37
+ if not tos_url or not local_file_path:
38
+ raise AICCException("TOS URL and local file path are required")
39
+
40
+ if os.path.exists(local_file_path):
41
+ raise AICCException("Local file already exists, please delete it first")
42
+
43
+ for attempt in range(1, self.max_retries + 1):
44
+ try:
45
+ logger.info(f"Download attempt {attempt}/{self.max_retries}")
46
+
47
+ # Download file using wget
48
+ wget.download(tos_url, local_file_path)
49
+
50
+ # Verify file was downloaded
51
+ if not os.path.exists(local_file_path):
52
+ raise AICCException("File download failed, try again...")
53
+
54
+ file_size = os.path.getsize(local_file_path)
55
+ if file_size == 0:
56
+ raise AICCException("Downloaded file is empty, try again...")
57
+
58
+ logger.info(f"Download successful, file size: {file_size} bytes")
59
+ return True
60
+
61
+ except Exception as e:
62
+ logger.warning(f"Download attempt {attempt} failed: {str(e)}, try again...")
63
+ if os.path.exists(local_file_path):
64
+ try:
65
+ os.remove(local_file_path)
66
+ except:
67
+ pass
68
+
69
+ if attempt == self.max_retries:
70
+ raise AICCException(
71
+ f"Failed to download encrypted file after {self.max_retries} attempts\n"
72
+ f"Last error: {str(e)}\n"
73
+ "Please check:\n"
74
+ " 1. TOS URL is accessible\n"
75
+ " 2. Network connection is stable\n"
76
+ " 3. File exists on TOS"
77
+ )
78
+
79
+ logger.info(f"Waiting {self.retry_delay} seconds before retry...")
80
+ time.sleep(self.retry_delay)
81
+ self.retry_delay *= 2
82
+
83
+ raise AICCException("Download failed unexpectedly")
84
+
85
+ def _download_small_file_httpx(self, tos_url: str, local_file_path: str) -> bool:
86
+ """
87
+ Download small files using httpx (fallback when wget is not available)
88
+
89
+ Args:
90
+ tos_url: TOS文件路径
91
+ local_file_path: Path to save the downloaded file
92
+ Returns:
93
+ bool: 是否下载成功
94
+ """
95
+ if not tos_url or not local_file_path:
96
+ raise AICCException("TOS URL and local file path are required")
97
+
98
+ if os.path.exists(local_file_path):
99
+ raise AICCException("Local file already exists, please delete it first")
100
+ for attempt in range(1, self.max_retries + 1):
101
+ try:
102
+ logger.info(f"Download attempt {attempt}/{self.max_retries}")
103
+
104
+ # Download file using httpx
105
+ with httpx.stream('GET', tos_url, timeout=self.default_download_timeout, follow_redirects=True) as response:
106
+ response.raise_for_status()
107
+
108
+ with open(local_file_path, 'wb') as f:
109
+ for chunk in response.iter_bytes(chunk_size=self.default_chunk_size):
110
+ f.write(chunk)
111
+
112
+ # Verify file was downloaded
113
+ if not os.path.exists(local_file_path):
114
+ raise AICCException("File download failed, try again...")
115
+
116
+ file_size = os.path.getsize(local_file_path)
117
+ if file_size == 0:
118
+ raise AICCException("Downloaded file is empty")
119
+
120
+ logger.info(f"Download successful, file size: {file_size} bytes")
121
+ return True
122
+
123
+ except Exception as e:
124
+ logger.warning(f"Download attempt {attempt} failed: {str(e)}")
125
+
126
+ # Clean up failed download
127
+ if os.path.exists(local_file_path):
128
+ try:
129
+ os.remove(local_file_path)
130
+ except:
131
+ pass
132
+
133
+ # If this was the last attempt, raise error
134
+ if attempt == self.max_retries:
135
+ raise AICCException(
136
+ f"Failed to download encrypted file after {self.max_retries} attempts\n"
137
+ f"Last error: {str(e)}\n"
138
+ "Please check:\n"
139
+ " 1. TOS URL is accessible\n"
140
+ " 2. Network connection is stable\n"
141
+ " 3. File exists on TOS"
142
+ )
143
+
144
+ # Wait before retry
145
+ logger.info(f"Waiting {self.retry_delay} seconds before retry...")
146
+ time.sleep(self.retry_delay)
147
+ self.retry_delay *= 2
148
+
149
+ # Should never reach here, but satisfy type checker
150
+ raise AICCException("Download failed unexpectedly")
151
+
152
+ def _download_large_file_streaming(self, tos_url: str, local_file_path: str, expected_size: int) -> bool:
153
+ """
154
+ Download large files using httpx streaming
155
+
156
+ Args:
157
+ tos_url: TOS文件路径
158
+ local_file_path: Path to save the downloaded file
159
+ expected_size: Expected file size in bytes
160
+
161
+ Returns:
162
+ Path to the downloaded file
163
+ """
164
+ if not tos_url or not local_file_path or expected_size <= 0:
165
+ raise AICCException("TOS URL, local file path, and expected file size are required and must be greater than 0")
166
+
167
+ if os.path.exists(local_file_path):
168
+ raise AICCException("Local file already exists, please delete it first")
169
+
170
+ for attempt in range(1, self.max_retries + 1):
171
+ try:
172
+ logger.info(f"Streaming download attempt {attempt}/{self.max_retries}")
173
+
174
+ # Streaming download with timeout
175
+ with httpx.stream('GET', tos_url, timeout=self.default_download_timeout, follow_redirects=True) as response:
176
+ response.raise_for_status()
177
+
178
+ downloaded_bytes = 0
179
+
180
+ with open(local_file_path, 'wb') as f:
181
+ for chunk in response.iter_bytes(chunk_size=self.default_chunk_size):
182
+ f.write(chunk)
183
+ downloaded_bytes += len(chunk)
184
+
185
+ # Log progress every 10MB
186
+ if downloaded_bytes % (10 * 1024 * 1024) < self.default_chunk_size:
187
+ progress = (downloaded_bytes / expected_size) * 100 if expected_size > 0 else 0
188
+ logger.info(f"Download progress: {progress:.1f}% ({downloaded_bytes / (1024*1024):.1f} MB / {expected_size / (1024*1024):.1f} MB)")
189
+
190
+ # Verify file was downloaded
191
+ if not os.path.exists(local_file_path):
192
+ raise AICCException("File download failed, try again...")
193
+
194
+ file_size = os.path.getsize(local_file_path)
195
+ if file_size == 0:
196
+ raise AICCException("Downloaded file is empty")
197
+
198
+ logger.info(f"Download successful, file size: {file_size} bytes")
199
+ return True
200
+
201
+ except Exception as e:
202
+ logger.warning(f"Streaming download attempt {attempt} failed: {str(e)}")
203
+
204
+ # Clean up failed download
205
+ if os.path.exists(local_file_path):
206
+ try:
207
+ os.remove(local_file_path)
208
+ except:
209
+ pass
210
+
211
+ # If this was the last attempt, raise error
212
+ if attempt == self.max_retries:
213
+ raise AICCException(
214
+ f"Failed to download large file after {self.max_retries} attempts\n"
215
+ f"Last error: {str(e)}\n"
216
+ "Please check:\n"
217
+ " 1. TOS URL is accessible\n"
218
+ " 2. Network connection is stable\n"
219
+ " 3. File exists on TOS\n"
220
+ " 4. Timeout is sufficient for file size"
221
+ )
222
+
223
+ # Wait before retry
224
+ logger.info(f"Waiting {self.retry_delay} seconds before retry...")
225
+ time.sleep(self.retry_delay)
226
+ self.retry_delay *= 2
227
+
228
+ # Should never reach here, but satisfy type checker
229
+ raise AICCException("Download failed unexpectedly")
230
+
231
+ def _extract_filename_from_url(self, url: str) -> str:
232
+ """
233
+ Extract filename from TOS URL
234
+
235
+ Args:
236
+ url: TOS URL
237
+
238
+ Returns:
239
+ Filename
240
+ """
241
+ # Remove query parameters
242
+ path = url.split('?')[0]
243
+ filename = os.path.basename(path)
244
+ return filename
245
+
246
+ def download_file(self, tos_url: str, local_file_path: str, encrypted_key: bytes) -> bool:
247
+ """
248
+ 下载文件到本地路径, encrypted_key有值时需要先解密文件, 再保存到本地路径
249
+ :return: 是否成功
250
+ :param tos_url: TOS文件路径
251
+ :param local_file_path: 本地文件路径
252
+ :param encrypted_key: 加密密钥, 为空时表示文件未加密
253
+ """
254
+ if not tos_url or not local_file_path:
255
+ raise AICCException("TOS URL and local file path are required")
256
+
257
+ if os.path.exists(local_file_path):
258
+ raise AICCException("Local file already exists, please delete it first")
259
+
260
+ if not tos_url.startswith('http'):
261
+ raise AICCException(f"Invalid TOS URL: {tos_url}\n URL must start with http:// or https://")
262
+
263
+ try:
264
+ if not self._download_small_file_wget(tos_url, local_file_path):
265
+ raise AICCException("download file from TOS failed failed, please check the URL")
266
+
267
+ if encrypted_key:
268
+ tmp_file_path = local_file_path + ".tmp"
269
+ decrypt_file(encrypted_key, local_file_path, tmp_file_path, mode="b")
270
+ os.remove(local_file_path)
271
+ os.rename(tmp_file_path, local_file_path)
272
+
273
+ return True
274
+
275
+ except AICCException as e:
276
+ logger.error(f"Error extracting filename from TOS URL: {e}")
277
+ raise e
278
+ except Exception as e:
279
+ logger.error(f"Error extracting filename from TOS URL: {str(e)}")
280
+ raise AICCException("download filename from TOS file failed, please check the URL")
281
+
282
+ def download_files(self, tos_url: str, local_file_path: str, encrypted_key: bytes) -> bool:
283
+ """
284
+ 下载多个文件并合并到,local_file_path, encrypted_key有值时需要先解密文件, 再保存
285
+ :return: 是否成功
286
+ :param tos_url: TOS文件路径
287
+ :param local_file_path: 本地文件路径
288
+ :param encrypted_key: AES加密密钥, 为空时表示文件未加密
289
+ """
290
+ if not tos_url or not local_file_path:
291
+ raise AICCException("TOS URL and local file path are required")
292
+
293
+ if os.path.exists(local_file_path):
294
+ raise AICCException("Local file already exists, please delete it first")
295
+
296
+ if not tos_url.startswith('http'):
297
+ raise AICCException(f"Invalid TOS URL: {tos_url}\n URL must start with http:// or https://")
298
+
299
+ try:
300
+ meta_file_npath = tos_url + "/meta.json"
301
+ resp = request.urlopen(meta_file_npath)
302
+ resp.raise_for_status()
303
+ meta_json = resp.json()
304
+ logger.info(f"Meta file content: {meta_json}")
305
+
306
+ file_list = meta_json.get("files", [])
307
+ if not file_list:
308
+ raise AICCException("No files found in the TOS directory")
309
+
310
+ # sort file_list by filename
311
+ file_list.sort()
312
+
313
+ video_files = []
314
+ for file_name in file_list:
315
+ tos_file_path = tos_url + "/" + file_name
316
+ tmp_file_path = local_file_path + "." + file_name
317
+ if not self._download_small_file_wget(tos_file_path, tmp_file_path):
318
+ raise AICCException(f"download file from TOS failed failed, please check the URL{tos_file_path}")
319
+
320
+ if encrypted_key:
321
+ decrypt_file_path = tmp_file_path + ".dec"
322
+ decrypt_file(encrypted_key, tmp_file_path, decrypt_file_path, mode="b")
323
+ os.remove(tmp_file_path)
324
+ os.rename(decrypt_file_path, tmp_file_path)
325
+
326
+ video_files.append(tmp_file_path)
327
+
328
+ # 将ret_video_file中的文件合并输出到ret_video_file_path
329
+ with open(local_file_path, 'wb') as f:
330
+ for video_file in video_files:
331
+ with open(video_file, 'rb') as video:
332
+ f.write(video.read())
333
+
334
+ return True
335
+
336
+ except AICCException as e:
337
+ logger.error(f"Error extracting filename from TOS URL: {e}")
338
+ raise e
339
+ except Exception as e:
340
+ logger.error(f"Error extracting filename from TOS URL: {str(e)}")
341
+ raise AICCException("download filename from TOS file failed, please check the URL")
@@ -0,0 +1,45 @@
1
+ __all__ = [
2
+ "format_query",
3
+ "file_to_base64",
4
+ "base64_to_file"
5
+ ]
6
+
7
+ import base64
8
+ import json
9
+ import urllib.parse
10
+ from typing import Dict
11
+
12
+
13
+ def file_to_base64(file_path):
14
+ with open(file_path, 'rb') as file:
15
+ file_data = file.read() # 读取文件内容
16
+ base64_data = base64.b64encode(file_data).decode('utf-8') # Base64 编码
17
+ return base64_data
18
+
19
+
20
+ # 辅助函数:将Base64数据写入文件
21
+ def base64_to_file(base64_data, output_file_path):
22
+ """将Base64编码的数据写入文件"""
23
+ if not base64_data:
24
+ print("没有Base64数据可写入文件。")
25
+ return False
26
+ try:
27
+ # 将Base64字符串解码为二进制数据
28
+ binary_data = base64.b64decode(base64_data)
29
+ # 写入文件
30
+ with open(output_file_path, 'wb') as f:
31
+ f.write(binary_data)
32
+ print(f"成功将Base64数据写入文件:{output_file_path}")
33
+ return True
34
+ except Exception as e:
35
+ print(f"写入文件失败:{e}")
36
+ return False
37
+
38
+
39
+ def format_query(params: Dict[str, str]) -> str:
40
+ return "&".join(
41
+ urllib.parse.quote(key, safe="-_.~")
42
+ + "="
43
+ + urllib.parse.quote(value, safe="-_.~")
44
+ for key, value in params.items()
45
+ )
@@ -0,0 +1,292 @@
1
+ """
2
+ CU开源模型机密推理客户端
3
+ """
4
+ __all__ = ["CUClient"]
5
+
6
+ import base64
7
+ import json
8
+ import time
9
+ import requests
10
+ import bytedance.jeddak_secure_channel as jsc
11
+ from typing import IO, Union
12
+ from .aicc_inference import AICCInference
13
+ from .common.constant import Constants
14
+ from .common.error import AICCException
15
+ from .common.log import logger
16
+
17
+
18
+ class CUClient(AICCInference):
19
+ """
20
+ AICC机密推理访问客户端.
21
+ """
22
+
23
+ def __init__(self,
24
+ base_url: str = None,
25
+ aicc_service_name: str = None,
26
+ policy_id: str = None,
27
+ is_openai: bool = True,
28
+ is_secure: bool = True,
29
+ ak: str = None,
30
+ sk: str = None,
31
+ region: str = "cn-beijing",
32
+ **kwargs):
33
+
34
+ if not base_url:
35
+ raise AICCException("base_url cannot be empty")
36
+
37
+ super().__init__(base_url, is_openai, is_secure, ak, sk, region)
38
+
39
+ self.aicc_service_name = aicc_service_name
40
+ self.policy_id = policy_id
41
+ self.kwargs = kwargs
42
+
43
+ self.jsc_client = None
44
+ self.max_retries = 3
45
+
46
+ if is_secure:
47
+ jsc_config = dict()
48
+ jsc_config["ra_service_name"] = aicc_service_name
49
+ jsc_config["ra_policy_id"] = policy_id
50
+ jsc_config["bytedance_top_info"] = dict()
51
+ jsc_config["bytedance_top_info"]["ak"] = ak
52
+ jsc_config["bytedance_top_info"]["sk"] = sk
53
+ jsc_config["bytedance_top_info"]["region"] = region
54
+ jsc_config["bytedance_top_info"]["url"] = self.kwargs.get("top_url", "open.volcengineapi.com")
55
+ jsc_config["bytedance_top_info"]["service"] = self.kwargs.get("top_service", "pcc")
56
+ jsc_config["log_config"] = self.kwargs.get("log_config", Constants.LOG_CONFIG)
57
+
58
+ try:
59
+ secure_channel_config = jsc.ClientConfig.from_dict(jsc_config)
60
+ self.jsc_client = jsc.Client(secure_channel_config)
61
+ except Exception as e:
62
+ logger.error(f"Error init jsc: {e}")
63
+ raise AICCException("create cu client failed")
64
+
65
+ def completions(self, prompt: str, output_stream: IO = None) -> Union[str, None]:
66
+ """
67
+ 生成聊天补全
68
+ :param prompt:
69
+ :param output_stream:
70
+ :return:
71
+ """
72
+ if not prompt:
73
+ raise AICCException("vllm completions Prompt cannot be empty")
74
+
75
+ try:
76
+ if self.is_secure:
77
+ url = f"{self.base_url}/v2/completions"
78
+ else:
79
+ url = f"{self.base_url}/v1/completions"
80
+
81
+ headers = {"Content-Type": "application/json"}
82
+ payload = {
83
+ "prompt": prompt,
84
+ "max_tokens": int(self.kwargs.get("max_tokens", 100)),
85
+ "temperature": float(self.kwargs.get("temperature", 1.0)),
86
+ "stream": True if output_stream else False
87
+ }
88
+
89
+ encrypt_key = None
90
+ if self.is_secure:
91
+ # 加密数据并发送
92
+ try:
93
+ encrypted_prompt, encrypt_key = self.jsc_client.encrypt_with_response(json.dumps(payload["prompt"]))
94
+ payload["prompt"] = encrypted_prompt # 敏感字段
95
+ except Exception as e:
96
+ logger.error(f"Error during encrypt: {e}")
97
+ raise AICCException("error during encrypt")
98
+
99
+ if output_stream:
100
+ try:
101
+ # 启用流式响应
102
+ with requests.post(url, json=payload, headers=headers, stream=True) as response:
103
+ response.raise_for_status()
104
+ for chunk in response.iter_lines():
105
+ if chunk:
106
+ if self.is_secure and encrypt_key:
107
+ data = encrypt_key.decrypt(base64.b64decode(chunk))
108
+ output_stream.write(f"{data}")
109
+ else:
110
+ output_stream.write(f"{chunk}")
111
+ except Exception as e:
112
+ logger.error(f"call AICC VLLM service completions stream error:{e}")
113
+ raise AICCException("vllm completions_stream error")
114
+ else:
115
+ for attempt in range(self.max_retries):
116
+ try:
117
+ response = requests.post(url, json=payload, headers=headers)
118
+ response.raise_for_status()
119
+ data = response.json()
120
+
121
+ if self.is_secure and encrypt_key:
122
+ for elm in data['choices']:
123
+ elm['text'] = encrypt_key.decrypt(elm['text']).decode()
124
+
125
+ return json.dumps(data)
126
+ except Exception as e:
127
+ if attempt == self.max_retries - 1:
128
+ logger.error(f"call AICC VLLM service completions error:{e}")
129
+ raise AICCException("call vllm completions error")
130
+ else:
131
+ logger.error(f"call AICC VLLM service completions error:{e}, try again....")
132
+ time.sleep(1)
133
+ except AICCException as e:
134
+ raise e
135
+ except Exception as e:
136
+ logger.error(f"completions error,Exception={e}")
137
+ raise AICCException("completions error")
138
+
139
+ def completions_stream(self, prompt: str, output_stream: IO) -> Union[str, None]:
140
+ return self.completions(prompt, output_stream)
141
+
142
+ def chat_completions(self, messages: list, output_stream: IO = None) -> Union[str, None]:
143
+ """
144
+ 生成聊天补全
145
+ :param messages:
146
+ :param output_stream:
147
+ :return:
148
+ """
149
+ try:
150
+ if not messages:
151
+ raise AICCException("vllm chat_completions Messages cannot be empty")
152
+
153
+ if not self.base_url:
154
+ raise AICCException("vllm chat_completions base_url cannot be empty")
155
+
156
+ if self.is_secure:
157
+ url = f"{self.base_url}/v2/chat/completions"
158
+ else:
159
+ url = f"{self.base_url}/v1/chat/completions"
160
+
161
+ headers = {"Content-Type": "application/json"}
162
+ payload = {
163
+ "messages": messages,
164
+ "max_tokens": int(self.kwargs.get("max_tokens", 100)),
165
+ "temperature": float(self.kwargs.get("temperature", 1.0)),
166
+ "stream": True if output_stream else False # 启用流式响应
167
+ }
168
+
169
+ encrypt_key = None
170
+ if self.is_secure:
171
+ # 加密数据并发送
172
+ try:
173
+ encrypted_messages, encrypt_key = \
174
+ self.jsc_client.encrypt_with_response(json.dumps(payload["messages"]))
175
+ payload["messages"] = encrypted_messages # 敏感字段
176
+ except Exception as e:
177
+ logger.error(f"Error during encrypt: {e}")
178
+ raise AICCException("Error during encrypt")
179
+
180
+ if output_stream:
181
+ with requests.post(url, json=payload, headers=headers, stream=True) as response:
182
+ response.raise_for_status()
183
+ for chunk in response.iter_lines():
184
+ if chunk:
185
+ if self.is_secure and encrypt_key:
186
+ data = encrypt_key.decrypt(base64.b64decode(chunk))
187
+ output_stream.write(f"{data}")
188
+ else:
189
+ output_stream.write(f"{chunk}")
190
+ else:
191
+ for attempt in range(self.max_retries):
192
+ try:
193
+ response = requests.post(url, json=payload, headers=headers)
194
+ response.raise_for_status()
195
+ data = response.json()
196
+
197
+ if self.is_secure and encrypt_key:
198
+ for elm in data['choices']:
199
+ elm['message'] = json.loads(encrypt_key.decrypt(elm['message']))
200
+
201
+ return json.dumps(data)
202
+ except Exception as e:
203
+ if attempt == self.max_retries - 1:
204
+ logger.error(f"call AICC VLLM service chat_completions error:{e}")
205
+ raise AICCException("vllm chat_completions error")
206
+ else:
207
+ logger.error(f"call AICC VLLM service chat_completions error:{e}, try again....")
208
+ time.sleep(1)
209
+ except AICCException as e:
210
+ raise e
211
+ except Exception as e:
212
+ logger.error(f"call AICC VLLM service chat_completions error: {e}")
213
+ raise AICCException("AICC VLLM service chat_completions error")
214
+
215
+ def chat_completions_stream(self, messages: list, output_stream: IO) -> Union[str, None]:
216
+ return self.chat_completions(messages, output_stream)
217
+
218
+ def chat_completions_stream_yield(self, messages: list):
219
+ """
220
+ 生成聊天补全流式响应
221
+ :param messages:
222
+ :return:
223
+ """
224
+ try:
225
+ if not messages:
226
+ raise AICCException("vllm chat_completions Messages cannot be empty")
227
+
228
+ if not self.base_url:
229
+ raise AICCException("vllm chat_completions_stream_yield base_url cannot be empty")
230
+
231
+ if self.is_secure:
232
+ url = f"{self.base_url}/v2/chat/completions"
233
+ else:
234
+ url = f"{self.base_url}/v1/chat/completions"
235
+
236
+ headers = {"Content-Type": "application/json"}
237
+ payload = {
238
+ "messages": messages,
239
+ "max_tokens": int(self.kwargs.get("max_tokens", 100)),
240
+ "temperature": float(self.kwargs.get("temperature", 1.0)),
241
+ "stream": True
242
+ }
243
+
244
+ encrypt_key = None
245
+ if self.is_secure:
246
+ if not self.jsc_client:
247
+ raise AICCException("jsc client is NULL")
248
+ try:
249
+ encrypted_messages, encrypt_key = \
250
+ self.jsc_client.encrypt_with_response(json.dumps(payload["messages"]))
251
+ payload["messages"] = encrypted_messages
252
+ except Exception as e:
253
+ logger.error(f"Error during encrypt: {e}")
254
+ raise AICCException("Error during encrypt")
255
+
256
+ with requests.post(url, json=payload, headers=headers, stream=True) as response:
257
+ response.raise_for_status()
258
+ for chunk in response.iter_lines():
259
+ if chunk:
260
+ if self.is_secure and encrypt_key:
261
+ data = encrypt_key.decrypt(base64.b64decode(chunk))
262
+ if isinstance(data, bytes):
263
+ data = data.decode()
264
+ yield data
265
+ else:
266
+ if isinstance(chunk, bytes):
267
+ chunk = chunk.decode()
268
+ yield chunk
269
+ except AICCException as e:
270
+ raise e
271
+ except Exception as e:
272
+ logger.error(f"call AICC VLLM service chat_completions_stream_yield error: {e}")
273
+ raise AICCException("AICC VLLM service chat_completions_stream_yield error")
274
+
275
+ def function_call(self, messages: list, tools: list, output_stream: IO = None):
276
+ """
277
+ :param messages:
278
+ :param tools:
279
+ :param output_stream:
280
+ :return:
281
+ """
282
+ error_info = "Sorry, this version does not support function_call at this time."
283
+ logger.error(error_info)
284
+ raise AICCException(error_info)
285
+
286
+ def function_call_stream(self, messages: list, tools: list, output_stream: IO):
287
+ return self.function_call(messages, tools, output_stream)
288
+
289
+ def asr_recognize(self, file_name: str) -> Union[str, None]:
290
+ error_info = "Sorry, this version does not support asr at this time."
291
+ logger.error(error_info)
292
+ return error_info