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,406 @@
1
+ import inspect
2
+ from functools import wraps
3
+
4
+ import httpx
5
+ from typing import Optional, Tuple
6
+ from urllib.parse import urlparse
7
+ from bytedance.jeddak_secure_channel import ResponseKey
8
+ import bytedance.jeddak_secure_channel as jsc
9
+ from .common.error import AICCException
10
+ from .common.log import logger
11
+
12
+
13
+ def _merge_unknown_kwargs_into_extra_body(kwargs: dict, accepted_params: set) -> None:
14
+ moved = {key: kwargs.pop(key) for key in list(kwargs) if key not in accepted_params}
15
+ if not moved:
16
+ return
17
+
18
+ extra_body = dict(kwargs.get("extra_body") or {})
19
+ for key, value in moved.items():
20
+ extra_body.setdefault(key, value)
21
+ kwargs["extra_body"] = extra_body
22
+
23
+
24
+ def _wrap_create_with_extra_kwargs(create_func):
25
+ signature = inspect.signature(create_func)
26
+ accepted_params = set(signature.parameters)
27
+ accepts_var_keyword = any(
28
+ param.kind == inspect.Parameter.VAR_KEYWORD
29
+ for param in signature.parameters.values()
30
+ )
31
+
32
+ def _merge(kwargs: dict) -> None:
33
+ # 底层函数本身带 **kwargs 时任何关键字都能接收,无需改写(也不会抛 TypeError)。
34
+ if accepts_var_keyword:
35
+ return
36
+ _merge_unknown_kwargs_into_extra_body(kwargs, accepted_params)
37
+
38
+ if inspect.iscoroutinefunction(create_func):
39
+ @wraps(create_func)
40
+ async def _async_wrapper(*args, **kwargs):
41
+ _merge(kwargs)
42
+ return await create_func(*args, **kwargs)
43
+
44
+ return _async_wrapper
45
+
46
+ @wraps(create_func)
47
+ def _wrapper(*args, **kwargs):
48
+ _merge(kwargs)
49
+ return create_func(*args, **kwargs)
50
+
51
+ return _wrapper
52
+
53
+
54
+ def enable_aicc_extra_kwargs(client):
55
+ """让 openai SDK 的 create() 支持直接传 AICC/方舟扩展参数(如 top_k)。
56
+
57
+ openai Python SDK 的 create() 形参表不含 top_k 等扩展字段,裸关键字会在本地抛
58
+ TypeError;此函数把这些非标准关键字并入 extra_body 后透传给网关。需在构造客户端后
59
+ 调用一次,同步与异步客户端均适用。
60
+ """
61
+ chat_completions = getattr(getattr(client, "chat", None), "completions", None)
62
+ if chat_completions is not None and getattr(chat_completions, "create", None) is not None:
63
+ chat_completions.create = _wrap_create_with_extra_kwargs(chat_completions.create)
64
+
65
+ responses = getattr(client, "responses", None)
66
+ if responses is not None and getattr(responses, "create", None) is not None:
67
+ responses.create = _wrap_create_with_extra_kwargs(responses.create)
68
+
69
+ return client
70
+
71
+
72
+
73
+ class AICCConfig:
74
+ """AICC 安全通信配置类"""
75
+
76
+ def __init__(self,
77
+ ak: str = "",
78
+ sk: str = "",
79
+ aicc_llm_service_name: str = "AICC.ConfidentialInference",
80
+ policy_id: str = "router_policy",
81
+ region: str = "cn-beijing",
82
+ top_url: str = "pcc.volcengineapi.com",
83
+ top_service: str = "pcc",
84
+ ra_url: str = "",
85
+ auth_token: str = "",
86
+ custom_headers: Optional[dict[str, str]] = None,
87
+ **kwargs):
88
+ """
89
+ :param ak: 火山引擎用户的ak
90
+ :param sk: 火山引擎用户的sk
91
+ :param aicc_llm_service_name: aicc推理服务的服务名称
92
+ :param policy_id: aicc推理服务做远程证明锁对应的policy
93
+ :param region: aicc推理服务所在的区域
94
+ :param service: aicc推理服务TOP网关的service
95
+ :param ra_url: 非TCA接口时的RA接口地址
96
+ :param auth_token: 非TCA接口时的鉴权凭据
97
+ :param kwargs:
98
+ """
99
+ self.ak = ak
100
+ self.sk = sk
101
+ self.aicc_llm_service_name = aicc_llm_service_name
102
+ self.policy_id = policy_id
103
+ self.region = region
104
+ self.top_url = top_url
105
+ self.top_service = top_service
106
+ self.ra_url = ra_url
107
+ self.auth_token = auth_token
108
+ self.custom_headers = custom_headers
109
+
110
+ self.kwargs = kwargs
111
+
112
+ self.HTTP_RA_PATH = "v1/security/token"
113
+ if self.ra_url:
114
+ if self.ra_url.startswith("wss://"):
115
+ self.ra_url = self.ra_url.replace("wss", "https")
116
+
117
+ if self.ra_url.endswith("/api/v3") or self.ra_url.endswith("/anthropic"):
118
+ parsed = urlparse(self.ra_url)
119
+ base_domain = f"{parsed.scheme}://{parsed.netloc}"
120
+ self.ra_url = f"{base_domain}/{self.HTTP_RA_PATH}"
121
+ else:
122
+ self.ra_url = f"{self.ra_url}/{self.HTTP_RA_PATH}"
123
+
124
+ def to_dict(self) -> dict:
125
+ """将配置对象转换为字典格式
126
+
127
+ :return: 包含所有配置项的字典
128
+ """
129
+ config_dict = {
130
+ "ak": self.ak,
131
+ "sk": self.sk,
132
+ "aicc_llm_service_name": self.aicc_llm_service_name,
133
+ "policy_id": self.policy_id,
134
+ "region": self.region,
135
+ "top_url": self.top_url,
136
+ "top_service": self.top_service,
137
+ "ra_url": self.ra_url,
138
+ "auth_token": self.auth_token,
139
+ "custom_headers": self.custom_headers
140
+ }
141
+
142
+ # 添加额外的 kwargs
143
+ if self.kwargs:
144
+ config_dict.update(self.kwargs)
145
+
146
+ return config_dict
147
+
148
+ def get_jsc_config(self) -> dict:
149
+ """生成aicc端云互信配置
150
+ """
151
+ jsc_config = dict()
152
+ jsc_config["ra_url"] = self.ra_url
153
+ jsc_config["auth_token"] = self.auth_token
154
+ jsc_config["ra_service_name"] = self.aicc_llm_service_name
155
+ jsc_config["ra_policy_id"] = self.policy_id
156
+ jsc_config["custom_headers"] = self.custom_headers
157
+
158
+ if self.ak and self.sk:
159
+ jsc_config["bytedance_top_info"] = dict()
160
+ jsc_config["bytedance_top_info"]["ak"] = self.ak
161
+ jsc_config["bytedance_top_info"]["sk"] = self.sk
162
+ jsc_config["bytedance_top_info"]["region"] = self.region
163
+ jsc_config["bytedance_top_info"]["url"] = self.top_url
164
+ jsc_config["bytedance_top_info"]["service"] = self.top_service
165
+
166
+ # 添加额外的 kwargs
167
+ if self.kwargs:
168
+ jsc_config.update(self.kwargs)
169
+
170
+ return jsc_config
171
+
172
+
173
+ AICC_SDK_VERSION = "0.1.0"
174
+ jsc_clients = dict()
175
+
176
+
177
+ class DemoSyncByteStream(httpx.SyncByteStream):
178
+ def __init__(self, raw_stream, encrypt_key: ResponseKey = None) -> None:
179
+ self._raw_stream = raw_stream
180
+ self.encrypt_key = encrypt_key
181
+ self.buffer = b""
182
+
183
+ def _decrypt_or_passthrough(self, payload: bytes) -> bytes:
184
+ if not payload or self.encrypt_key is None:
185
+ return payload
186
+
187
+ try:
188
+ return self.encrypt_key.decrypt(payload)
189
+ except Exception:
190
+ return payload
191
+
192
+ def __iter__(self):
193
+ for part in self._raw_stream:
194
+ if not isinstance(part, bytes):
195
+ part = part.encode("utf-8")
196
+
197
+ for line in part.splitlines(keepends=True):
198
+ self.buffer += line
199
+ if self.buffer.endswith((b"\r", b"\n")):
200
+ payload = self.buffer.strip()
201
+ cipher_events = self._decrypt_or_passthrough(payload)
202
+ cipher_events += b"\n" if self.buffer[-1] == 10 else b"" # 10 == "\n"
203
+ self.buffer = b""
204
+ yield cipher_events
205
+
206
+ if self.buffer:
207
+ payload = self.buffer
208
+ self.buffer = b""
209
+ yield self._decrypt_or_passthrough(payload)
210
+
211
+ def close(self) -> None:
212
+ if hasattr(self._raw_stream, "close"):
213
+ self._raw_stream.close()
214
+
215
+
216
+ class DemoAsyncByteStream(httpx.AsyncByteStream):
217
+ def __init__(self, raw_stream, encrypt_key: ResponseKey = None) -> None:
218
+ self._raw_stream = raw_stream
219
+ self.encrypt_key = encrypt_key
220
+ self.buffer = b""
221
+
222
+ def _decrypt_or_passthrough(self, payload: bytes) -> bytes:
223
+ if not payload or self.encrypt_key is None:
224
+ return payload
225
+
226
+ try:
227
+ return self.encrypt_key.decrypt(payload)
228
+ except Exception:
229
+ return payload
230
+
231
+ async def __aiter__(self):
232
+ async for part in self._raw_stream:
233
+ if not isinstance(part, bytes):
234
+ part = part.encode("utf-8")
235
+
236
+ for line in part.splitlines(keepends=True):
237
+ self.buffer += line
238
+ if self.buffer.endswith((b"\r", b"\n")):
239
+ payload = self.buffer.strip()
240
+ cipher_events = self._decrypt_or_passthrough(payload)
241
+ cipher_events += b"\n" if self.buffer[-1] == 10 else b"" # 10 == "\n"
242
+ self.buffer = b""
243
+ yield cipher_events
244
+
245
+ if self.buffer:
246
+ payload = self.buffer
247
+ self.buffer = b""
248
+ yield self._decrypt_or_passthrough(payload)
249
+
250
+ async def aclose(self) -> None:
251
+ if hasattr(self._raw_stream, "aclose"):
252
+ await self._raw_stream.aclose()
253
+ elif hasattr(self._raw_stream, "close"):
254
+ self._raw_stream.close()
255
+
256
+
257
+ class SecureHttpClient(httpx.Client):
258
+ def __init__(self, *args, **kwargs):
259
+ self.aicc_config: Optional[AICCConfig] = None
260
+
261
+ if kwargs.get("aicc_config"):
262
+ try:
263
+ self.aicc_config = kwargs.pop("aicc_config")
264
+ secure_channel_config = jsc.ClientConfig.from_dict(self.aicc_config.get_jsc_config())
265
+ jsc_clients[self.aicc_config.aicc_llm_service_name] = jsc.Client(secure_channel_config)
266
+ except Exception as e:
267
+ logger.error(f"Error init jsc, aicc_llm_service_name={self.aicc_config.aicc_llm_service_name}, {e}")
268
+ self.aicc_config = None
269
+ raise AICCException("New deepseek client failed")
270
+
271
+ super().__init__(*args, **kwargs)
272
+
273
+ def jsc_encryption(self, msg: bytes) -> Tuple[str, ResponseKey]:
274
+ encrypt_key: Optional[ResponseKey] = None
275
+
276
+ if self.aicc_config:
277
+ jsc_client = jsc_clients.get(self.aicc_config.aicc_llm_service_name)
278
+ if jsc_client:
279
+ plain_len = len(msg)
280
+ msg, encrypt_key = jsc_client.encrypt_with_response(msg)
281
+ cipher_len = len(msg) if isinstance(msg, (bytes, bytearray, str)) else -1
282
+ logger.info(
283
+ f"[JSC] jsc_encryption invoked\n"
284
+ f" plainLen={plain_len}\n"
285
+ f" cipherLen={cipher_len}\n"
286
+ f" responseKeyExists={encrypt_key is not None}"
287
+ )
288
+
289
+ return msg, encrypt_key
290
+
291
+ def send(self, request: httpx.Request, *args, **kwargs):
292
+ encrypt_key: Optional[ResponseKey] = None
293
+
294
+ if self.aicc_config:
295
+ # 对所有请求进行加密(包括multipart请求)
296
+ request.headers["X-AICC-Encryption-Enable"] = "true"
297
+ request.headers["X-AICC-Encryption-SDK"] = "aicc"
298
+ request.headers["X-AICC-Encryption-Version"] = AICC_SDK_VERSION
299
+
300
+ # 先读取内容(处理流式请求)
301
+ try:
302
+ content = request.content
303
+ except httpx.RequestNotRead:
304
+ content = request.read()
305
+
306
+ encryption_content, encrypt_key = self.jsc_encryption(content)
307
+ if isinstance(encryption_content, str):
308
+ encryption_content = encryption_content.encode("utf-8")
309
+
310
+ request.stream = httpx.ByteStream(encryption_content)
311
+ if hasattr(request, "_content"):
312
+ delattr(request, "_content")
313
+ # 不修改Content-Type,保持原始的Content-Type(如multipart/form-data)
314
+ request.headers["Content-Length"] = str(len(encryption_content))
315
+ request.read()
316
+
317
+ resp = super().send(request, *args, **kwargs)
318
+
319
+ if 200 <= resp.status_code < 300:
320
+ if encrypt_key:
321
+ if not kwargs.get("stream", False):
322
+ content = resp.content
323
+ decrypted_content = encrypt_key.decrypt(content)
324
+ resp._content = decrypted_content
325
+ else:
326
+ resp.stream = DemoSyncByteStream(resp.stream, encrypt_key)
327
+ else:
328
+ if encrypt_key:
329
+ try:
330
+ if not kwargs.get("stream", False):
331
+ content = resp.content
332
+ decrypted_content = encrypt_key.decrypt(content)
333
+ resp._content = decrypted_content
334
+ else:
335
+ resp.stream = DemoSyncByteStream(resp.stream, encrypt_key)
336
+ except Exception as e:
337
+ pass
338
+ return resp
339
+
340
+
341
+ class AsyncSecureHttpClient(httpx.AsyncClient):
342
+ def __init__(self, *args, **kwargs):
343
+ self.aicc_config: Optional[AICCConfig] = None
344
+
345
+ if kwargs.get("aicc_config"):
346
+ try:
347
+ self.aicc_config = kwargs.pop("aicc_config")
348
+ secure_channel_config = jsc.ClientConfig.from_dict(self.aicc_config.get_jsc_config())
349
+ jsc_clients[self.aicc_config.aicc_llm_service_name] = jsc.Client(secure_channel_config)
350
+ except Exception as e:
351
+ logger.error(f"Error init jsc, aicc_llm_service_name={self.aicc_config.aicc_llm_service_name}, {e}")
352
+ self.aicc_config = None
353
+ raise AICCException("New async secure client failed")
354
+
355
+ super().__init__(*args, **kwargs)
356
+
357
+ def jsc_encryption(self, msg: bytes) -> Tuple[str, ResponseKey]:
358
+ encrypt_key: Optional[ResponseKey] = None
359
+
360
+ if self.aicc_config:
361
+ jsc_client = jsc_clients.get(self.aicc_config.aicc_llm_service_name)
362
+ if jsc_client:
363
+ msg, encrypt_key = jsc_client.encrypt_with_response(msg)
364
+
365
+ return msg, encrypt_key
366
+
367
+ async def send(self, request: httpx.Request, *args, **kwargs):
368
+ encrypt_key: Optional[ResponseKey] = None
369
+
370
+ if self.aicc_config:
371
+ request.headers["X-AICC-Encryption-Enable"] = "true"
372
+ request.headers["X-AICC-Encryption-SDK"] = "aicc"
373
+ request.headers["X-AICC-Encryption-Version"] = AICC_SDK_VERSION
374
+
375
+ encryption_content, encrypt_key = self.jsc_encryption(request.content)
376
+ if isinstance(encryption_content, str):
377
+ encryption_content = encryption_content.encode("utf-8")
378
+
379
+ request.stream = httpx.ByteStream(encryption_content)
380
+ if hasattr(request, "_content"):
381
+ delattr(request, "_content")
382
+ request.headers["Content-Type"] = "application/json"
383
+ request.headers["Content-Length"] = str(len(encryption_content))
384
+
385
+ resp = await super().send(request, *args, **kwargs)
386
+
387
+ if 200 <= resp.status_code < 300:
388
+ if encrypt_key:
389
+ if not kwargs.get("stream", False):
390
+ content = await resp.aread()
391
+ decrypted_content = encrypt_key.decrypt(content)
392
+ resp._content = decrypted_content
393
+ else:
394
+ resp.stream = DemoAsyncByteStream(resp.stream, encrypt_key)
395
+ else:
396
+ if encrypt_key:
397
+ try:
398
+ if not kwargs.get("stream", False):
399
+ content = await resp.aread()
400
+ decrypted_content = encrypt_key.decrypt(content)
401
+ resp._content = decrypted_content
402
+ else:
403
+ resp.stream = DemoAsyncByteStream(resp.stream, encrypt_key)
404
+ except Exception as e:
405
+ pass
406
+ return resp