volcengine-python-sdk-aicc 0.0.41__tar.gz → 0.0.42__tar.gz

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 (84) hide show
  1. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/PKG-INFO +26 -9
  2. volcengine_python_sdk_aicc-0.0.42/README.md +52 -0
  3. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/volcengine_aicc_sdk/__init__.py +1 -1
  4. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/volcengine_aicc_sdk/aicc_client.py +5 -0
  5. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/volcengine_aicc_sdk/secure_http_client.py +4 -0
  6. volcengine_python_sdk_aicc-0.0.42/bytedance/volcengine_aicc_sdk/tts_client.py +295 -0
  7. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/pyproject.toml +3 -10
  8. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/tests/test_secure_http_client.py +0 -8
  9. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/tests/test_seedance_inference_client.py +0 -8
  10. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/tests/test_tos_utils.py +0 -9
  11. volcengine_python_sdk_aicc-0.0.42/tests/test_tts_client.py +293 -0
  12. volcengine_python_sdk_aicc-0.0.42/tests/test_tts_streaming_pcm.py +45 -0
  13. volcengine_python_sdk_aicc-0.0.42/volcengine/aicc/__init__.py +47 -0
  14. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/volcengine_python_sdk_aicc.egg-info/PKG-INFO +26 -9
  15. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/volcengine_python_sdk_aicc.egg-info/SOURCES.txt +4 -0
  16. volcengine_python_sdk_aicc-0.0.42/volcengine_python_sdk_aicc.egg-info/top_level.txt +2 -0
  17. volcengine_python_sdk_aicc-0.0.41/README.md +0 -35
  18. volcengine_python_sdk_aicc-0.0.41/volcengine_python_sdk_aicc.egg-info/top_level.txt +0 -1
  19. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/LICENSE +0 -0
  20. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/__init__.py +0 -0
  21. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/jeddak_secure_channel/__init__.py +0 -0
  22. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/jeddak_secure_channel/client.py +0 -0
  23. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/jeddak_secure_channel/config.py +0 -0
  24. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/jeddak_secure_channel/consts.py +0 -0
  25. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/jeddak_secure_channel/crypto/__init__.py +0 -0
  26. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/jeddak_secure_channel/crypto/aes.py +0 -0
  27. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/jeddak_secure_channel/crypto/envelope.py +0 -0
  28. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/jeddak_secure_channel/crypto/rsa.py +0 -0
  29. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/jeddak_secure_channel/error.py +0 -0
  30. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/jeddak_secure_channel/log/__init__.py +0 -0
  31. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/jeddak_secure_channel/log/config.py +0 -0
  32. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/jeddak_secure_channel/log/logger.py +0 -0
  33. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/jeddak_secure_channel/logger_factory.py +0 -0
  34. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/jeddak_secure_channel/ra.py +0 -0
  35. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/jeddak_secure_channel/server.py +0 -0
  36. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/jeddak_secure_channel/service/__init__.py +0 -0
  37. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/jeddak_secure_channel/service/eps.py +0 -0
  38. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/jeddak_secure_channel/service/eps_models.py +0 -0
  39. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/jeddak_secure_channel/service/ras.py +0 -0
  40. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/jeddak_secure_channel/service/tks.py +0 -0
  41. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/jeddak_secure_channel/service/tks_models.py +0 -0
  42. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/jeddak_secure_channel/utils.py +0 -0
  43. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/jeddak_secure_model/model_automation_tool.py +0 -0
  44. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/jeddak_secure_model/model_encryption.py +0 -0
  45. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/jeddak_secure_model/model_hub_admin.py +0 -0
  46. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/jeddak_secure_model/pcc_utils.py +0 -0
  47. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/jeddak_secure_model/rds_utils.py +0 -0
  48. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/tks/attester.py +0 -0
  49. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/tks/client.py +0 -0
  50. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/tks/consts.py +0 -0
  51. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/tks/crypto/aes.py +0 -0
  52. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/tks/crypto/ec.py +0 -0
  53. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/tks/exceptions.py +0 -0
  54. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/volcengine_aicc_sdk/aicc_inference.py +0 -0
  55. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/volcengine_aicc_sdk/ark_inference_client.py +0 -0
  56. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/volcengine_aicc_sdk/asr_inference_client.py +0 -0
  57. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/volcengine_aicc_sdk/asr_websocket_client.py +0 -0
  58. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/volcengine_aicc_sdk/auc_inference_client.py +0 -0
  59. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/volcengine_aicc_sdk/common/__init__.py +0 -0
  60. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/volcengine_aicc_sdk/common/aes.py +0 -0
  61. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/volcengine_aicc_sdk/common/constant.py +0 -0
  62. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/volcengine_aicc_sdk/common/error.py +0 -0
  63. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/volcengine_aicc_sdk/common/log/__init__.py +0 -0
  64. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/volcengine_aicc_sdk/common/log/config.py +0 -0
  65. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/volcengine_aicc_sdk/common/log/logger.py +0 -0
  66. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/volcengine_aicc_sdk/common/tos_utils.py +0 -0
  67. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/volcengine_aicc_sdk/common/utils.py +0 -0
  68. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/volcengine_aicc_sdk/cu_inference_client.py +0 -0
  69. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/volcengine_aicc_sdk/deepseek_inference_client.py +0 -0
  70. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/volcengine_aicc_sdk/seedance_inference_client.py +0 -0
  71. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/volcengine_aicc_sdk/seedream_inference_client.py +0 -0
  72. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/setup.cfg +0 -0
  73. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/setup.py +0 -0
  74. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/tests/test_aicc_client.py +0 -0
  75. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/tests/test_aicc_inference.py +0 -0
  76. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/tests/test_ark_inference_client.py +0 -0
  77. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/tests/test_asr_inference_client.py +0 -0
  78. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/tests/test_asr_websocket_client.py +0 -0
  79. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/tests/test_auc_inference_client.py +0 -0
  80. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/tests/test_cu_inference_client.py +0 -0
  81. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/tests/test_deepseek_inference_client.py +0 -0
  82. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/tests/test_seedream_inference_client.py +0 -0
  83. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/volcengine_python_sdk_aicc.egg-info/dependency_links.txt +0 -0
  84. {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/volcengine_python_sdk_aicc.egg-info/requires.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: volcengine-python-sdk-aicc
3
- Version: 0.0.41
3
+ Version: 0.0.42
4
4
  Summary: Volcengine AICC Python SDK for confidential and secure AI inference
5
5
  Author-email: Volcengine AICC SDK Team <volcengine@bytedance.com>
6
6
  License-Expression: Apache-2.0
@@ -45,7 +45,7 @@ Dynamic: license-file
45
45
  # Volcengine AICC SDK for Python
46
46
 
47
47
  The official Python SDK for Volcengine AICC, providing confidential and secure
48
- AI inference clients (chat, vision, video generation, ASR, etc.) with remote
48
+ AI inference clients (chat, vision, video generation, ASR, TTS, etc.) with remote
49
49
  attestation verification.
50
50
 
51
51
  ## Installation
@@ -54,21 +54,38 @@ attestation verification.
54
54
  pip install volcengine-python-sdk-aicc
55
55
  ```
56
56
 
57
- The distribution name is `volcengine-python-sdk-aicc`; the Python import
58
- package is `bytedance.volcengine_aicc_sdk`.
59
-
60
- ## Quick start
57
+ ## Quick start (recommended public import)
61
58
 
62
59
  ```python
63
- from bytedance.volcengine_aicc_sdk import AICCClient
64
- from bytedance.volcengine_aicc_sdk.secure_http_client import AICCConfig
60
+ from volcengine.aicc import AICCClient, AICCConfig
65
61
 
66
62
  client = AICCClient(config=AICCConfig(base_url="<YOUR_BASE_URL>", api_key="<YOUR_API_KEY>"))
67
63
  ```
68
64
 
65
+ ## Legacy import compatibility
66
+
67
+ Existing customer code remains supported unchanged:
68
+
69
+ ```python
70
+ from bytedance.volcengine_aicc_sdk import AICCClient
71
+ from bytedance.volcengine_aicc_sdk.secure_http_client import AICCConfig, SecureHttpClient
72
+ ```
73
+
74
+ The distribution name is `volcengine-python-sdk-aicc`. `volcengine.aicc` is the recommended
75
+ public import facade; `bytedance.volcengine_aicc_sdk` remains the implementation and legacy
76
+ compatibility path. Both expose the same class objects.
77
+
78
+ ## Available clients
79
+
80
+ `AICCClient`, `AICCConfig`, `SecureHttpClient`, `AsyncSecureHttpClient`,
81
+ `AICCInference`, `ARKClient`, `DeepSeekClient`, `CUClient`, `AUCClient`,
82
+ `ASRClient`, `AsrWebSocketClient`, `SeedanceClient`, `SeedreamClient`,
83
+ `TTSClient`, and `TTSResponse`.
84
+
69
85
  ## Project layout
70
86
 
71
- - `bytedance.volcengine_aicc_sdk`: AICC inference clients.
87
+ - `volcengine.aicc`: recommended public import facade.
88
+ - `bytedance.volcengine_aicc_sdk`: implementation and legacy import path.
72
89
  - `bytedance.jeddak_secure_channel`: confidential-computing secure channel and
73
90
  remote attestation report verification.
74
91
  - `bytedance.jeddak_secure_model`: secure model helpers.
@@ -0,0 +1,52 @@
1
+ # Volcengine AICC SDK for Python
2
+
3
+ The official Python SDK for Volcengine AICC, providing confidential and secure
4
+ AI inference clients (chat, vision, video generation, ASR, TTS, etc.) with remote
5
+ attestation verification.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ pip install volcengine-python-sdk-aicc
11
+ ```
12
+
13
+ ## Quick start (recommended public import)
14
+
15
+ ```python
16
+ from volcengine.aicc import AICCClient, AICCConfig
17
+
18
+ client = AICCClient(config=AICCConfig(base_url="<YOUR_BASE_URL>", api_key="<YOUR_API_KEY>"))
19
+ ```
20
+
21
+ ## Legacy import compatibility
22
+
23
+ Existing customer code remains supported unchanged:
24
+
25
+ ```python
26
+ from bytedance.volcengine_aicc_sdk import AICCClient
27
+ from bytedance.volcengine_aicc_sdk.secure_http_client import AICCConfig, SecureHttpClient
28
+ ```
29
+
30
+ The distribution name is `volcengine-python-sdk-aicc`. `volcengine.aicc` is the recommended
31
+ public import facade; `bytedance.volcengine_aicc_sdk` remains the implementation and legacy
32
+ compatibility path. Both expose the same class objects.
33
+
34
+ ## Available clients
35
+
36
+ `AICCClient`, `AICCConfig`, `SecureHttpClient`, `AsyncSecureHttpClient`,
37
+ `AICCInference`, `ARKClient`, `DeepSeekClient`, `CUClient`, `AUCClient`,
38
+ `ASRClient`, `AsrWebSocketClient`, `SeedanceClient`, `SeedreamClient`,
39
+ `TTSClient`, and `TTSResponse`.
40
+
41
+ ## Project layout
42
+
43
+ - `volcengine.aicc`: recommended public import facade.
44
+ - `bytedance.volcengine_aicc_sdk`: implementation and legacy import path.
45
+ - `bytedance.jeddak_secure_channel`: confidential-computing secure channel and
46
+ remote attestation report verification.
47
+ - `bytedance.jeddak_secure_model`: secure model helpers.
48
+ - `bytedance.tks`: trusted key service helpers.
49
+
50
+ ## License
51
+
52
+ Licensed under the Apache License, Version 2.0. See [LICENSE](LICENSE).
@@ -4,7 +4,7 @@ __all__ = [
4
4
  "__version__",
5
5
  ]
6
6
 
7
- __version__ = "0.0.41"
7
+ __version__ = "0.0.42"
8
8
 
9
9
  from .aicc_client import AICCModelType
10
10
  from .aicc_client import AICCClient
@@ -1,5 +1,6 @@
1
1
  from bytedance.volcengine_aicc_sdk.seedance_inference_client import SeedanceClient
2
2
  from bytedance.volcengine_aicc_sdk.seedream_inference_client import SeedreamClient
3
+ from bytedance.volcengine_aicc_sdk.tts_client import TTSClient
3
4
  from typing import Any, List, Optional, Union
4
5
  """
5
6
  AICC访问客户端
@@ -200,6 +201,10 @@ class AICCClient:
200
201
  def get_secure_http_client(self) -> SecureHttpClient:
201
202
  return self.secure_http_client
202
203
 
204
+ def get_tts_client(self) -> TTSClient:
205
+ """复用当前客户端的安全传输与 Router 凭据创建 TTS 客户端。"""
206
+ return TTSClient(self.base_url, self.api_key, self.secure_http_client)
207
+
203
208
  def _init_secure_http_client(self) -> SecureHttpClient:
204
209
  if self.aicc_config:
205
210
  return SecureHttpClient(aicc_config=self.aicc_config)
@@ -316,6 +316,10 @@ class SecureHttpClient(httpx.Client):
316
316
 
317
317
  resp = super().send(request, *args, **kwargs)
318
318
 
319
+ if request.extensions.get("aicc_raw_response"):
320
+ resp.extensions["aicc_response_key"] = encrypt_key
321
+ return resp
322
+
319
323
  if 200 <= resp.status_code < 300:
320
324
  if encrypt_key:
321
325
  if not kwargs.get("stream", False):
@@ -0,0 +1,295 @@
1
+ import base64
2
+ import binascii
3
+ import json
4
+ from typing import Any, Dict, Iterator, Union
5
+
6
+ import httpx
7
+
8
+ from .common.error import AICCException
9
+ from .common.log import logger
10
+ from .secure_http_client import SecureHttpClient
11
+
12
+
13
+ _ENCRYPTION_FORMAT = "X-AICC-Encryption-Format"
14
+ _ENCRYPTION_CHUNK_SIZE = "X-AICC-Encryption-Chunk-Size"
15
+ _AUDIO_CONTENT_TYPE = "X-AICC-Audio-Content-Type"
16
+ _NDJSON_V1 = "jsc-ndjson-v1"
17
+ _MAX_ENCRYPTED_LINE = 64 * 1024
18
+ _MAX_AUDIO_BLOCK = 4096
19
+ _MAX_ERROR_BODY = 1024 * 1024
20
+ _MAX_SSE_EVENT_DATA = 1024 * 1024
21
+ _MAX_PLAINTEXT_SSE_LINE = 1024 * 1024
22
+
23
+
24
+ class TTSResponse:
25
+ """Streaming TTS response whose bytes are authenticated before delivery."""
26
+
27
+ def __init__(self, response: httpx.Response, audio: Iterator[bytes]):
28
+ self._response = response
29
+ self._audio = audio
30
+ self.headers = response.headers
31
+ self.status_code = response.status_code
32
+
33
+ def iter_bytes(self) -> Iterator[bytes]:
34
+ return self._audio
35
+
36
+ def read(self) -> bytes:
37
+ return b"".join(self._audio)
38
+
39
+ def close(self) -> None:
40
+ self._response.close()
41
+
42
+ def __enter__(self):
43
+ return self
44
+
45
+ def __exit__(self, exc_type, exc_value, traceback):
46
+ self.close()
47
+
48
+
49
+ class TTSClient:
50
+ """Client for the Router `/audio/speech` streaming API.
51
+
52
+ 流式 PCM 用法说明:
53
+ - 推荐生产用法: ``stream_format="audio"`` 搭配 ``stream=True`` 与
54
+ ``response_format="pcm"``, SDK 的 audio 分支按 HTTP chunked 原样透传
55
+ (底层走 ``response.iter_raw()``), 调用方可通过
56
+ :meth:`TTSResponse.iter_bytes` 边合成边消费 PCM 字节, 实现低延迟实时播放。
57
+ - ``stream_format="sse"`` 仅作为 OpenAI 契约兼容保留, Router 后端不再实现,
58
+ 调用会返回 502 BACKEND_PROTOCOL_ERROR, 不建议使用。
59
+ """
60
+
61
+ def __init__(self, base_url: str, api_key: str, secure_http_client: SecureHttpClient):
62
+ self.base_url = base_url
63
+ self.api_key = api_key
64
+ self.secure_http_client = secure_http_client
65
+
66
+ def synthesize(
67
+ self,
68
+ model: str,
69
+ input: str,
70
+ voice: Union[str, Dict[str, Any]],
71
+ stream_format: str = "audio",
72
+ **extra: Any,
73
+ ) -> TTSResponse:
74
+ """Start synthesis and return a streaming plaintext audio response."""
75
+ request_url = self.base_url.rstrip("/") + "/audio/speech"
76
+ logger.info(
77
+ f"TTS synthesis started, url={request_url}, model={model}, "
78
+ f"stream_format={stream_format}"
79
+ )
80
+ response = None
81
+ try:
82
+ payload = self._build_payload(model, input, voice, stream_format, extra)
83
+ request = self.secure_http_client.build_request(
84
+ "POST",
85
+ request_url,
86
+ headers={
87
+ "Authorization": f"Bearer {self.api_key}",
88
+ "Content-Type": "application/json",
89
+ },
90
+ json=payload,
91
+ )
92
+ request.extensions["aicc_raw_response"] = True
93
+ response = self.secure_http_client.send(request, stream=True)
94
+
95
+ if not 200 <= response.status_code < 300:
96
+ error_body = self._read_error_body(response)
97
+ raise AICCException(
98
+ f"TTS request failed: status={response.status_code}, response={error_body}"
99
+ )
100
+
101
+ response_key = response.extensions.get("aicc_response_key")
102
+ if self.secure_http_client.aicc_config is not None and response_key is None:
103
+ raise AICCException("TTS response decryption key is unavailable")
104
+
105
+ if stream_format == "sse":
106
+ audio = self._iter_sse_audio(response, response_key)
107
+ else:
108
+ audio = self._iter_audio(response, response_key)
109
+ if response_key is not None:
110
+ content_type = response.headers.get(_AUDIO_CONTENT_TYPE)
111
+ if content_type:
112
+ response.headers["Content-Type"] = content_type
113
+
114
+ logger.info(
115
+ f"TTS synthesis response ready, url={request_url}, "
116
+ f"status={response.status_code}, stream_format={stream_format}"
117
+ )
118
+ return TTSResponse(
119
+ response,
120
+ self._log_stream_failures(audio, response, request_url),
121
+ )
122
+ except Exception as exc:
123
+ if response is not None:
124
+ response.close()
125
+ logger.error(
126
+ f"TTS synthesis failed, url={request_url}, model={model}, "
127
+ f"stream_format={stream_format}, error={exc}"
128
+ )
129
+ raise
130
+
131
+ @staticmethod
132
+ def _build_payload(
133
+ model: str,
134
+ input: str,
135
+ voice: Union[str, Dict[str, Any]],
136
+ stream_format: str,
137
+ extra: Dict[str, Any],
138
+ ) -> Dict[str, Any]:
139
+ if not isinstance(model, str) or not model.strip():
140
+ raise ValueError("TTS model is required")
141
+ if not isinstance(input, str) or not input.strip():
142
+ raise ValueError("TTS input is required")
143
+ if isinstance(voice, str):
144
+ if not voice.strip():
145
+ raise ValueError("TTS voice is required")
146
+ elif (
147
+ not isinstance(voice, dict)
148
+ or not isinstance(voice.get("id"), str)
149
+ or not voice["id"].strip()
150
+ ):
151
+ raise ValueError(
152
+ "TTS voice must be a non-empty string or an object with a non-empty id"
153
+ )
154
+ if stream_format not in ("audio", "sse"):
155
+ raise ValueError("TTS stream_format must be audio or sse")
156
+
157
+ conflicts = {"model", "input", "voice", "stream_format"}.intersection(extra)
158
+ if conflicts:
159
+ raise ValueError(f"TTS extra fields conflict with declared fields: {sorted(conflicts)}")
160
+ return {
161
+ "model": model,
162
+ "input": input,
163
+ "voice": voice,
164
+ "stream_format": stream_format,
165
+ **extra,
166
+ }
167
+
168
+ @staticmethod
169
+ def _read_error_body(response: httpx.Response) -> str:
170
+ body = bytearray()
171
+ for chunk in response.iter_raw():
172
+ body.extend(chunk[: _MAX_ERROR_BODY - len(body)])
173
+ if len(body) == _MAX_ERROR_BODY:
174
+ break
175
+ return body.decode("utf-8", errors="replace")
176
+
177
+ @staticmethod
178
+ def _iter_lines(
179
+ response: httpx.Response,
180
+ require_lf: bool,
181
+ max_line: int = _MAX_ENCRYPTED_LINE,
182
+ ) -> Iterator[bytes]:
183
+ buffer = bytearray()
184
+ for chunk in response.iter_raw():
185
+ buffer.extend(chunk)
186
+ while True:
187
+ newline = buffer.find(b"\n")
188
+ if newline < 0:
189
+ if len(buffer) > max_line:
190
+ raise AICCException("TTS response line exceeds size limit")
191
+ break
192
+ if newline > max_line:
193
+ raise AICCException("TTS response line exceeds size limit")
194
+ line = bytes(buffer[:newline])
195
+ del buffer[: newline + 1]
196
+ yield line[:-1] if line.endswith(b"\r") else line
197
+ if buffer:
198
+ if require_lf:
199
+ raise AICCException("TTS truncated encrypted audio record")
200
+ yield bytes(buffer[:-1] if buffer.endswith(b"\r") else buffer)
201
+
202
+ def _iter_audio(self, response: httpx.Response, response_key: Any) -> Iterator[bytes]:
203
+ if response_key is None:
204
+ yield from response.iter_raw()
205
+ return
206
+ if response.headers.get(_ENCRYPTION_FORMAT) != _NDJSON_V1:
207
+ raise AICCException(f"TTS unexpected {_ENCRYPTION_FORMAT} header")
208
+ if response.headers.get(_ENCRYPTION_CHUNK_SIZE) != str(_MAX_AUDIO_BLOCK):
209
+ raise AICCException(f"TTS unexpected {_ENCRYPTION_CHUNK_SIZE} header")
210
+
211
+ for envelope in self._iter_lines(response, require_lf=True):
212
+ try:
213
+ plaintext = response_key.decrypt(envelope)
214
+ except Exception as exc:
215
+ raise AICCException(f"TTS failed to decrypt audio record: {exc}") from exc
216
+ if not 1 <= len(plaintext) <= _MAX_AUDIO_BLOCK:
217
+ raise AICCException(
218
+ f"TTS invalid decrypted audio block size {len(plaintext)}"
219
+ )
220
+ yield plaintext
221
+
222
+ def _iter_sse_audio(self, response: httpx.Response, response_key: Any) -> Iterator[bytes]:
223
+ event_name = ""
224
+ data_lines = []
225
+ data_size = 0
226
+
227
+ def dispatch():
228
+ nonlocal event_name, data_lines, data_size
229
+ if not data_lines:
230
+ event_name = ""
231
+ return None, False
232
+ try:
233
+ payload = json.loads("\n".join(data_lines))
234
+ except (TypeError, ValueError) as exc:
235
+ raise AICCException(f"TTS failed to decode SSE data: {exc}") from exc
236
+ current_event = event_name or payload.get("type", "")
237
+ event_name = ""
238
+ data_lines = []
239
+ data_size = 0
240
+ if current_event == "speech.audio.delta":
241
+ try:
242
+ return base64.b64decode(payload.get("audio", ""), validate=True), False
243
+ except (binascii.Error, ValueError) as exc:
244
+ raise AICCException(f"TTS failed to decode SSE audio: {exc}") from exc
245
+ if current_event == "speech.audio.done":
246
+ return None, True
247
+ if current_event == "error":
248
+ raise AICCException("TTS speech backend returned an SSE error")
249
+ return None, False
250
+
251
+ max_line = (
252
+ _MAX_ENCRYPTED_LINE
253
+ if response_key is not None
254
+ else _MAX_PLAINTEXT_SSE_LINE
255
+ )
256
+ lines = self._iter_lines(response, require_lf=False, max_line=max_line)
257
+ for raw_line in lines:
258
+ if response_key is not None:
259
+ try:
260
+ raw_line = response_key.decrypt(raw_line)
261
+ except Exception as exc:
262
+ raise AICCException(f"TTS failed to decrypt SSE line: {exc}") from exc
263
+ line = raw_line.decode("utf-8")
264
+ if not line:
265
+ audio, done = dispatch()
266
+ if audio is not None:
267
+ yield audio
268
+ if done:
269
+ response.close()
270
+ return
271
+ continue
272
+ if line.startswith("event:"):
273
+ event_name = line[6:].lstrip()
274
+ elif line.startswith("data:"):
275
+ value = line[5:].lstrip()
276
+ data_size += len(value.encode("utf-8")) + (1 if data_lines else 0)
277
+ if data_size > _MAX_SSE_EVENT_DATA:
278
+ raise AICCException("TTS SSE event data exceeds size limit")
279
+ data_lines.append(value)
280
+ audio, _ = dispatch()
281
+ if audio is not None:
282
+ yield audio
283
+
284
+ @staticmethod
285
+ def _log_stream_failures(
286
+ audio: Iterator[bytes],
287
+ response: httpx.Response,
288
+ request_url: str,
289
+ ) -> Iterator[bytes]:
290
+ try:
291
+ yield from audio
292
+ except Exception as exc:
293
+ response.close()
294
+ logger.error(f"TTS stream failed, url={request_url}, error={exc}")
295
+ raise
@@ -41,22 +41,17 @@ dependencies = [
41
41
  ]
42
42
 
43
43
  [project.urls]
44
- # 公开源码仓库地址待开源仓库确认后补充(例如 GitHub 仓库)。
45
44
  Homepage = "https://pypi.org/project/volcengine-python-sdk-aicc/"
46
45
 
47
- # Build configurations
48
-
49
46
  [build-system]
50
- requires = [
51
- "setuptools >= 75.3, < 100",
52
- ]
47
+ requires = ["setuptools >= 75.3, < 100"]
53
48
  build-backend = "setuptools.build_meta"
54
49
 
55
50
  [tool.setuptools.dynamic]
56
51
  version = { attr = "bytedance.volcengine_aicc_sdk.__version__" }
57
52
 
58
53
  [tool.setuptools.packages.find]
59
- include = ["bytedance*"]
54
+ include = ["bytedance*", "volcengine*"]
60
55
 
61
56
  [tool.setuptools]
62
57
  include-package-data = false
@@ -74,9 +69,7 @@ addopts = ["--import-mode=importlib"]
74
69
  testpaths = ["tests"]
75
70
 
76
71
  [tool.coverage.run]
77
- omit = [
78
- "example"
79
- ]
72
+ omit = ["example"]
80
73
 
81
74
  [tool.black]
82
75
  line-length = 100
@@ -22,14 +22,6 @@ sys.modules['bytedance.jeddak_secure_channel.crypto'] = mock_crypto
22
22
  mock_crypto.aes = mock.MagicMock()
23
23
  mock_crypto.rsa = mock.MagicMock()
24
24
 
25
- # 模拟 httpx 模块
26
- mock_httpx = mock.MagicMock()
27
- sys.modules['httpx'] = mock_httpx
28
- # 定义必要的类和方法
29
- mock_httpx.Client = mock.MagicMock()
30
- mock_httpx.Request = mock.MagicMock()
31
- mock_httpx.SyncByteStream = mock.MagicMock()
32
-
33
25
  # 模拟 volcenginesdkarkruntime 及其子模块
34
26
  sys.modules['volcenginesdkarkruntime'] = mock.MagicMock()
35
27
  sys.modules['volcenginesdkarkruntime._utils'] = mock.MagicMock()
@@ -25,14 +25,6 @@ sys.modules['cryptography.hazmat.primitives.asymmetric'] = mock.MagicMock()
25
25
  sys.modules['cryptography.hazmat.backends'] = mock.MagicMock()
26
26
  sys.modules['cryptography.hazmat.backends.openssl'] = mock.MagicMock()
27
27
 
28
- sys.modules['httpx'] = mock.MagicMock()
29
- sys.modules['httpx._types'] = mock.MagicMock()
30
-
31
- import urllib
32
- sys.modules['urllib.request'] = mock.MagicMock()
33
- if not hasattr(urllib, 'request'):
34
- urllib.request = sys.modules['urllib.request']
35
-
36
28
  sys.modules['bytedance.volcengine_aicc_sdk.common.aes'] = mock.MagicMock()
37
29
  sys.modules['bytedance.volcengine_aicc_sdk.common.log'] = mock.MagicMock()
38
30
 
@@ -25,15 +25,6 @@ sys.modules['cryptography.hazmat.primitives.asymmetric'] = mock.MagicMock()
25
25
  sys.modules['cryptography.hazmat.backends'] = mock.MagicMock()
26
26
  sys.modules['cryptography.hazmat.backends.openssl'] = mock.MagicMock()
27
27
 
28
- mock_httpx = mock.MagicMock()
29
- sys.modules['httpx'] = mock_httpx
30
- sys.modules['httpx._types'] = mock.MagicMock()
31
-
32
- import urllib
33
- sys.modules['urllib.request'] = mock.MagicMock()
34
- if not hasattr(urllib, 'request'):
35
- urllib.request = sys.modules['urllib.request']
36
-
37
28
  sys.modules['bytedance.volcengine_aicc_sdk.common.aes'] = mock.MagicMock()
38
29
  sys.modules['bytedance.volcengine_aicc_sdk.common.log'] = mock.MagicMock()
39
30