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.
@@ -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)