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.
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/PKG-INFO +26 -9
- volcengine_python_sdk_aicc-0.0.42/README.md +52 -0
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/volcengine_aicc_sdk/__init__.py +1 -1
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/volcengine_aicc_sdk/aicc_client.py +5 -0
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/volcengine_aicc_sdk/secure_http_client.py +4 -0
- volcengine_python_sdk_aicc-0.0.42/bytedance/volcengine_aicc_sdk/tts_client.py +295 -0
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/pyproject.toml +3 -10
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/tests/test_secure_http_client.py +0 -8
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/tests/test_seedance_inference_client.py +0 -8
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/tests/test_tos_utils.py +0 -9
- volcengine_python_sdk_aicc-0.0.42/tests/test_tts_client.py +293 -0
- volcengine_python_sdk_aicc-0.0.42/tests/test_tts_streaming_pcm.py +45 -0
- volcengine_python_sdk_aicc-0.0.42/volcengine/aicc/__init__.py +47 -0
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/volcengine_python_sdk_aicc.egg-info/PKG-INFO +26 -9
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/volcengine_python_sdk_aicc.egg-info/SOURCES.txt +4 -0
- volcengine_python_sdk_aicc-0.0.42/volcengine_python_sdk_aicc.egg-info/top_level.txt +2 -0
- volcengine_python_sdk_aicc-0.0.41/README.md +0 -35
- volcengine_python_sdk_aicc-0.0.41/volcengine_python_sdk_aicc.egg-info/top_level.txt +0 -1
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/LICENSE +0 -0
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/__init__.py +0 -0
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/jeddak_secure_channel/__init__.py +0 -0
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/jeddak_secure_channel/client.py +0 -0
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/jeddak_secure_channel/config.py +0 -0
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/jeddak_secure_channel/consts.py +0 -0
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/jeddak_secure_channel/crypto/__init__.py +0 -0
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/jeddak_secure_channel/crypto/aes.py +0 -0
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/jeddak_secure_channel/crypto/envelope.py +0 -0
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/jeddak_secure_channel/crypto/rsa.py +0 -0
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/jeddak_secure_channel/error.py +0 -0
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/jeddak_secure_channel/log/__init__.py +0 -0
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/jeddak_secure_channel/log/config.py +0 -0
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/jeddak_secure_channel/log/logger.py +0 -0
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/jeddak_secure_channel/logger_factory.py +0 -0
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/jeddak_secure_channel/ra.py +0 -0
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/jeddak_secure_channel/server.py +0 -0
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/jeddak_secure_channel/service/__init__.py +0 -0
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/jeddak_secure_channel/service/eps.py +0 -0
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/jeddak_secure_channel/service/eps_models.py +0 -0
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/jeddak_secure_channel/service/ras.py +0 -0
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/jeddak_secure_channel/service/tks.py +0 -0
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/jeddak_secure_channel/service/tks_models.py +0 -0
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/jeddak_secure_channel/utils.py +0 -0
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/jeddak_secure_model/model_automation_tool.py +0 -0
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/jeddak_secure_model/model_encryption.py +0 -0
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/jeddak_secure_model/model_hub_admin.py +0 -0
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/jeddak_secure_model/pcc_utils.py +0 -0
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/jeddak_secure_model/rds_utils.py +0 -0
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/tks/attester.py +0 -0
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/tks/client.py +0 -0
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/tks/consts.py +0 -0
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/tks/crypto/aes.py +0 -0
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/tks/crypto/ec.py +0 -0
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/tks/exceptions.py +0 -0
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/volcengine_aicc_sdk/aicc_inference.py +0 -0
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/volcengine_aicc_sdk/ark_inference_client.py +0 -0
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/volcengine_aicc_sdk/asr_inference_client.py +0 -0
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/volcengine_aicc_sdk/asr_websocket_client.py +0 -0
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/volcengine_aicc_sdk/auc_inference_client.py +0 -0
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/volcengine_aicc_sdk/common/__init__.py +0 -0
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/volcengine_aicc_sdk/common/aes.py +0 -0
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/volcengine_aicc_sdk/common/constant.py +0 -0
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/volcengine_aicc_sdk/common/error.py +0 -0
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/volcengine_aicc_sdk/common/log/__init__.py +0 -0
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/volcengine_aicc_sdk/common/log/config.py +0 -0
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/volcengine_aicc_sdk/common/log/logger.py +0 -0
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/volcengine_aicc_sdk/common/tos_utils.py +0 -0
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/volcengine_aicc_sdk/common/utils.py +0 -0
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/volcengine_aicc_sdk/cu_inference_client.py +0 -0
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/volcengine_aicc_sdk/deepseek_inference_client.py +0 -0
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/volcengine_aicc_sdk/seedance_inference_client.py +0 -0
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/bytedance/volcengine_aicc_sdk/seedream_inference_client.py +0 -0
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/setup.cfg +0 -0
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/setup.py +0 -0
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/tests/test_aicc_client.py +0 -0
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/tests/test_aicc_inference.py +0 -0
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/tests/test_ark_inference_client.py +0 -0
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/tests/test_asr_inference_client.py +0 -0
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/tests/test_asr_websocket_client.py +0 -0
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/tests/test_auc_inference_client.py +0 -0
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/tests/test_cu_inference_client.py +0 -0
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/tests/test_deepseek_inference_client.py +0 -0
- {volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/tests/test_seedream_inference_client.py +0 -0
- {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
- {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.
|
|
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
|
-
|
|
58
|
-
package is `bytedance.volcengine_aicc_sdk`.
|
|
59
|
-
|
|
60
|
-
## Quick start
|
|
57
|
+
## Quick start (recommended public import)
|
|
61
58
|
|
|
62
59
|
```python
|
|
63
|
-
from
|
|
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
|
-
- `
|
|
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).
|
|
@@ -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
|
|
{volcengine_python_sdk_aicc-0.0.41 → volcengine_python_sdk_aicc-0.0.42}/tests/test_tos_utils.py
RENAMED
|
@@ -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
|
|