synapsai-python 0.1.0__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.
- synapsai/__init__.py +16 -0
- synapsai/client.py +628 -0
- synapsai/exceptions.py +64 -0
- synapsai/logging.py +70 -0
- synapsai/processing.py +100 -0
- synapsai/resources/__init__.py +59 -0
- synapsai/resources/audio.py +495 -0
- synapsai/resources/chat.py +199 -0
- synapsai/resources/classifications.py +454 -0
- synapsai/resources/completions.py +162 -0
- synapsai/resources/embeddings.py +207 -0
- synapsai/resources/feature_extraction.py +80 -0
- synapsai/resources/fill_mask.py +88 -0
- synapsai/resources/images.py +560 -0
- synapsai/resources/models.py +80 -0
- synapsai/resources/question_answering.py +257 -0
- synapsai/resources/rerank.py +90 -0
- synapsai/resources/videos.py +303 -0
- synapsai/types/__init__.py +144 -0
- synapsai/types/audio.py +177 -0
- synapsai/types/classifications.py +228 -0
- synapsai/types/common.py +56 -0
- synapsai/types/completion.py +244 -0
- synapsai/types/embeddings.py +68 -0
- synapsai/types/feature_extraction.py +31 -0
- synapsai/types/fill_mask.py +38 -0
- synapsai/types/images.py +172 -0
- synapsai/types/models.py +28 -0
- synapsai/types/question_answering.py +127 -0
- synapsai/types/rerank.py +40 -0
- synapsai/types/videos.py +92 -0
- synapsai/utils.py +32 -0
- synapsai_python-0.1.0.dist-info/METADATA +312 -0
- synapsai_python-0.1.0.dist-info/RECORD +37 -0
- synapsai_python-0.1.0.dist-info/WHEEL +5 -0
- synapsai_python-0.1.0.dist-info/licenses/LICENSE +201 -0
- synapsai_python-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
# Copyright 2026 SynapsAI Technologies Inc.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""
|
|
16
|
+
Audio resource handlers
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from typing import Union, Iterator, AsyncIterator, List, TYPE_CHECKING, Literal, Optional
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
|
|
22
|
+
from ..types.audio import (
|
|
23
|
+
AudioSpeechResponse,
|
|
24
|
+
AudioTranscriptionResponse,
|
|
25
|
+
AudioTranslationResponse,
|
|
26
|
+
AudioTranscriptionChunk,
|
|
27
|
+
AudioTranslationChunk,
|
|
28
|
+
AudioFormat,
|
|
29
|
+
TimestampGranularity,
|
|
30
|
+
)
|
|
31
|
+
from ..processing import process_audio_input
|
|
32
|
+
from ..logging import get_logger
|
|
33
|
+
from ..exceptions import APIError
|
|
34
|
+
|
|
35
|
+
logger = get_logger(__name__)
|
|
36
|
+
|
|
37
|
+
if TYPE_CHECKING:
|
|
38
|
+
from ..client import SynapsAI, AsyncSynapsAI
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class _SpeechStreamingResponse:
|
|
42
|
+
"""Synchronous streaming response wrapper for speech audio."""
|
|
43
|
+
|
|
44
|
+
def __init__(self, response):
|
|
45
|
+
self._response = response
|
|
46
|
+
|
|
47
|
+
def __enter__(self) -> "_SpeechStreamingResponse":
|
|
48
|
+
return self
|
|
49
|
+
|
|
50
|
+
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
|
|
51
|
+
close = getattr(self._response, "close", None)
|
|
52
|
+
if callable(close):
|
|
53
|
+
close()
|
|
54
|
+
|
|
55
|
+
def __iter__(self) -> Iterator[bytes]:
|
|
56
|
+
for chunk in self._response.iter_bytes(chunk_size=8192):
|
|
57
|
+
if chunk:
|
|
58
|
+
yield chunk
|
|
59
|
+
|
|
60
|
+
def stream_to_file(self, file_path) -> None:
|
|
61
|
+
"""Stream audio to a file path or file-like object."""
|
|
62
|
+
# Accept both Path-like and str for convenience
|
|
63
|
+
if hasattr(file_path, "write"):
|
|
64
|
+
# File-like object
|
|
65
|
+
for chunk in self:
|
|
66
|
+
file_path.write(chunk)
|
|
67
|
+
else:
|
|
68
|
+
path = Path(file_path)
|
|
69
|
+
with path.open("wb") as f:
|
|
70
|
+
for chunk in self:
|
|
71
|
+
f.write(chunk)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class _SpeechWithStreamingResponse:
|
|
75
|
+
"""Namespace that mirrors openai.audio.speech.with_streaming_response."""
|
|
76
|
+
|
|
77
|
+
def __init__(self, speech_resource: "SpeechResource"):
|
|
78
|
+
self._speech_resource = speech_resource
|
|
79
|
+
|
|
80
|
+
def create(
|
|
81
|
+
self,
|
|
82
|
+
model: str,
|
|
83
|
+
input: str,
|
|
84
|
+
response_format: Union[str, AudioFormat] = "mp3",
|
|
85
|
+
speed: float = 1.0,
|
|
86
|
+
**kwargs,
|
|
87
|
+
) -> _SpeechStreamingResponse:
|
|
88
|
+
"""Create a streaming speech response."""
|
|
89
|
+
|
|
90
|
+
# Build request (re-use SpeechResource's internal logic)
|
|
91
|
+
request_data = self._speech_resource._client._build_request(
|
|
92
|
+
model=model,
|
|
93
|
+
input=input,
|
|
94
|
+
response_format=response_format,
|
|
95
|
+
speed=speed,
|
|
96
|
+
stream_format="audio",
|
|
97
|
+
**kwargs,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
endpoint = "audio/speech"
|
|
101
|
+
|
|
102
|
+
response = self._speech_resource._client._post(
|
|
103
|
+
endpoint,
|
|
104
|
+
json_data=request_data
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
return _SpeechStreamingResponse(response)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class SpeechResource:
|
|
111
|
+
"""Speech synthesis resource"""
|
|
112
|
+
|
|
113
|
+
def __init__(self, client: "SynapsAI"):
|
|
114
|
+
self._client = client
|
|
115
|
+
self.with_streaming_response = _SpeechWithStreamingResponse(self)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class TranscriptionsResource:
|
|
119
|
+
"""Audio transcription resource"""
|
|
120
|
+
|
|
121
|
+
def __init__(self, client: "SynapsAI"):
|
|
122
|
+
self._client = client
|
|
123
|
+
|
|
124
|
+
def create(
|
|
125
|
+
self,
|
|
126
|
+
model: str,
|
|
127
|
+
file,
|
|
128
|
+
language: str = None,
|
|
129
|
+
prompt: str = None,
|
|
130
|
+
response_format: Literal["json", "text", "str", "verbose_json", "vtt"] = "json",
|
|
131
|
+
temperature: float = 0.0,
|
|
132
|
+
seed: Optional[int] = None,
|
|
133
|
+
top_p: Optional[float] = None,
|
|
134
|
+
top_k: Optional[int] = None,
|
|
135
|
+
n: Optional[int] = None,
|
|
136
|
+
frequency_penalty: Optional[float] = None,
|
|
137
|
+
presence_penalty: Optional[float] = None,
|
|
138
|
+
max_completion_tokens: Optional[int] = None,
|
|
139
|
+
to_language: Optional[str] = None,
|
|
140
|
+
repetition_penalty: Optional[float] = None,
|
|
141
|
+
timestamp_granularities: List[Union[str, TimestampGranularity]] = None,
|
|
142
|
+
stream: bool = False,
|
|
143
|
+
**kwargs
|
|
144
|
+
) -> Union[AudioTranscriptionResponse, Iterator[AudioTranscriptionChunk]]:
|
|
145
|
+
"""Transcribe audio to text"""
|
|
146
|
+
|
|
147
|
+
# Handle file input
|
|
148
|
+
file_data = process_audio_input(file)
|
|
149
|
+
|
|
150
|
+
# Build request
|
|
151
|
+
request_data = self._client._build_request(
|
|
152
|
+
model=model,
|
|
153
|
+
file=file_data,
|
|
154
|
+
language=language,
|
|
155
|
+
prompt=prompt,
|
|
156
|
+
response_format=response_format,
|
|
157
|
+
temperature=temperature,
|
|
158
|
+
seed=seed,
|
|
159
|
+
top_p=top_p,
|
|
160
|
+
top_k=top_k,
|
|
161
|
+
n=n,
|
|
162
|
+
frequency_penalty=frequency_penalty,
|
|
163
|
+
presence_penalty=presence_penalty,
|
|
164
|
+
max_completion_tokens=max_completion_tokens,
|
|
165
|
+
to_language=to_language,
|
|
166
|
+
repetition_penalty=repetition_penalty,
|
|
167
|
+
timestamp_granularities=timestamp_granularities,
|
|
168
|
+
stream=stream,
|
|
169
|
+
**kwargs
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
endpoint = "audio/transcriptions"
|
|
173
|
+
|
|
174
|
+
if stream:
|
|
175
|
+
return self._stream_transcriptions(endpoint, request_data)
|
|
176
|
+
else:
|
|
177
|
+
response = self._client._post(endpoint, json_data=request_data)
|
|
178
|
+
return AudioTranscriptionResponse(**response.json())
|
|
179
|
+
|
|
180
|
+
def _stream_transcriptions(self, endpoint, request_data) -> Iterator[AudioTranscriptionChunk]:
|
|
181
|
+
for chunk_data in self._client._stream_response(endpoint, request_data):
|
|
182
|
+
try:
|
|
183
|
+
error = chunk_data.get("error")
|
|
184
|
+
if error:
|
|
185
|
+
raise APIError(error)
|
|
186
|
+
yield AudioTranscriptionChunk(**chunk_data)
|
|
187
|
+
except APIError as e:
|
|
188
|
+
raise e
|
|
189
|
+
except Exception as e:
|
|
190
|
+
logger.warning(
|
|
191
|
+
"Failed to parse AudioTranscriptionChunk",
|
|
192
|
+
exc_info=True,
|
|
193
|
+
extra={"endpoint": endpoint},
|
|
194
|
+
)
|
|
195
|
+
continue
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
class TranslationsResource:
|
|
199
|
+
"""Audio translation resource"""
|
|
200
|
+
|
|
201
|
+
def __init__(self, client: "SynapsAI"):
|
|
202
|
+
self._client = client
|
|
203
|
+
|
|
204
|
+
def create(
|
|
205
|
+
self,
|
|
206
|
+
model: str,
|
|
207
|
+
file,
|
|
208
|
+
language: str = None,
|
|
209
|
+
prompt: str = None,
|
|
210
|
+
response_format: Literal["json", "text", "str", "verbose_json", "vtt"] = "json",
|
|
211
|
+
temperature: float = 0.0,
|
|
212
|
+
seed: Optional[int] = None,
|
|
213
|
+
top_p: Optional[float] = None,
|
|
214
|
+
top_k: Optional[int] = None,
|
|
215
|
+
n: Optional[int] = None,
|
|
216
|
+
frequency_penalty: Optional[float] = None,
|
|
217
|
+
presence_penalty: Optional[float] = None,
|
|
218
|
+
max_completion_tokens: Optional[int] = None,
|
|
219
|
+
to_language: Optional[str] = None,
|
|
220
|
+
repetition_penalty: Optional[float] = None,
|
|
221
|
+
stream: bool = False,
|
|
222
|
+
**kwargs
|
|
223
|
+
) -> Union[AudioTranslationResponse, Iterator[AudioTranslationChunk]]:
|
|
224
|
+
"""Translate audio to English text"""
|
|
225
|
+
|
|
226
|
+
# Handle file input
|
|
227
|
+
file_data = process_audio_input(file)
|
|
228
|
+
|
|
229
|
+
# Build request
|
|
230
|
+
request_data = self._client._build_request(
|
|
231
|
+
model=model,
|
|
232
|
+
file=file_data,
|
|
233
|
+
language=language,
|
|
234
|
+
prompt=prompt,
|
|
235
|
+
response_format=response_format,
|
|
236
|
+
temperature=temperature,
|
|
237
|
+
seed=seed,
|
|
238
|
+
top_p=top_p,
|
|
239
|
+
top_k=top_k,
|
|
240
|
+
n=n,
|
|
241
|
+
frequency_penalty=frequency_penalty,
|
|
242
|
+
presence_penalty=presence_penalty,
|
|
243
|
+
max_completion_tokens=max_completion_tokens,
|
|
244
|
+
to_language=to_language,
|
|
245
|
+
repetition_penalty=repetition_penalty,
|
|
246
|
+
stream=stream,
|
|
247
|
+
**kwargs
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
endpoint = "audio/translations"
|
|
251
|
+
|
|
252
|
+
if stream:
|
|
253
|
+
return self._stream_translations(endpoint, request_data)
|
|
254
|
+
else:
|
|
255
|
+
response = self._client._post(endpoint, json_data=request_data)
|
|
256
|
+
return AudioTranslationResponse(**response.json())
|
|
257
|
+
|
|
258
|
+
def _stream_translations(self, endpoint, request_data) -> Iterator[AudioTranslationChunk]:
|
|
259
|
+
for chunk_data in self._client._stream_response(endpoint, request_data):
|
|
260
|
+
try:
|
|
261
|
+
error = chunk_data.get("error")
|
|
262
|
+
if error:
|
|
263
|
+
raise APIError(error)
|
|
264
|
+
yield AudioTranslationChunk(**chunk_data)
|
|
265
|
+
except APIError as e:
|
|
266
|
+
raise e
|
|
267
|
+
except Exception:
|
|
268
|
+
logger.warning(
|
|
269
|
+
"Failed to parse AudioTranslationChunk",
|
|
270
|
+
exc_info=True,
|
|
271
|
+
extra={"endpoint": endpoint},
|
|
272
|
+
)
|
|
273
|
+
continue
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
class AudioResource:
|
|
277
|
+
"""Audio resource handler"""
|
|
278
|
+
|
|
279
|
+
def __init__(self, client: "SynapsAI"):
|
|
280
|
+
self._client = client
|
|
281
|
+
self.speech = SpeechResource(client)
|
|
282
|
+
self.transcriptions = TranscriptionsResource(client)
|
|
283
|
+
self.translations = TranslationsResource(client)
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
class AsyncSpeechResource:
|
|
287
|
+
"""Async speech synthesis resource"""
|
|
288
|
+
|
|
289
|
+
def __init__(self, client: "AsyncSynapsAI"):
|
|
290
|
+
self._client = client
|
|
291
|
+
|
|
292
|
+
async def create(
|
|
293
|
+
self,
|
|
294
|
+
model: str,
|
|
295
|
+
input: str,
|
|
296
|
+
response_format: Union[str, AudioFormat] = "mp3",
|
|
297
|
+
speed: float = 1.0,
|
|
298
|
+
stream: bool = False,
|
|
299
|
+
**kwargs
|
|
300
|
+
) -> Union[AudioSpeechResponse, AsyncIterator[bytes]]:
|
|
301
|
+
"""Generate speech from text asynchronously"""
|
|
302
|
+
|
|
303
|
+
# Build request
|
|
304
|
+
request_data = self._client._build_request(
|
|
305
|
+
model=model,
|
|
306
|
+
input=input,
|
|
307
|
+
response_format=response_format,
|
|
308
|
+
speed=speed,
|
|
309
|
+
stream=stream,
|
|
310
|
+
**kwargs
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
endpoint = "audio/speech"
|
|
314
|
+
|
|
315
|
+
# Make request
|
|
316
|
+
response = await self._client._post(
|
|
317
|
+
endpoint,
|
|
318
|
+
json_data=request_data,
|
|
319
|
+
stream=stream
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
if stream:
|
|
323
|
+
return self._stream_audio(response)
|
|
324
|
+
else:
|
|
325
|
+
content_type = response.headers.get("content-type", "audio/mpeg")
|
|
326
|
+
return AudioSpeechResponse(
|
|
327
|
+
content=response.content,
|
|
328
|
+
content_type=content_type
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
async def _stream_audio(self, response) -> AsyncIterator[bytes]:
|
|
332
|
+
"""Stream audio chunks asynchronously"""
|
|
333
|
+
async for chunk in response.aiter_bytes(chunk_size=8192):
|
|
334
|
+
if chunk:
|
|
335
|
+
yield chunk
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
class AsyncTranscriptionsResource:
|
|
339
|
+
"""Async audio transcription resource"""
|
|
340
|
+
|
|
341
|
+
def __init__(self, client: "AsyncSynapsAI"):
|
|
342
|
+
self._client = client
|
|
343
|
+
|
|
344
|
+
async def create(
|
|
345
|
+
self,
|
|
346
|
+
model: str,
|
|
347
|
+
file,
|
|
348
|
+
language: str = None,
|
|
349
|
+
prompt: str = None,
|
|
350
|
+
response_format: Literal["json", "text", "str", "verbose_json", "vtt"] = "json",
|
|
351
|
+
temperature: float = 0.0,
|
|
352
|
+
seed: Optional[int] = None,
|
|
353
|
+
top_p: Optional[float] = None,
|
|
354
|
+
top_k: Optional[int] = None,
|
|
355
|
+
n: Optional[int] = None,
|
|
356
|
+
frequency_penalty: Optional[float] = None,
|
|
357
|
+
presence_penalty: Optional[float] = None,
|
|
358
|
+
max_completion_tokens: Optional[int] = None,
|
|
359
|
+
to_language: Optional[str] = None,
|
|
360
|
+
repetition_penalty: Optional[float] = None,
|
|
361
|
+
timestamp_granularities: List[Union[str, TimestampGranularity]] = None,
|
|
362
|
+
stream: bool = False,
|
|
363
|
+
**kwargs
|
|
364
|
+
) -> Union[AudioTranscriptionResponse, AsyncIterator[AudioTranscriptionChunk]]:
|
|
365
|
+
"""Transcribe audio to text asynchronously"""
|
|
366
|
+
|
|
367
|
+
# Handle file input
|
|
368
|
+
file_data = process_audio_input(file)
|
|
369
|
+
|
|
370
|
+
# Build request
|
|
371
|
+
request_data = self._client._build_request(
|
|
372
|
+
model=model,
|
|
373
|
+
file=file_data,
|
|
374
|
+
language=language,
|
|
375
|
+
prompt=prompt,
|
|
376
|
+
response_format=response_format,
|
|
377
|
+
temperature=temperature,
|
|
378
|
+
seed=seed,
|
|
379
|
+
top_p=top_p,
|
|
380
|
+
top_k=top_k,
|
|
381
|
+
n=n,
|
|
382
|
+
frequency_penalty=frequency_penalty,
|
|
383
|
+
presence_penalty=presence_penalty,
|
|
384
|
+
max_completion_tokens=max_completion_tokens,
|
|
385
|
+
to_language=to_language,
|
|
386
|
+
repetition_penalty=repetition_penalty,
|
|
387
|
+
timestamp_granularities=timestamp_granularities,
|
|
388
|
+
stream=stream,
|
|
389
|
+
**kwargs
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
endpoint = "audio/transcriptions"
|
|
393
|
+
|
|
394
|
+
if stream:
|
|
395
|
+
return self._stream_transcriptions(endpoint, request_data)
|
|
396
|
+
else:
|
|
397
|
+
response = await self._client._post(endpoint, json_data=request_data)
|
|
398
|
+
return AudioTranscriptionResponse(**response.json())
|
|
399
|
+
|
|
400
|
+
async def _stream_transcriptions(self, endpoint, request_data) -> AsyncIterator[AudioTranscriptionChunk]:
|
|
401
|
+
"""Stream transcription chunks asynchronously"""
|
|
402
|
+
async for chunk_data in self._client._stream_response(endpoint, request_data):
|
|
403
|
+
try:
|
|
404
|
+
yield AudioTranscriptionChunk(**chunk_data)
|
|
405
|
+
except Exception:
|
|
406
|
+
logger.warning(
|
|
407
|
+
"Failed to parse AudioTranscriptionChunk",
|
|
408
|
+
exc_info=True,
|
|
409
|
+
extra={"endpoint": endpoint},
|
|
410
|
+
)
|
|
411
|
+
continue
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
class AsyncTranslationsResource:
|
|
415
|
+
"""Async audio translation resource"""
|
|
416
|
+
|
|
417
|
+
def __init__(self, client: "AsyncSynapsAI"):
|
|
418
|
+
self._client = client
|
|
419
|
+
|
|
420
|
+
async def create(
|
|
421
|
+
self,
|
|
422
|
+
model: str,
|
|
423
|
+
file,
|
|
424
|
+
language: str = None,
|
|
425
|
+
prompt: str = None,
|
|
426
|
+
response_format: Literal["json", "text", "str", "verbose_json", "vtt"] = "json",
|
|
427
|
+
temperature: float = 0.0,
|
|
428
|
+
seed: Optional[int] = None,
|
|
429
|
+
top_p: Optional[float] = None,
|
|
430
|
+
top_k: Optional[int] = None,
|
|
431
|
+
n: Optional[int] = None,
|
|
432
|
+
frequency_penalty: Optional[float] = None,
|
|
433
|
+
presence_penalty: Optional[float] = None,
|
|
434
|
+
max_completion_tokens: Optional[int] = None,
|
|
435
|
+
to_language: Optional[str] = None,
|
|
436
|
+
repetition_penalty: Optional[float] = None,
|
|
437
|
+
stream: bool = False,
|
|
438
|
+
**kwargs
|
|
439
|
+
) -> Union[AudioTranslationResponse, AsyncIterator[AudioTranslationChunk]]:
|
|
440
|
+
"""Translate audio to English text asynchronously"""
|
|
441
|
+
|
|
442
|
+
# Handle file input
|
|
443
|
+
file_data = process_audio_input(file)
|
|
444
|
+
|
|
445
|
+
# Build request
|
|
446
|
+
request_data = self._client._build_request(
|
|
447
|
+
model=model,
|
|
448
|
+
file=file_data,
|
|
449
|
+
language=language,
|
|
450
|
+
prompt=prompt,
|
|
451
|
+
response_format=response_format,
|
|
452
|
+
temperature=temperature,
|
|
453
|
+
seed=seed,
|
|
454
|
+
top_p=top_p,
|
|
455
|
+
top_k=top_k,
|
|
456
|
+
n=n,
|
|
457
|
+
frequency_penalty=frequency_penalty,
|
|
458
|
+
presence_penalty=presence_penalty,
|
|
459
|
+
max_completion_tokens=max_completion_tokens,
|
|
460
|
+
to_language=to_language,
|
|
461
|
+
repetition_penalty=repetition_penalty,
|
|
462
|
+
stream=stream,
|
|
463
|
+
**kwargs
|
|
464
|
+
)
|
|
465
|
+
|
|
466
|
+
endpoint = "audio/translations"
|
|
467
|
+
|
|
468
|
+
if stream:
|
|
469
|
+
return self._stream_translations(endpoint, request_data)
|
|
470
|
+
else:
|
|
471
|
+
response = await self._client._post(endpoint, json_data=request_data)
|
|
472
|
+
return AudioTranslationResponse(**response.json())
|
|
473
|
+
|
|
474
|
+
async def _stream_translations(self, endpoint, request_data) -> AsyncIterator[AudioTranslationChunk]:
|
|
475
|
+
"""Stream translation chunks asynchronously"""
|
|
476
|
+
async for chunk_data in self._client._stream_response(endpoint, request_data):
|
|
477
|
+
try:
|
|
478
|
+
yield AudioTranslationChunk(**chunk_data)
|
|
479
|
+
except Exception:
|
|
480
|
+
logger.warning(
|
|
481
|
+
"Failed to parse AudioTranslationChunk",
|
|
482
|
+
exc_info=True,
|
|
483
|
+
extra={"endpoint": endpoint},
|
|
484
|
+
)
|
|
485
|
+
continue
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
class AsyncAudioResource:
|
|
489
|
+
"""Async audio resource handler"""
|
|
490
|
+
|
|
491
|
+
def __init__(self, client: "AsyncSynapsAI"):
|
|
492
|
+
self._client = client
|
|
493
|
+
self.speech = AsyncSpeechResource(client)
|
|
494
|
+
self.transcriptions = AsyncTranscriptionsResource(client)
|
|
495
|
+
self.translations = AsyncTranslationsResource(client)
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# Copyright 2026 SynapsAI Technologies Inc.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""
|
|
16
|
+
Chat completion resource handlers
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from typing import Union, Iterator, AsyncIterator, TYPE_CHECKING, Optional, Literal
|
|
20
|
+
|
|
21
|
+
from ..types.completion import (
|
|
22
|
+
ChatCompletionResponse,
|
|
23
|
+
ChatCompletionChunk,
|
|
24
|
+
)
|
|
25
|
+
from ..logging import get_logger
|
|
26
|
+
from ..exceptions import APIError
|
|
27
|
+
|
|
28
|
+
if TYPE_CHECKING:
|
|
29
|
+
from ..client import SynapsAI, AsyncSynapsAI
|
|
30
|
+
|
|
31
|
+
logger = get_logger(__name__)
|
|
32
|
+
|
|
33
|
+
class ChatCompletionsResource:
|
|
34
|
+
"""Chat completions resource"""
|
|
35
|
+
|
|
36
|
+
def __init__(self, client: "SynapsAI"):
|
|
37
|
+
self._client = client
|
|
38
|
+
|
|
39
|
+
def create(
|
|
40
|
+
self,
|
|
41
|
+
model: str,
|
|
42
|
+
messages: list,
|
|
43
|
+
temperature: float = 1.0,
|
|
44
|
+
top_p: float = 1.0,
|
|
45
|
+
n: int = 1,
|
|
46
|
+
stream: bool = False,
|
|
47
|
+
stop = [],
|
|
48
|
+
max_completion_tokens = 128,
|
|
49
|
+
presence_penalty: float = 0.0,
|
|
50
|
+
frequency_penalty: float = 0.0,
|
|
51
|
+
logit_bias = None,
|
|
52
|
+
functions = None,
|
|
53
|
+
function_call = None,
|
|
54
|
+
tools = None,
|
|
55
|
+
tool_choice = None,
|
|
56
|
+
response_format = None,
|
|
57
|
+
seed = None,
|
|
58
|
+
reasoning_effort: Optional[Literal["none", "minimal", "low", "medium", "high", "xhigh", "max"]] = None,
|
|
59
|
+
**kwargs
|
|
60
|
+
) -> Union[ChatCompletionResponse, Iterator[ChatCompletionChunk]]:
|
|
61
|
+
"""Create a chat completion"""
|
|
62
|
+
|
|
63
|
+
# Build request
|
|
64
|
+
request_data = self._client._build_request(
|
|
65
|
+
model=model,
|
|
66
|
+
messages=messages,
|
|
67
|
+
temperature=temperature,
|
|
68
|
+
top_p=top_p,
|
|
69
|
+
n=n,
|
|
70
|
+
stream=stream,
|
|
71
|
+
stop=stop,
|
|
72
|
+
max_completion_tokens=max_completion_tokens,
|
|
73
|
+
presence_penalty=presence_penalty,
|
|
74
|
+
frequency_penalty=frequency_penalty,
|
|
75
|
+
logit_bias=logit_bias,
|
|
76
|
+
functions=functions,
|
|
77
|
+
function_call=function_call,
|
|
78
|
+
tools=tools,
|
|
79
|
+
tool_choice=tool_choice,
|
|
80
|
+
response_format=response_format,
|
|
81
|
+
seed=seed,
|
|
82
|
+
reasoning_effort=reasoning_effort,
|
|
83
|
+
**kwargs
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
endpoint = "chat/completions"
|
|
87
|
+
|
|
88
|
+
if stream:
|
|
89
|
+
return self._stream_completions(endpoint, request_data)
|
|
90
|
+
else:
|
|
91
|
+
response = self._client._post(endpoint, request_data)
|
|
92
|
+
return ChatCompletionResponse.model_validate(response.json())
|
|
93
|
+
|
|
94
|
+
def _stream_completions(self, endpoint, request_data) -> Iterator[ChatCompletionChunk]:
|
|
95
|
+
for chunk_data in self._client._stream_response(endpoint, request_data):
|
|
96
|
+
try:
|
|
97
|
+
error = chunk_data.get("error")
|
|
98
|
+
if error:
|
|
99
|
+
raise APIError(error)
|
|
100
|
+
yield ChatCompletionChunk.model_validate(chunk_data)
|
|
101
|
+
except APIError as e:
|
|
102
|
+
raise e
|
|
103
|
+
except Exception as e:
|
|
104
|
+
logger.warning(
|
|
105
|
+
"Failed to parse ChatCompletionChunk",
|
|
106
|
+
exc_info=True,
|
|
107
|
+
extra={"endpoint": endpoint},
|
|
108
|
+
)
|
|
109
|
+
continue
|
|
110
|
+
|
|
111
|
+
class ChatResource:
|
|
112
|
+
"""Chat resource handler"""
|
|
113
|
+
|
|
114
|
+
def __init__(self, client: "SynapsAI"):
|
|
115
|
+
self._client = client
|
|
116
|
+
self.completions = ChatCompletionsResource(client)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class AsyncChatCompletionsResource:
|
|
120
|
+
"""Async chat completions resource"""
|
|
121
|
+
|
|
122
|
+
def __init__(self, client: "AsyncSynapsAI"):
|
|
123
|
+
self._client = client
|
|
124
|
+
|
|
125
|
+
async def create(
|
|
126
|
+
self,
|
|
127
|
+
model: str,
|
|
128
|
+
messages: list,
|
|
129
|
+
temperature: float = 1.0,
|
|
130
|
+
top_p: float = 1.0,
|
|
131
|
+
n: int = 1,
|
|
132
|
+
stream: bool = False,
|
|
133
|
+
stop = [],
|
|
134
|
+
max_completion_tokens = 128,
|
|
135
|
+
presence_penalty: float = 0.0,
|
|
136
|
+
frequency_penalty: float = 0.0,
|
|
137
|
+
logit_bias = None,
|
|
138
|
+
functions = None,
|
|
139
|
+
function_call = None,
|
|
140
|
+
tools = None,
|
|
141
|
+
tool_choice = None,
|
|
142
|
+
response_format = None,
|
|
143
|
+
seed = None,
|
|
144
|
+
reasoning_effort: Optional[Literal["none", "minimal", "low", "medium", "high", "xhigh", "max"]] = None,
|
|
145
|
+
**kwargs
|
|
146
|
+
) -> Union[ChatCompletionResponse, AsyncIterator[ChatCompletionChunk]]:
|
|
147
|
+
"""Create a chat completion asynchronously"""
|
|
148
|
+
|
|
149
|
+
# Build request
|
|
150
|
+
request_data = self._client._build_request(
|
|
151
|
+
model=model,
|
|
152
|
+
messages=messages,
|
|
153
|
+
temperature=temperature,
|
|
154
|
+
top_p=top_p,
|
|
155
|
+
n=n,
|
|
156
|
+
stream=stream,
|
|
157
|
+
stop=stop,
|
|
158
|
+
max_completion_tokens=max_completion_tokens,
|
|
159
|
+
presence_penalty=presence_penalty,
|
|
160
|
+
frequency_penalty=frequency_penalty,
|
|
161
|
+
logit_bias=logit_bias,
|
|
162
|
+
functions=functions,
|
|
163
|
+
function_call=function_call,
|
|
164
|
+
tools=tools,
|
|
165
|
+
tool_choice=tool_choice,
|
|
166
|
+
response_format=response_format,
|
|
167
|
+
seed=seed,
|
|
168
|
+
reasoning_effort=reasoning_effort,
|
|
169
|
+
**kwargs
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
endpoint = "chat/completions"
|
|
173
|
+
|
|
174
|
+
if stream:
|
|
175
|
+
return self._stream_completions(endpoint, request_data)
|
|
176
|
+
else:
|
|
177
|
+
response = await self._client._post(endpoint, request_data)
|
|
178
|
+
return ChatCompletionResponse.model_validate(response.json())
|
|
179
|
+
|
|
180
|
+
async def _stream_completions(self, endpoint, request_data) -> AsyncIterator[ChatCompletionChunk]:
|
|
181
|
+
"""Stream chat completion chunks asynchronously"""
|
|
182
|
+
async for chunk_data in self._client._stream_response(endpoint, request_data):
|
|
183
|
+
try:
|
|
184
|
+
yield ChatCompletionChunk.model_validate(chunk_data)
|
|
185
|
+
except Exception as e:
|
|
186
|
+
logger.warning(
|
|
187
|
+
"Failed to parse ChatCompletionChunk",
|
|
188
|
+
exc_info=True,
|
|
189
|
+
extra={"endpoint": endpoint},
|
|
190
|
+
)
|
|
191
|
+
continue
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
class AsyncChatResource:
|
|
195
|
+
"""Async chat resource handler"""
|
|
196
|
+
|
|
197
|
+
def __init__(self, client: "AsyncSynapsAI"):
|
|
198
|
+
self._client = client
|
|
199
|
+
self.completions = AsyncChatCompletionsResource(client)
|