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.

@@ -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 deepcopy_minimal, with_sts_token, async_with_sts_token
34
- from ..._utils._key_agreement import aes_gcm_decrypt_base64_string, aes_gcm_decrypt_base64_list, decrypt_validate
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
+ )