volcengine-python-sdk 4.0.28__py2.py3-none-any.whl → 4.0.29__py2.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.
Potentially problematic release.
This version of volcengine-python-sdk might be problematic. Click here for more details.
- {volcengine_python_sdk-4.0.28.dist-info → volcengine_python_sdk-4.0.29.dist-info}/METADATA +1 -1
- {volcengine_python_sdk-4.0.28.dist-info → volcengine_python_sdk-4.0.29.dist-info}/RECORD +19 -17
- volcenginesdkarkruntime/_client.py +7 -6
- volcenginesdkarkruntime/_utils/_key_agreement.py +18 -14
- volcenginesdkarkruntime/resources/batch_chat/completions.py +10 -141
- volcenginesdkarkruntime/resources/beta/chat/completions.py +10 -0
- volcenginesdkarkruntime/resources/chat/completions.py +9 -206
- volcenginesdkarkruntime/resources/encryption.py +209 -0
- volcenginesdkarkruntime/resources/images/images.py +72 -55
- volcenginesdkarkruntime/types/content_generation/content_generation_task.py +15 -0
- volcenginesdkarkruntime/types/images/__init__.py +2 -2
- volcenginesdkarkruntime/types/images/images.py +7 -1
- volcenginesdkarkruntime/types/shared/reasoning_effort.py +17 -0
- volcenginesdkcore/api_client.py +1 -1
- volcenginesdkcore/configuration.py +1 -1
- {volcengine_python_sdk-4.0.28.dist-info → volcengine_python_sdk-4.0.29.dist-info}/WHEEL +0 -0
- {volcengine_python_sdk-4.0.28.dist-info → volcengine_python_sdk-4.0.29.dist-info}/licenses/LICENSE.txt +0 -0
- {volcengine_python_sdk-4.0.28.dist-info → volcengine_python_sdk-4.0.29.dist-info}/licenses/NOTICE.md +0 -0
- {volcengine_python_sdk-4.0.28.dist-info → volcengine_python_sdk-4.0.29.dist-info}/top_level.txt +0 -0
|
@@ -23,19 +23,16 @@ from typing import (
|
|
|
23
23
|
AsyncIterator,
|
|
24
24
|
)
|
|
25
25
|
|
|
26
|
-
import os
|
|
27
|
-
import json
|
|
28
26
|
import httpx
|
|
29
|
-
import warnings
|
|
30
27
|
from typing_extensions import Literal
|
|
31
28
|
|
|
32
29
|
from ..._types import Body, Query, Headers
|
|
33
|
-
from ..._utils._utils import
|
|
34
|
-
from
|
|
30
|
+
from ..._utils._utils import with_sts_token, async_with_sts_token
|
|
31
|
+
from ..encryption import with_e2e_encryption, async_with_e2e_encryption
|
|
35
32
|
from ..._base_client import make_request_options
|
|
36
33
|
from ..._resource import SyncAPIResource, AsyncAPIResource
|
|
37
34
|
from ..._compat import cached_property
|
|
38
|
-
|
|
35
|
+
from ...types.shared.reasoning_effort import ReasoningEffort
|
|
39
36
|
from ..._response import (
|
|
40
37
|
to_raw_response_wrapper,
|
|
41
38
|
async_to_raw_response_wrapper,
|
|
@@ -52,46 +49,10 @@ from ...types.chat import (
|
|
|
52
49
|
ChatCompletionToolParam,
|
|
53
50
|
ChatCompletionToolChoiceOptionParam,
|
|
54
51
|
)
|
|
55
|
-
from ..._constants import ARK_E2E_ENCRYPTION_HEADER
|
|
56
52
|
|
|
57
53
|
__all__ = ["Completions", "AsyncCompletions"]
|
|
58
54
|
|
|
59
55
|
|
|
60
|
-
def _process_messages(
|
|
61
|
-
messages: Iterable[ChatCompletionMessageParam], f: Callable[[str], str]
|
|
62
|
-
):
|
|
63
|
-
for message in messages:
|
|
64
|
-
if message.get("content", None) is not None:
|
|
65
|
-
current_content = message.get("content")
|
|
66
|
-
if isinstance(current_content, str):
|
|
67
|
-
message["content"] = f(current_content)
|
|
68
|
-
elif isinstance(current_content, Iterable):
|
|
69
|
-
for part in current_content:
|
|
70
|
-
if part.get("type", None) == "text":
|
|
71
|
-
part["text"] = f(part["text"])
|
|
72
|
-
elif part.get("type", None) == "image_url":
|
|
73
|
-
if part["image_url"]["url"].startswith("data:"):
|
|
74
|
-
part["image_url"]["url"] = f(
|
|
75
|
-
part["image_url"]["url"])
|
|
76
|
-
else:
|
|
77
|
-
warnings.warn(
|
|
78
|
-
"encryption is not supported for image url, "
|
|
79
|
-
"please use base64 image if you want encryption"
|
|
80
|
-
)
|
|
81
|
-
else:
|
|
82
|
-
raise TypeError(
|
|
83
|
-
"encryption is not supported for content type {}".format(
|
|
84
|
-
type(part)
|
|
85
|
-
)
|
|
86
|
-
)
|
|
87
|
-
else:
|
|
88
|
-
raise TypeError(
|
|
89
|
-
"encryption is not supported for content type {}".format(
|
|
90
|
-
type(message.get("content"))
|
|
91
|
-
)
|
|
92
|
-
)
|
|
93
|
-
|
|
94
|
-
|
|
95
56
|
class Completions(SyncAPIResource):
|
|
96
57
|
@cached_property
|
|
97
58
|
def with_raw_response(self) -> CompletionsWithRawResponse:
|
|
@@ -101,70 +62,8 @@ class Completions(SyncAPIResource):
|
|
|
101
62
|
def with_streaming_response(self) -> CompletionsWithStreamingResponse:
|
|
102
63
|
return CompletionsWithStreamingResponse(self)
|
|
103
64
|
|
|
104
|
-
def _encrypt(
|
|
105
|
-
self,
|
|
106
|
-
model: str,
|
|
107
|
-
messages: Iterable[ChatCompletionMessageParam],
|
|
108
|
-
extra_headers: Headers,
|
|
109
|
-
) -> tuple[bytes, bytes, str, str]:
|
|
110
|
-
client, ring_id, key_id = self._client._get_endpoint_certificate(model)
|
|
111
|
-
_crypto_key, _crypto_nonce, session_token = client.generate_ecies_key_pair()
|
|
112
|
-
extra_headers["X-Session-Token"] = session_token
|
|
113
|
-
_process_messages(
|
|
114
|
-
messages,
|
|
115
|
-
lambda x: client.encrypt_string_with_key(
|
|
116
|
-
_crypto_key, _crypto_nonce, x),
|
|
117
|
-
)
|
|
118
|
-
return _crypto_key, _crypto_nonce, ring_id, key_id
|
|
119
|
-
|
|
120
|
-
def _decrypt_chunk(
|
|
121
|
-
self, key: bytes, nonce: bytes, resp: Stream[ChatCompletionChunk]
|
|
122
|
-
) -> Iterator[ChatCompletionChunk]:
|
|
123
|
-
for chunk in resp:
|
|
124
|
-
if chunk.choices is not None:
|
|
125
|
-
for index, choice in enumerate(chunk.choices):
|
|
126
|
-
if (
|
|
127
|
-
choice.delta is not None and choice.delta.content is not None
|
|
128
|
-
and choice.finish_reason != 'content_filter'
|
|
129
|
-
):
|
|
130
|
-
choice.delta.content = aes_gcm_decrypt_base64_string(
|
|
131
|
-
key, nonce, choice.delta.content
|
|
132
|
-
)
|
|
133
|
-
chunk.choices[index] = choice
|
|
134
|
-
yield chunk
|
|
135
|
-
|
|
136
|
-
def _decrypt(
|
|
137
|
-
self,
|
|
138
|
-
key: bytes,
|
|
139
|
-
nonce: bytes,
|
|
140
|
-
resp: ChatCompletion | Stream[ChatCompletionChunk],
|
|
141
|
-
) -> ChatCompletion | Stream[ChatCompletionChunk]:
|
|
142
|
-
if isinstance(resp, ChatCompletion):
|
|
143
|
-
if resp.choices is not None:
|
|
144
|
-
for index, choice in enumerate(resp.choices):
|
|
145
|
-
if (
|
|
146
|
-
choice.message is not None and choice.finish_reason != 'content_filter'
|
|
147
|
-
and choice.message.content is not None
|
|
148
|
-
):
|
|
149
|
-
try:
|
|
150
|
-
content = aes_gcm_decrypt_base64_string(
|
|
151
|
-
key, nonce, choice.message.content
|
|
152
|
-
)
|
|
153
|
-
except Exception:
|
|
154
|
-
content = ''
|
|
155
|
-
if content == '' or not decrypt_validate(choice.message.content):
|
|
156
|
-
content = aes_gcm_decrypt_base64_list(
|
|
157
|
-
key, nonce, choice.message.content
|
|
158
|
-
)
|
|
159
|
-
choice.message.content = content
|
|
160
|
-
resp.choices[index] = choice
|
|
161
|
-
return resp
|
|
162
|
-
else:
|
|
163
|
-
return Stream._make_stream_from_iterator(
|
|
164
|
-
self._decrypt_chunk(key, nonce, resp)
|
|
165
|
-
)
|
|
166
|
-
|
|
167
65
|
@with_sts_token
|
|
66
|
+
@with_e2e_encryption
|
|
168
67
|
def create(
|
|
169
68
|
self,
|
|
170
69
|
*,
|
|
@@ -191,29 +90,13 @@ class Completions(SyncAPIResource):
|
|
|
191
90
|
response_format: completion_create_params.ResponseFormat | None = None,
|
|
192
91
|
thinking: completion_create_params.Thinking | None = None,
|
|
193
92
|
max_completion_tokens: Optional[int] | None = None,
|
|
93
|
+
reasoning_effort: Optional[ReasoningEffort] | None = None,
|
|
194
94
|
user: str | None = None,
|
|
195
95
|
extra_headers: Headers | None = None,
|
|
196
96
|
extra_query: Query | None = None,
|
|
197
97
|
extra_body: Body | None = None,
|
|
198
98
|
timeout: float | httpx.Timeout | None = None,
|
|
199
99
|
) -> ChatCompletion | Stream[ChatCompletionChunk]:
|
|
200
|
-
is_encrypt = False
|
|
201
|
-
if (
|
|
202
|
-
extra_headers is not None
|
|
203
|
-
and extra_headers.get(ARK_E2E_ENCRYPTION_HEADER, None) == "true"
|
|
204
|
-
):
|
|
205
|
-
is_encrypt = True
|
|
206
|
-
messages = deepcopy_minimal(messages)
|
|
207
|
-
e2e_key, e2e_nonce, ring_id, key_id = self._encrypt(
|
|
208
|
-
model, messages, extra_headers)
|
|
209
|
-
if os.environ.get("VOLC_ARK_ENCRYPTION") == "AICC":
|
|
210
|
-
info = {
|
|
211
|
-
'Version': 'AICCv0.1',
|
|
212
|
-
'RingID': ring_id,
|
|
213
|
-
'KeyID': key_id,
|
|
214
|
-
}
|
|
215
|
-
extra_headers["X-Encrypt-Info"] = json.dumps(info)
|
|
216
|
-
|
|
217
100
|
resp = self._post(
|
|
218
101
|
"/chat/completions",
|
|
219
102
|
body={
|
|
@@ -241,6 +124,7 @@ class Completions(SyncAPIResource):
|
|
|
241
124
|
"response_format": response_format,
|
|
242
125
|
"thinking": thinking,
|
|
243
126
|
"max_completion_tokens": max_completion_tokens,
|
|
127
|
+
"reasoning_effort": reasoning_effort,
|
|
244
128
|
},
|
|
245
129
|
options=make_request_options(
|
|
246
130
|
extra_headers=extra_headers,
|
|
@@ -252,9 +136,6 @@ class Completions(SyncAPIResource):
|
|
|
252
136
|
stream=stream or False,
|
|
253
137
|
stream_cls=Stream[ChatCompletionChunk],
|
|
254
138
|
)
|
|
255
|
-
|
|
256
|
-
if is_encrypt:
|
|
257
|
-
resp = self._decrypt(e2e_key, e2e_nonce, resp)
|
|
258
139
|
return resp
|
|
259
140
|
|
|
260
141
|
|
|
@@ -267,70 +148,8 @@ class AsyncCompletions(AsyncAPIResource):
|
|
|
267
148
|
def with_streaming_response(self) -> AsyncCompletionsWithStreamingResponse:
|
|
268
149
|
return AsyncCompletionsWithStreamingResponse(self)
|
|
269
150
|
|
|
270
|
-
def _encrypt(
|
|
271
|
-
self,
|
|
272
|
-
model: str,
|
|
273
|
-
messages: Iterable[ChatCompletionMessageParam],
|
|
274
|
-
extra_headers: Headers,
|
|
275
|
-
) -> tuple[bytes, bytes, str, str]:
|
|
276
|
-
client, ring_id, key_id = self._client._get_endpoint_certificate(model)
|
|
277
|
-
_crypto_key, _crypto_nonce, session_token = client.generate_ecies_key_pair()
|
|
278
|
-
extra_headers["X-Session-Token"] = session_token
|
|
279
|
-
_process_messages(
|
|
280
|
-
messages,
|
|
281
|
-
lambda x: client.encrypt_string_with_key(
|
|
282
|
-
_crypto_key, _crypto_nonce, x),
|
|
283
|
-
)
|
|
284
|
-
return _crypto_key, _crypto_nonce, ring_id, key_id
|
|
285
|
-
|
|
286
|
-
async def _decrypt_chunk(
|
|
287
|
-
self, key: bytes, nonce: bytes, resp: AsyncStream[ChatCompletionChunk]
|
|
288
|
-
) -> AsyncIterator[ChatCompletionChunk]:
|
|
289
|
-
async for chunk in resp:
|
|
290
|
-
if chunk.choices is not None:
|
|
291
|
-
for index, choice in enumerate(chunk.choices):
|
|
292
|
-
if (
|
|
293
|
-
choice.delta is not None and choice.delta.content is not None
|
|
294
|
-
and choice.finish_reason != 'content_filter'
|
|
295
|
-
):
|
|
296
|
-
choice.delta.content = aes_gcm_decrypt_base64_string(
|
|
297
|
-
key, nonce, choice.delta.content
|
|
298
|
-
)
|
|
299
|
-
chunk.choices[index] = choice
|
|
300
|
-
yield chunk
|
|
301
|
-
|
|
302
|
-
async def _decrypt(
|
|
303
|
-
self,
|
|
304
|
-
key: bytes,
|
|
305
|
-
nonce: bytes,
|
|
306
|
-
resp: ChatCompletion | AsyncStream[ChatCompletionChunk],
|
|
307
|
-
) -> ChatCompletion | AsyncStream[ChatCompletionChunk]:
|
|
308
|
-
if isinstance(resp, ChatCompletion):
|
|
309
|
-
if resp.choices is not None:
|
|
310
|
-
for index, choice in enumerate(resp.choices):
|
|
311
|
-
if (
|
|
312
|
-
choice.message is not None and choice.finish_reason != 'content_filter'
|
|
313
|
-
and choice.message.content is not None
|
|
314
|
-
):
|
|
315
|
-
try:
|
|
316
|
-
content = aes_gcm_decrypt_base64_string(
|
|
317
|
-
key, nonce, choice.message.content
|
|
318
|
-
)
|
|
319
|
-
except Exception:
|
|
320
|
-
content = ''
|
|
321
|
-
if content == '' or not decrypt_validate(choice.message.content):
|
|
322
|
-
content = aes_gcm_decrypt_base64_list(
|
|
323
|
-
key, nonce, choice.message.content
|
|
324
|
-
)
|
|
325
|
-
choice.message.content = content
|
|
326
|
-
resp.choices[index] = choice
|
|
327
|
-
return resp
|
|
328
|
-
else:
|
|
329
|
-
return AsyncStream._make_stream_from_iterator(
|
|
330
|
-
self._decrypt_chunk(key, nonce, resp)
|
|
331
|
-
)
|
|
332
|
-
|
|
333
151
|
@async_with_sts_token
|
|
152
|
+
@async_with_e2e_encryption
|
|
334
153
|
async def create(
|
|
335
154
|
self,
|
|
336
155
|
*,
|
|
@@ -358,27 +177,12 @@ class AsyncCompletions(AsyncAPIResource):
|
|
|
358
177
|
response_format: completion_create_params.ResponseFormat | None = None,
|
|
359
178
|
thinking: completion_create_params.Thinking | None = None,
|
|
360
179
|
max_completion_tokens: Optional[int] | None = None,
|
|
180
|
+
reasoning_effort: Optional[ReasoningEffort] | None = None,
|
|
361
181
|
extra_headers: Headers | None = None,
|
|
362
182
|
extra_query: Query | None = None,
|
|
363
183
|
extra_body: Body | None = None,
|
|
364
184
|
timeout: float | httpx.Timeout | None = None,
|
|
365
185
|
) -> ChatCompletion | AsyncStream[ChatCompletionChunk]:
|
|
366
|
-
is_encrypt = False
|
|
367
|
-
if (
|
|
368
|
-
extra_headers is not None
|
|
369
|
-
and extra_headers.get(ARK_E2E_ENCRYPTION_HEADER, None) == "true"
|
|
370
|
-
):
|
|
371
|
-
is_encrypt = True
|
|
372
|
-
messages = deepcopy_minimal(messages)
|
|
373
|
-
e2e_key, e2e_nonce, ring_id, key_id = self._encrypt(
|
|
374
|
-
model, messages, extra_headers)
|
|
375
|
-
if os.environ.get("VOLC_ARK_ENCRYPTION") == "AICC":
|
|
376
|
-
info = {
|
|
377
|
-
'Version': 'AICCv0.1',
|
|
378
|
-
'RingID': ring_id,
|
|
379
|
-
'KeyID': key_id,
|
|
380
|
-
}
|
|
381
|
-
extra_headers["X-Encrypt-Info"] = json.dumps(info)
|
|
382
186
|
|
|
383
187
|
resp = await self._post(
|
|
384
188
|
"/chat/completions",
|
|
@@ -407,6 +211,7 @@ class AsyncCompletions(AsyncAPIResource):
|
|
|
407
211
|
"response_format": response_format,
|
|
408
212
|
"thinking": thinking,
|
|
409
213
|
"max_completion_tokens": max_completion_tokens,
|
|
214
|
+
"reasoning_effort": reasoning_effort,
|
|
410
215
|
},
|
|
411
216
|
options=make_request_options(
|
|
412
217
|
extra_headers=extra_headers,
|
|
@@ -419,8 +224,6 @@ class AsyncCompletions(AsyncAPIResource):
|
|
|
419
224
|
stream_cls=AsyncStream[ChatCompletionChunk],
|
|
420
225
|
)
|
|
421
226
|
|
|
422
|
-
if is_encrypt:
|
|
423
|
-
resp = await self._decrypt(e2e_key, e2e_nonce, resp)
|
|
424
227
|
return resp
|
|
425
228
|
|
|
426
229
|
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from .._constants import ARK_E2E_ENCRYPTION_HEADER
|
|
3
|
+
from .._streaming import Stream, AsyncStream
|
|
4
|
+
from ..types.chat import (
|
|
5
|
+
ChatCompletion,
|
|
6
|
+
ChatCompletionChunk,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
import json
|
|
11
|
+
import warnings
|
|
12
|
+
from copy import deepcopy
|
|
13
|
+
from typing import (
|
|
14
|
+
Union,
|
|
15
|
+
Iterable,
|
|
16
|
+
Callable,
|
|
17
|
+
Iterator,
|
|
18
|
+
AsyncIterator,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
from .._utils._key_agreement import aes_gcm_decrypt_base64_string, aes_gcm_decrypt_base64_list, decrypt_validate
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _decrypt_chunk(
|
|
25
|
+
key: bytes, nonce: bytes, resp: Stream[ChatCompletionChunk]
|
|
26
|
+
) -> Iterator[ChatCompletionChunk]:
|
|
27
|
+
for chunk in resp:
|
|
28
|
+
if chunk.choices is not None:
|
|
29
|
+
for index, choice in enumerate(chunk.choices):
|
|
30
|
+
if (
|
|
31
|
+
choice.delta is not None and choice.delta.content is not None
|
|
32
|
+
and choice.finish_reason != 'content_filter'
|
|
33
|
+
):
|
|
34
|
+
choice.delta.content = aes_gcm_decrypt_base64_string(
|
|
35
|
+
key, nonce, choice.delta.content
|
|
36
|
+
)
|
|
37
|
+
chunk.choices[index] = choice
|
|
38
|
+
yield chunk
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
async def _async_decrypt_chunk(
|
|
42
|
+
key: bytes, nonce: bytes, resp: AsyncStream[ChatCompletionChunk]
|
|
43
|
+
) -> AsyncIterator[ChatCompletionChunk]:
|
|
44
|
+
async for chunk in resp:
|
|
45
|
+
if chunk.choices is not None:
|
|
46
|
+
for index, choice in enumerate(chunk.choices):
|
|
47
|
+
if (
|
|
48
|
+
choice.delta is not None and choice.delta.content is not None
|
|
49
|
+
and choice.finish_reason != 'content_filter'
|
|
50
|
+
):
|
|
51
|
+
choice.delta.content = aes_gcm_decrypt_base64_string(
|
|
52
|
+
key, nonce, choice.delta.content
|
|
53
|
+
)
|
|
54
|
+
chunk.choices[index] = choice
|
|
55
|
+
yield chunk
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _decrypt(
|
|
59
|
+
key: bytes,
|
|
60
|
+
nonce: bytes,
|
|
61
|
+
resp: Union[ChatCompletion, Stream[ChatCompletionChunk]]
|
|
62
|
+
) -> ChatCompletion | Stream[ChatCompletionChunk]:
|
|
63
|
+
if isinstance(resp, ChatCompletion):
|
|
64
|
+
if resp.choices is not None:
|
|
65
|
+
for index, choice in enumerate(resp.choices):
|
|
66
|
+
if (
|
|
67
|
+
choice.message is not None and choice.finish_reason != 'content_filter'
|
|
68
|
+
and choice.message.content is not None
|
|
69
|
+
):
|
|
70
|
+
try:
|
|
71
|
+
content = aes_gcm_decrypt_base64_string(
|
|
72
|
+
key, nonce, choice.message.content
|
|
73
|
+
)
|
|
74
|
+
except Exception:
|
|
75
|
+
content = ''
|
|
76
|
+
if content == '' or not decrypt_validate(choice.message.content):
|
|
77
|
+
content = aes_gcm_decrypt_base64_list(
|
|
78
|
+
key, nonce, choice.message.content
|
|
79
|
+
)
|
|
80
|
+
choice.message.content = content
|
|
81
|
+
resp.choices[index] = choice
|
|
82
|
+
return resp
|
|
83
|
+
else:
|
|
84
|
+
return Stream._make_stream_from_iterator(
|
|
85
|
+
_decrypt_chunk(key, nonce, resp)
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
async def _async_decrypt(
|
|
90
|
+
key: bytes,
|
|
91
|
+
nonce: bytes,
|
|
92
|
+
resp: ChatCompletion | AsyncStream[ChatCompletionChunk],
|
|
93
|
+
) -> ChatCompletion | AsyncStream[ChatCompletionChunk]:
|
|
94
|
+
if isinstance(resp, ChatCompletion):
|
|
95
|
+
if resp.choices is not None:
|
|
96
|
+
for index, choice in enumerate(resp.choices):
|
|
97
|
+
if (
|
|
98
|
+
choice.message is not None and choice.finish_reason != 'content_filter'
|
|
99
|
+
and choice.message.content is not None
|
|
100
|
+
):
|
|
101
|
+
try:
|
|
102
|
+
content = aes_gcm_decrypt_base64_string(
|
|
103
|
+
key, nonce, choice.message.content
|
|
104
|
+
)
|
|
105
|
+
except Exception:
|
|
106
|
+
content = ''
|
|
107
|
+
if content == '' or not decrypt_validate(choice.message.content):
|
|
108
|
+
content = aes_gcm_decrypt_base64_list(
|
|
109
|
+
key, nonce, choice.message.content
|
|
110
|
+
)
|
|
111
|
+
choice.message.content = content
|
|
112
|
+
resp.choices[index] = choice
|
|
113
|
+
return resp
|
|
114
|
+
else:
|
|
115
|
+
return AsyncStream._make_stream_from_iterator(
|
|
116
|
+
_async_decrypt_chunk(key, nonce, resp)
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def with_e2e_encryption(func):
|
|
121
|
+
def wrapper(*args, **kwargs):
|
|
122
|
+
is_encrypt, _crypto_key, _crypto_nonce = _content_encryption(
|
|
123
|
+
args, kwargs)
|
|
124
|
+
resp = func(*args, **kwargs)
|
|
125
|
+
if is_encrypt:
|
|
126
|
+
resp = _decrypt(_crypto_key, _crypto_nonce, resp)
|
|
127
|
+
return resp
|
|
128
|
+
|
|
129
|
+
return wrapper
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def async_with_e2e_encryption(func):
|
|
133
|
+
async def wrapper(*args, **kwargs):
|
|
134
|
+
is_encrypt, _crypto_key, _crypto_nonce = _content_encryption(
|
|
135
|
+
args, kwargs)
|
|
136
|
+
resp = await func(*args, **kwargs)
|
|
137
|
+
if is_encrypt:
|
|
138
|
+
resp = await _async_decrypt(_crypto_key, _crypto_nonce, resp)
|
|
139
|
+
return resp
|
|
140
|
+
|
|
141
|
+
return wrapper
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _content_encryption(args, kwargs):
|
|
145
|
+
assert len(args) > 0
|
|
146
|
+
extra_headers = (
|
|
147
|
+
kwargs.get("extra_headers") if kwargs.get("extra_headers") else {}
|
|
148
|
+
)
|
|
149
|
+
if (
|
|
150
|
+
extra_headers is not None
|
|
151
|
+
and extra_headers.get(ARK_E2E_ENCRYPTION_HEADER, None) == "true"
|
|
152
|
+
):
|
|
153
|
+
model: str = kwargs.get("model", "")
|
|
154
|
+
messages = deepcopy(kwargs["messages"])
|
|
155
|
+
ark_client = args[0]._client
|
|
156
|
+
client, ring_id, key_id, exp_time = ark_client._get_endpoint_certificate(
|
|
157
|
+
model)
|
|
158
|
+
_crypto_key, _crypto_nonce, session_token = client.generate_ecies_key_pair()
|
|
159
|
+
extra_headers["X-Session-Token"] = session_token
|
|
160
|
+
_process_messages(
|
|
161
|
+
messages,
|
|
162
|
+
lambda x: client.encrypt_string_with_key(
|
|
163
|
+
_crypto_key, _crypto_nonce, x),
|
|
164
|
+
)
|
|
165
|
+
info = {"ExpireTime": exp_time}
|
|
166
|
+
if os.environ.get("VOLC_ARK_ENCRYPTION") == "AICC":
|
|
167
|
+
info["Version"] = 'AICCv0.1'
|
|
168
|
+
info["KeyID"] = key_id
|
|
169
|
+
info["RingID"] = ring_id
|
|
170
|
+
extra_headers["X-Encrypt-Info"] = json.dumps(info)
|
|
171
|
+
kwargs["extra_headers"] = {**extra_headers}
|
|
172
|
+
kwargs["messages"] = messages
|
|
173
|
+
return True, _crypto_key, _crypto_nonce
|
|
174
|
+
return False, None, None
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _process_messages(
|
|
178
|
+
messages, f: Callable[[str], str]
|
|
179
|
+
):
|
|
180
|
+
for message in messages:
|
|
181
|
+
if message.get("content", None) is not None:
|
|
182
|
+
current_content = message.get("content")
|
|
183
|
+
if isinstance(current_content, str):
|
|
184
|
+
message["content"] = f(current_content)
|
|
185
|
+
elif isinstance(current_content, Iterable):
|
|
186
|
+
for part in current_content:
|
|
187
|
+
if part.get("type", None) == "text":
|
|
188
|
+
part["text"] = f(part["text"])
|
|
189
|
+
elif part.get("type", None) == "image_url":
|
|
190
|
+
if part["image_url"]["url"].startswith("data:"):
|
|
191
|
+
part["image_url"]["url"] = f(
|
|
192
|
+
part["image_url"]["url"])
|
|
193
|
+
else:
|
|
194
|
+
warnings.warn(
|
|
195
|
+
"encryption is not supported for image url, "
|
|
196
|
+
"please use base64 image if you want encryption"
|
|
197
|
+
)
|
|
198
|
+
else:
|
|
199
|
+
raise TypeError(
|
|
200
|
+
"encryption is not supported for content type {}".format(
|
|
201
|
+
type(part)
|
|
202
|
+
)
|
|
203
|
+
)
|
|
204
|
+
else:
|
|
205
|
+
raise TypeError(
|
|
206
|
+
"encryption is not supported for content type {}".format(
|
|
207
|
+
type(message.get("content"))
|
|
208
|
+
)
|
|
209
|
+
)
|