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,500 @@
1
+ """
2
+ seedance机密推理客户端
3
+ """
4
+ __all__ = ["SeedanceClient"]
5
+
6
+ import os
7
+ import base64
8
+ import requests
9
+ import traceback
10
+ from urllib.parse import unquote
11
+ from cryptography.hazmat.primitives import serialization as crypto_serialization
12
+ from cryptography.hazmat.primitives.asymmetric import rsa
13
+ from cryptography.hazmat.backends import default_backend as crypto_default_backend
14
+ from bytedance.jeddak_secure_channel.crypto import PrivateKey
15
+ from .secure_http_client import SecureHttpClient
16
+ from .common.tos_utils import TOSUtils
17
+ from .common.error import AICCException
18
+ from .common.log import logger
19
+ from .secure_http_client import AICCConfig
20
+ from .common.aes import decrypt_file
21
+
22
+
23
+ class SeedanceClient:
24
+ """
25
+ DeepSeek机密推理客户端.
26
+ """
27
+
28
+ def __init__(self,
29
+ base_url: str = "",
30
+ api_key: str = "",
31
+ endpoint: str = "",
32
+ aicc_config: AICCConfig = None,
33
+ **kwargs):
34
+ self.base_url = base_url
35
+ self.api_key = api_key
36
+ self.endpoint = endpoint
37
+ self.aicc_config = aicc_config
38
+ self.tos_utils = TOSUtils()
39
+ self.kwargs = kwargs
40
+ self.is_secure = self.kwargs.get("is_secure", True)
41
+
42
+ if aicc_config:
43
+ self.secure_http_client = SecureHttpClient(aicc_config=aicc_config)
44
+ else:
45
+ if self.is_secure:
46
+ aicc_config = AICCConfig(ra_url=base_url, auth_token=api_key)
47
+ self.secure_http_client = SecureHttpClient(aicc_config=aicc_config)
48
+ else:
49
+ self.secure_http_client = SecureHttpClient()
50
+
51
+ # 是否开启视频文件加密, 默认加密
52
+ self.is_enable_video_encrypt = str(self.kwargs.get("is_enable_video_encrypt", True)).lower() == "true"
53
+
54
+ # 视频文件的存储位置, 目前仅支持火山TOS
55
+ self.video_file_storage_location = self.kwargs.get("video_file_storage_location", "TOS").upper()
56
+
57
+ self.video_file_encrypt_pub_key_content = None
58
+ self.video_file_encrypt_priv_key_content = None
59
+
60
+ self.video_file_encrypt_priv_key: PrivateKey = None
61
+
62
+ self.timeout = self.kwargs.get("timeout", 120.0)
63
+
64
+ # API Endpoints
65
+ self.CREATE_TASK_URL = "/contents/generations/tasks"
66
+ self.QUERY_TASK_URL = "/contents/generations/tasks"
67
+ self.QUERY_TASK_LIST_URL = "/contents/generations/tasks"
68
+ self.DELETE_TASK_URL = "/contents/generations/tasks"
69
+
70
+ def _get_base_url(self):
71
+ """
72
+ Get base URL with trailing slash removed
73
+ """
74
+ return self.base_url.rstrip("/")
75
+
76
+ def set_video_file_encrypt_key(self, pub_key_path: str = "", priv_key_path: str = ""):
77
+ """
78
+ Set video file encrypt key
79
+ Args:
80
+ pub_key_path: Public key for encrypt
81
+ priv_key_path: Private key for decrypt
82
+ """
83
+ if os.path.exists(pub_key_path):
84
+ with open(pub_key_path, 'r') as f:
85
+ content = f.read()
86
+ if '-----BEGIN' in content and '-----END' in content:
87
+ self.video_file_encrypt_pub_key_content = content
88
+
89
+ if os.path.exists(priv_key_path):
90
+ with open(priv_key_path, 'r') as f:
91
+ content = f.read()
92
+ if '-----BEGIN' in content and '-----END' in content:
93
+ self.video_file_encrypt_priv_key_content = content
94
+ self.video_file_encrypt_priv_key = \
95
+ PrivateKey.from_private_pem(self.video_file_encrypt_priv_key_content)
96
+
97
+ def _generate_key_pair(self, pub_key_path: str = "", priv_key_path: str = ""):
98
+ """
99
+ Generate RSA 4096 key pair and save to files
100
+
101
+ Args:
102
+ pub_key_path: Path to save public key
103
+ priv_key_path: Path to save private key
104
+
105
+ Returns:
106
+ Tuple of (public_key_path, private_key_path)
107
+ """
108
+ try:
109
+ # Generate RSA key pair
110
+ key = rsa.generate_private_key(
111
+ backend=crypto_default_backend(),
112
+ public_exponent=65537,
113
+ key_size=4096
114
+ )
115
+
116
+ # Save private key
117
+ private_key = key.private_bytes(
118
+ crypto_serialization.Encoding.PEM,
119
+ crypto_serialization.PrivateFormat.PKCS8,
120
+ crypto_serialization.NoEncryption()
121
+ )
122
+ with open(priv_key_path, 'wb') as f:
123
+ f.write(private_key)
124
+
125
+ # Save public key
126
+ public_key = key.public_key().public_bytes(
127
+ crypto_serialization.Encoding.PEM,
128
+ crypto_serialization.PublicFormat.SubjectPublicKeyInfo
129
+ )
130
+ with open(pub_key_path, 'wb') as f:
131
+ f.write(public_key)
132
+ except Exception as e:
133
+ logger.error(f"[Error] Failed to generate key pair: {e}")
134
+ raise AICCException("Failed to generate RSA key pair") from e
135
+
136
+ def generate_video_file_encrypt_key(self, pub_key_path: str = "", priv_key_path: str = ""):
137
+ """
138
+ Generate video file encrypt key
139
+ Args:
140
+ pub_key_path: Public key for encrypt
141
+ priv_key_path: Private key for decrypt
142
+ """
143
+ # 如果没有提供路径报错
144
+ if not pub_key_path or not priv_key_path:
145
+ raise ValueError("pub_key_path and priv_key_path must be provided")
146
+
147
+ self._generate_key_pair(pub_key_path, priv_key_path)
148
+ # self.set_video_file_encrypt_key(pub_key_path, priv_key_path)
149
+
150
+ def create_video_generation_task(self, data: dict, custom_header: dict = None) -> str:
151
+ """
152
+ :param data:
153
+ :param custom_header:
154
+ :return:
155
+ """
156
+ if not data:
157
+ raise AICCException("data must be provided")
158
+
159
+ if "model" not in data:
160
+ data["model"] = self.endpoint
161
+
162
+ base_url = self._get_base_url()
163
+ url = f"{base_url}{self.CREATE_TASK_URL}"
164
+ headers = {
165
+ "Content-Type": "application/json",
166
+ "Authorization": f"Bearer {self.api_key}"
167
+ }
168
+ if custom_header:
169
+ headers.update(custom_header)
170
+
171
+ # Add encryption headers if provided
172
+ if self.is_enable_video_encrypt:
173
+ if not self.video_file_encrypt_pub_key_content or not self.video_file_encrypt_priv_key_content:
174
+ raise AICCException("pub_key and priv_key must be provided when Is-Enable-Encrypt is 'true'")
175
+
176
+ headers["Enable-TOS-Content-Result-Encryption"] = str(self.is_enable_video_encrypt).lower()
177
+ headers["X-Encryption-Algorithm"] = "RSA_OAEP_4096_AES_256"
178
+ if self.video_file_encrypt_pub_key_content:
179
+ # Base64 encode the PEM public key for safe HTTP header transmission
180
+ # This avoids issues with newlines and special characters in headers
181
+ pk_base64 = base64.b64encode(self.video_file_encrypt_pub_key_content.encode()).decode()
182
+ headers["PK"] = pk_base64
183
+
184
+ # logger.info(f"[Request] Data: {json.dumps(data, ensure_ascii=False, indent=2)}")
185
+
186
+ response = self.secure_http_client.post(url, headers=headers, json=data, timeout=self.timeout)
187
+ if response.status_code == 200:
188
+ result = response.json()
189
+ task_id = result.get("id")
190
+ return task_id
191
+ else:
192
+ logger.error(f"[Error] url: {url}, response: {response.text}")
193
+ raise AICCException(f"create seedance task error: {response.text}")
194
+
195
+ def create_video_generation_task_by_message(self, message: list, custom_header: dict = None,
196
+ generate_audio: bool = True,
197
+ ratio: str = "16:9", duration: int = 11,
198
+ watermark: bool = False) -> str:
199
+ """
200
+ message: List of messages to generate video
201
+ """
202
+ if not message:
203
+ raise AICCException("message must be provided")
204
+
205
+ data = {
206
+ "model": self.endpoint,
207
+ "content": message,
208
+ "generate_audio": generate_audio,
209
+ "ratio": ratio,
210
+ "duration": duration,
211
+ "watermark": watermark
212
+ }
213
+ return self.create_video_generation_task(data, custom_header)
214
+
215
+ def query_video_generation_task(self, task_id: str) -> dict:
216
+ """
217
+ Query video generation task
218
+ API: GET /api/v3/contents/generations/tasks/{task_id}
219
+ """
220
+
221
+ if not task_id:
222
+ raise AICCException("task_id must be provided")
223
+
224
+ base_url = self._get_base_url()
225
+ url = f"{base_url}{self.QUERY_TASK_URL}/{task_id}"
226
+ headers = {
227
+ "Content-Type": "application/json",
228
+ "Authorization": f"Bearer {self.api_key}"
229
+ }
230
+
231
+ # logger.info(f"[Request] URL: {url}")
232
+
233
+ # response = self.secure_http_client.get(url, headers=headers, timeout=self.timeout)
234
+ response = self.secure_http_client.request(
235
+ "GET", url, headers=headers, content=b"{}", timeout=self.timeout
236
+ )
237
+
238
+ if response.status_code == 200:
239
+ result = response.json()
240
+ # logger.info(f"[Response] Body: {json.dumps(result, ensure_ascii=False, indent=2)}")
241
+
242
+ # status = result.get("status")
243
+ # if status == "succeeded":
244
+ # video_url = result.get("content", {}).get("video_url")
245
+ # print(f"[Video URL] {video_url}")
246
+
247
+ return result
248
+ else:
249
+ try:
250
+ result = response.json()
251
+ return result
252
+ except Exception as e:
253
+ logger.error(f"[Error] url: {url}, Exception={e}")
254
+ raise AICCException(f"Failed to query video generation task")
255
+
256
+ def query_video_generation_task_list(
257
+ self,
258
+ page_num: int = 0,
259
+ page_size: int = 0,
260
+ status: str = "",
261
+ task_ids: list = None,
262
+ model: str = "",
263
+ service_tier: str = ""
264
+ ) -> dict:
265
+ """
266
+ Query video generation task list
267
+ API: GET /api/v3/contents/generations/tasks
268
+
269
+ Args:
270
+ page_num: Page number (only added if > 0)
271
+ page_size: Page size (only added if > 0)
272
+ status: Filter by status (only added if not empty)
273
+ task_ids: Filter by task IDs (only added if not empty list)
274
+ model: Filter by model (only added if not empty)
275
+ service_tier: Filter by service tier (only added if not empty)
276
+
277
+ Returns:
278
+ Query result dict
279
+ """
280
+ from urllib.parse import urlencode
281
+
282
+ base_url = self._get_base_url()
283
+
284
+ # Build query parameters (only add non-empty values)
285
+ params = []
286
+
287
+ if page_num > 0:
288
+ params.append(f"page_num={page_num}")
289
+
290
+ if page_size > 0:
291
+ params.append(f"page_size={page_size}")
292
+
293
+ if status:
294
+ params.append(f"filter.status={status}")
295
+
296
+ if task_ids and len(task_ids) > 0:
297
+ for task_id in task_ids:
298
+ params.append(f"filter.task_ids={task_id}")
299
+
300
+ if model:
301
+ params.append(f"filter.model={model}")
302
+
303
+ if service_tier:
304
+ params.append(f"filter.service_tier={service_tier}")
305
+
306
+ # Build URL with query parameters
307
+ query_string = "&".join(params)
308
+ if query_string:
309
+ url = f"{base_url}{self.QUERY_TASK_LIST_URL}?{query_string}"
310
+ else:
311
+ url = f"{base_url}{self.QUERY_TASK_LIST_URL}"
312
+
313
+ headers = {
314
+ "Content-Type": "application/json",
315
+ "Authorization": f"Bearer {self.api_key}"
316
+ }
317
+
318
+ response = self.secure_http_client.get(url, headers=headers, timeout=self.timeout)
319
+ try:
320
+ result = response.json()
321
+ return result
322
+ except Exception as e:
323
+ logger.error(f"query_video_generation_task_list error: {e}")
324
+ raise AICCException(f"Failed to query video generation task list")
325
+
326
+ def delete_video_generation_task(self, task_id: str):
327
+ """
328
+ Delete video generation task
329
+ API: DELETE /api/v3/contents/generations/tasks/{task_id}
330
+ """
331
+ if not task_id:
332
+ raise AICCException("task_id must be provided")
333
+
334
+ base_url = self._get_base_url()
335
+ url = f"{base_url}{self.DELETE_TASK_URL}/{task_id}"
336
+ headers = {
337
+ "Content-Type": "application/json",
338
+ "Authorization": f"Bearer {self.api_key}"
339
+ }
340
+ response = self.secure_http_client.delete(url, headers=headers, timeout=self.timeout)
341
+ try:
342
+ result = response.json()
343
+ return result
344
+ except Exception as e:
345
+ logger.error(f"delete_video_generation_task error: {e}")
346
+ raise AICCException(f"Failed to delete_video_generation_task")
347
+
348
+ def video_file_download(self, task_id: str, video_file_path: str) -> bool:
349
+ """
350
+ Download and decrypt encrypted video
351
+ This function polls the task status until it succeeds, then downloads
352
+ and decrypts the video using the provided private key.
353
+ Args:
354
+ task_id: Video generation task ID
355
+ video_file_path: Directory to save decrypted video
356
+
357
+ Returns:
358
+ Path to decrypted video file, or None if failed
359
+ """
360
+ if not task_id:
361
+ raise AICCException("task_id must be provided")
362
+
363
+ if not video_file_path:
364
+ raise AICCException("video_file_path must be provided")
365
+
366
+ result = self.query_video_generation_task(task_id)
367
+ if not result:
368
+ logger.error(f"[Error] Failed to query task,task_id={task_id}")
369
+ return False
370
+
371
+ status = result.get("status")
372
+ if status == "succeeded":
373
+ video_url = result.get("content", {}).get("video_url")
374
+ if not video_url:
375
+ logger.error("[Error] No video_url in response")
376
+ return False
377
+
378
+ return self.download_file_normal(video_url, video_file_path)
379
+ # encrypted_key = b""
380
+ # if self.is_enable_video_encrypt:
381
+ # encrypted_key = result.get("encrypted_key")
382
+ # if not encrypted_key:
383
+ # logger.error("[Error] No encrypted_key in response")
384
+ # return False
385
+ # if not self.video_file_encrypt_priv_key:
386
+ # logger.error("[Error] No private key set, cannot decrypt encrypted video")
387
+ # return False
388
+ # else:
389
+ # # Base64 decode the encrypted key before RSA decryption
390
+ # encrypted_key_bytes = base64.b64decode(encrypted_key)
391
+ # encrypted_key = self.video_file_encrypt_priv_key.decrypt(encrypted_key_bytes)
392
+ #
393
+ # logger.info(f"\n[Downloading] Starting download and decryption...")
394
+ #
395
+ # try:
396
+ # if self.video_file_storage_location == "TOS":
397
+ # self.tos_utils.download_file(tos_url=video_url, local_file_path=video_file_path,
398
+ # encrypted_key=encrypted_key)
399
+ # else:
400
+ # logger.error(f"[Error] Unsupported video storage location: {self.video_file_storage_location}")
401
+ # return False
402
+ #
403
+ # logger.info(f"\n[Success] Decrypted video saved to: {video_file_path}")
404
+ # return True
405
+ #
406
+ # except Exception as e:
407
+ # logger.error(f"[Error] Download and decryption failed: {str(e)}")
408
+ # return False
409
+
410
+ elif status == "failed":
411
+ error_info = result.get("error", "Unknown error")
412
+ logger.error(f"[Error] Task failed: {error_info}")
413
+ return False
414
+
415
+ elif status in ["pending", "running", "queued"]:
416
+ logger.info(f"[Waiting] Task is {status}, please wait for it to finish...")
417
+ return False
418
+ else:
419
+ logger.warning(f"[Warning] Unknown status: {status}")
420
+ return False
421
+
422
+ def video_file_download_by_url(self, video_url: str, video_file_path: str) -> bool:
423
+ """
424
+ Download and decrypt encrypted video
425
+ This function polls the task status until it succeeds, then downloads
426
+ and decrypts the video using the provided private key.
427
+ Args:
428
+ video_url: Video video_url
429
+ video_file_path: Directory to save decrypted video
430
+
431
+ Returns:
432
+ Path to decrypted video file, or None if failed
433
+ """
434
+ if not video_url:
435
+ raise AICCException("video_url must be provided")
436
+
437
+ if not video_file_path:
438
+ raise AICCException("video_file_path must be provided")
439
+
440
+ logger.info(f"\n[Downloading] Starting download and decryption...")
441
+
442
+ try:
443
+ return self.download_file_normal(video_url, video_file_path)
444
+ except Exception as e:
445
+ logger.error(f"[Error] Download and decryption failed: {str(e)}")
446
+ return False
447
+
448
+ def download_file_normal(self, video_url: str, video_file_path: str) -> bool:
449
+ """
450
+ Download video file
451
+ Args:
452
+ video_url: Video video_url
453
+ video_file_path: Directory to save video file
454
+
455
+ Returns:
456
+ Path to video file, or None if failed
457
+ """
458
+ if not video_url:
459
+ raise AICCException("video_url must be provided")
460
+
461
+ if not video_file_path:
462
+ raise AICCException("video_file_path must be provided")
463
+
464
+ if os.path.exists(video_file_path) and os.path.getsize(video_file_path) > 0:
465
+ logger.error(f"[Error] File {video_file_path} already exists and is not empty")
466
+ return False
467
+
468
+ try:
469
+ response = requests.get(video_url, stream=True)
470
+ with open(video_file_path, "wb") as f:
471
+ for chunk in response.iter_content(chunk_size=8192):
472
+ f.write(chunk)
473
+
474
+ if self.is_enable_video_encrypt:
475
+ if not self.video_file_encrypt_priv_key:
476
+ logger.error("[Error] No private key set, cannot decrypt encrypted video")
477
+ return False
478
+
479
+ headers = response.headers
480
+ encrypted_key = headers.get("x-tos-meta-enc-dek")
481
+ # nonce = headers.get("x-tos-meta-x-tos-meta-base-nonce")
482
+ if not encrypted_key:
483
+ logger.error("[Error] No encrypted_key in response")
484
+ return False
485
+
486
+ try:
487
+ encrypted_key_bytes = base64.b64decode(unquote(encrypted_key))
488
+ encrypted_key = self.video_file_encrypt_priv_key.decrypt(encrypted_key_bytes)
489
+ tmp_file_path = video_file_path + ".tmp"
490
+ decrypt_file(encrypted_key, video_file_path, tmp_file_path, 'b')
491
+ os.remove(video_file_path)
492
+ os.rename(tmp_file_path, video_file_path)
493
+ except Exception as e:
494
+ logger.error(f"[Error] Failed to decode encrypted_key: {e}")
495
+ return False
496
+ except Exception as e:
497
+ logger.error(f"[Error] Download failed: {e}")
498
+ traceback.print_exc()
499
+ return False
500
+ return True