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 ADDED
@@ -0,0 +1,16 @@
1
+ """
2
+ SynapsAI - OpenAI Compatible API Client Library
3
+
4
+ A scalable and maintainable Python client library for interacting with SynapsAI services,
5
+ compatible with OpenAI API patterns.
6
+ """
7
+
8
+ from .client import SynapsAI, AsyncSynapsAI
9
+ from .types import *
10
+ from .resources import *
11
+
12
+ __version__ = "0.1.0"
13
+ __all__ = [
14
+ "SynapsAI",
15
+ "AsyncSynapsAI",
16
+ ]
synapsai/client.py ADDED
@@ -0,0 +1,628 @@
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
+ Main client classes for SynapsAI API
17
+ """
18
+
19
+ import httpx
20
+ import json
21
+ import os
22
+ import time
23
+ import random
24
+ import asyncio
25
+ from typing import Optional, Dict, Any, Union, AsyncIterator, Iterator
26
+
27
+ from .resources import (
28
+ ChatResource,
29
+ AsyncChatResource,
30
+ ImagesResource,
31
+ AsyncImagesResource,
32
+ VideosResource,
33
+ AsyncVideosResource,
34
+ EmbeddingsResource,
35
+ AsyncEmbeddingsResource,
36
+ AudioResource,
37
+ AsyncAudioResource,
38
+ CompletionsResource,
39
+ AsyncCompletionsResource,
40
+ ClassificationsResource,
41
+ AsyncClassificationsResource,
42
+ QuestionAnsweringResource,
43
+ AsyncQuestionAnsweringResource,
44
+ ModelsResource,
45
+ AsyncModelsResource,
46
+ FeatureExtractionResource,
47
+ AsyncFeatureExtractionResource,
48
+ FillMaskResource,
49
+ AsyncFillMaskResource,
50
+ RerankResource,
51
+ AsyncRerankResource,
52
+ )
53
+ from .exceptions import APIError, AuthenticationError
54
+ from .utils import build_url
55
+ from .logging import get_logger
56
+
57
+ logger = get_logger(__name__)
58
+
59
+ class BaseClient:
60
+ """Base client with common functionality"""
61
+
62
+ def __init__(
63
+ self,
64
+ api_key: Optional[str] = None,
65
+ base_url: Optional[str] = None,
66
+ timeout: float = 300.0,
67
+ max_retries: int = 1,
68
+ headers: Optional[Dict[str, str]] = None,
69
+ httpx_client: Optional[httpx.Client] = None,
70
+ ):
71
+ """
72
+ Initialize the client with the provided arguments.
73
+
74
+ Args:
75
+ api_key: API key for authentication.
76
+ base_url: Base URL for the API.
77
+ timeout: Timeout for requests (seconds or httpx.Timeout).
78
+ max_retries: Maximum number of retries for requests (>=1).
79
+ headers: Additional headers to include in requests.
80
+ httpx_client: Custom HTTP client to use for requests.
81
+ """
82
+ if api_key is None:
83
+ api_key = os.environ.get("SYNAPSAI_API_KEY")
84
+
85
+ if api_key is None:
86
+ raise AuthenticationError(
87
+ "No API key provided. You can set your API key in an environment variable `SYNAPSAI_API_KEY`, or you can pass it as an argument `SynapsAI(api_key=...)`."
88
+ )
89
+
90
+ if base_url is None:
91
+ base_url = os.environ.get("SYNAPSAI_API_BASE")
92
+
93
+ if base_url is None:
94
+ base_url = "https://api.synapsai.cloud/v1"
95
+
96
+ self.api_key = api_key
97
+ self.base_url = base_url.rstrip("/")
98
+ self.timeout = timeout
99
+
100
+ # ensure sensible value
101
+ self.max_retries = max(1, int(max_retries))
102
+
103
+ # Set up default headers
104
+ self._headers = {
105
+ "Content-Type": "application/json",
106
+ "User-Agent": "synapsai-python/1.0.0",
107
+ "Authorization": f"Bearer {api_key}",
108
+ }
109
+
110
+ if headers:
111
+ self._headers.update(headers)
112
+
113
+ self._client = httpx_client
114
+
115
+ def _build_request(
116
+ self,
117
+ **kwargs
118
+ ) -> Dict[str, Any]:
119
+ """Build request payload for SynapsAI API"""
120
+ # Filter out None values
121
+ filtered_kwargs = {k: v for k, v in kwargs.items() if v is not None}
122
+
123
+ return {
124
+ **filtered_kwargs
125
+ }
126
+
127
+ def _handle_error_response(self, response: httpx.Response) -> None:
128
+ # Ensure content is loaded for streamed responses
129
+ try:
130
+ # If _content is missing, this will populate it
131
+ if not hasattr(response, "_content"):
132
+ response.read()
133
+ except Exception:
134
+ # If this fails, we still fall back to a generic message below
135
+ pass
136
+
137
+ try:
138
+ error_data = response.json()
139
+ # Support the infra server structured error format:
140
+ if isinstance(error_data, dict) and "error" in error_data:
141
+ err = error_data["error"]
142
+ message = err.get("message") or err.get("error") or str(err)
143
+
144
+ # keep original status semantics
145
+ raise APIError(message, status_code=response.status_code)
146
+ else:
147
+ # try to extract message from common patterns
148
+ if isinstance(error_data, dict):
149
+ message = error_data.get("error", {}).get("message") or error_data.get("message") or str(error_data)
150
+ else:
151
+ message = str(error_data)
152
+ except Exception:
153
+ message = f"HTTP {response.status_code}: {response.text}"
154
+ raise APIError(message, status_code=response.status_code)
155
+
156
+ def _should_retry(self, method: str, response: Optional[httpx.Response], exc: Optional[BaseException], attempt: int) -> bool:
157
+ """
158
+ Decide whether to retry a request.
159
+
160
+ Retry on:
161
+ - network/transport errors (httpx.RequestError)
162
+ - timeout errors (httpx.TimeoutException)
163
+ - HTTP 429 (rate limit) and 5xx server errors
164
+
165
+ For safety, we limit number of retries via self.max_retries externally.
166
+ """
167
+ # If exception is present and is a network/timeout error -> retry
168
+ if exc is not None:
169
+ if isinstance(exc, (httpx.RequestError, httpx.TimeoutException)):
170
+ return True
171
+ return False
172
+
173
+ if response is not None:
174
+ # Retry on 429 or 5xx
175
+ if response.status_code == 429:
176
+ return True
177
+ if 500 <= response.status_code < 600:
178
+ return True
179
+
180
+ return False
181
+
182
+ def _backoff_delay(self, attempt: int) -> float:
183
+ """
184
+ Exponential backoff with jitter.
185
+
186
+ attempt is 0-based. We use base 0.5s.
187
+ """
188
+ base = 0.5
189
+ delay = base * (2 ** attempt)
190
+ # add jitter up to 0.5s
191
+ delay += random.uniform(0, 0.5)
192
+ # Cap delay to a sensible maximum (e.g., 30s)
193
+ return min(delay, 30.0)
194
+
195
+
196
+ class SynapsAI(BaseClient):
197
+ """Synchronous SynapsAI client"""
198
+
199
+ chat: ChatResource
200
+ images: ImagesResource
201
+ videos: VideosResource
202
+ embeddings: EmbeddingsResource
203
+ audio: AudioResource
204
+ completions: CompletionsResource
205
+ classifications: ClassificationsResource
206
+ question_answering: QuestionAnsweringResource
207
+ models: ModelsResource
208
+ feature_extraction: FeatureExtractionResource
209
+ fill_mask: FillMaskResource
210
+ rerank: RerankResource
211
+
212
+ def __init__(self, api_key: Optional[str] = None, **kwargs):
213
+ super().__init__(api_key=api_key, **kwargs)
214
+ if self._client is None:
215
+ self._client = httpx.Client(
216
+ timeout=self.timeout,
217
+ headers=self._headers,
218
+ http2=True,
219
+ verify=False,
220
+ )
221
+
222
+ # Initialize resource handlers
223
+ self.chat = ChatResource(self)
224
+ self.images = ImagesResource(self)
225
+ self.videos = VideosResource(self)
226
+ self.embeddings = EmbeddingsResource(self)
227
+ self.audio = AudioResource(self)
228
+ self.completions = CompletionsResource(self)
229
+ self.classifications = ClassificationsResource(self)
230
+ self.question_answering = QuestionAnsweringResource(self)
231
+ self.models = ModelsResource(self)
232
+ self.feature_extraction = FeatureExtractionResource(self)
233
+ self.fill_mask = FillMaskResource(self)
234
+ self.rerank = RerankResource(self)
235
+
236
+ def __enter__(self):
237
+ return self
238
+
239
+ def __exit__(self, exc_type, exc_val, exc_tb):
240
+ self.close()
241
+
242
+ def close(self):
243
+ """Close the HTTP client"""
244
+ self._client.close()
245
+
246
+ def _request(
247
+ self,
248
+ method: str,
249
+ endpoint: str,
250
+ json_data: Optional[Dict[str, Any]] = None,
251
+ data: Optional[Dict[str, Any]] = None,
252
+ files: Optional[Dict[str, Any]] = None,
253
+ stream: bool = False,
254
+ ) -> httpx.Response:
255
+ """
256
+ Make a request to the API with retries.
257
+
258
+ Retries on network errors, timeouts, and server-side errors (429, 5xx).
259
+ """
260
+ url = build_url(self.base_url, endpoint)
261
+
262
+ # prepare kwargs used for every attempt
263
+ base_kwargs = {
264
+ "method": method,
265
+ "url": url,
266
+ "timeout": self.timeout,
267
+ }
268
+
269
+ if json_data:
270
+ base_kwargs["json"] = json_data
271
+ if data:
272
+ base_kwargs["data"] = data
273
+ if files:
274
+ base_kwargs["files"] = files
275
+ if stream:
276
+ # note: for streaming we still attempt to (re)establish the stream on failures opening it
277
+ base_kwargs["stream"] = True
278
+
279
+ attempt = 0
280
+ last_exc: Optional[BaseException] = None
281
+ last_response: Optional[httpx.Response] = None
282
+
283
+ while attempt < self.max_retries:
284
+ try:
285
+ response = self._client.request(**base_kwargs)
286
+ last_response = response
287
+
288
+ # If server-side error or rate-limit, decide if retry
289
+ if response.status_code >= 400:
290
+ if self._should_retry(method, response, None, attempt) and attempt < (self.max_retries - 1):
291
+ # close body and wait before retrying
292
+ try:
293
+ response.read()
294
+ except Exception:
295
+ pass
296
+ delay = self._backoff_delay(attempt)
297
+ time.sleep(delay)
298
+ attempt += 1
299
+ continue
300
+ else:
301
+ # Non-retryable or out of retries -> raise
302
+ self._handle_error_response(response)
303
+ # success
304
+ return response
305
+
306
+ except (httpx.RequestError, httpx.TimeoutException) as e:
307
+ last_exc = e
308
+ # decide if we should retry
309
+ if self._should_retry(method, None, e, attempt) and attempt < (self.max_retries - 1):
310
+ delay = self._backoff_delay(attempt)
311
+ time.sleep(delay)
312
+ attempt += 1
313
+ continue
314
+ # no more retries
315
+ raise APIError(str(e))
316
+
317
+ # If we exit loop without returning, raise last known problem
318
+ if last_response is not None:
319
+ self._handle_error_response(last_response)
320
+ if last_exc is not None:
321
+ raise APIError(str(last_exc))
322
+ raise APIError("Unknown error during request")
323
+
324
+ def _post(
325
+ self,
326
+ endpoint: str,
327
+ json_data: Optional[Dict[str, Any]] = None,
328
+ data: Optional[Dict[str, Any]] = None,
329
+ files: Optional[Dict[str, Any]] = None,
330
+ ) -> httpx.Response:
331
+ """Make a POST request"""
332
+ return self._request("POST", endpoint, json_data, data, files)
333
+
334
+ def _get(self, endpoint: str) -> httpx.Response:
335
+ """Make a GET request"""
336
+ return self._request("GET", endpoint)
337
+
338
+ def _delete(self, endpoint: str) -> httpx.Response:
339
+ """Make a DELETE request"""
340
+ return self._request("DELETE", endpoint)
341
+
342
+ def _stream_response(
343
+ self,
344
+ endpoint: str,
345
+ json_data: Optional[Dict[str, Any]] = None,
346
+ data: Optional[Dict[str, Any]] = None,
347
+ files: Optional[Dict[str, Any]] = None,
348
+ ) -> Iterator[Dict[str, Any]]:
349
+ """
350
+ Stream response data from a POST endpoint.
351
+
352
+ The attempt to establish the stream will be retried using the same backoff rules.
353
+ Once the stream is established, streaming errors are raised as-is.
354
+ """
355
+ url = build_url(self.base_url, endpoint)
356
+ base_kwargs = {
357
+ "method": "POST",
358
+ "url": url,
359
+ "timeout": self.timeout,
360
+ "json": json_data,
361
+ "data": data,
362
+ "files": files,
363
+ }
364
+
365
+ attempt = 0
366
+ while attempt < self.max_retries:
367
+ try:
368
+ with self._client.stream(**base_kwargs) as response:
369
+ if response.status_code >= 400:
370
+ # decide whether to retry establishing stream
371
+ if self._should_retry("POST", response, None, attempt) and attempt < (self.max_retries - 1):
372
+ try:
373
+ response.read()
374
+ except Exception:
375
+ pass
376
+ delay = self._backoff_delay(attempt)
377
+ time.sleep(delay)
378
+ attempt += 1
379
+ continue
380
+ else:
381
+ self._handle_error_response(response)
382
+
383
+ # stream established, iterate lines and yield
384
+ for line in response.iter_lines():
385
+ line = line.strip()
386
+ if not line:
387
+ continue
388
+ if line.startswith("data: "):
389
+ data_line = line[6:] # Remove "data: " prefix
390
+ if data_line == "[DONE]":
391
+ return
392
+ try:
393
+ yield json.loads(data_line)
394
+ except json.JSONDecodeError as e:
395
+ # Log the malformed data for debugging
396
+ logger.warning(f"Received malformed JSON data: {data_line[:100]}...")
397
+ logger.warning(f"JSON decode error: {e}")
398
+ continue
399
+ # If stream ends naturally, return
400
+ return
401
+
402
+ except (httpx.RequestError, httpx.TimeoutException) as e:
403
+ # network issue while establishing stream, maybe retry
404
+ if attempt < (self.max_retries - 1):
405
+ delay = self._backoff_delay(attempt)
406
+ time.sleep(delay)
407
+ attempt += 1
408
+ continue
409
+ raise APIError(str(e))
410
+
411
+ raise APIError("Failed to establish stream after retries")
412
+
413
+ def _get_stream(self, endpoint: str) -> httpx.Response:
414
+ """Make a streaming GET request for binary content (e.g. video download)."""
415
+ url = build_url(self.base_url, endpoint)
416
+ request = self._client.build_request("GET", url, timeout=self.timeout)
417
+ response = self._client.send(request, stream=True)
418
+ if response.status_code >= 400:
419
+ response.read()
420
+ self._handle_error_response(response)
421
+ return response
422
+
423
+
424
+ class AsyncSynapsAI(BaseClient):
425
+ """Asynchronous SynapsAI client"""
426
+ chat: AsyncChatResource
427
+ images: AsyncImagesResource
428
+ videos: AsyncVideosResource
429
+ embeddings: AsyncEmbeddingsResource
430
+ audio: AsyncAudioResource
431
+ completions: AsyncCompletionsResource
432
+ classifications: AsyncClassificationsResource
433
+ question_answering: AsyncQuestionAnsweringResource
434
+ models: AsyncModelsResource
435
+ feature_extraction: AsyncFeatureExtractionResource
436
+ fill_mask: AsyncFillMaskResource
437
+ rerank: AsyncRerankResource
438
+
439
+ def __init__(self, api_key: Optional[str] = None, **kwargs):
440
+ super().__init__(api_key=api_key, **kwargs)
441
+ if self._client is None:
442
+ self._client = httpx.AsyncClient(
443
+ timeout=self.timeout,
444
+ headers=self._headers,
445
+ http2=True,
446
+ )
447
+
448
+ # Initialize async resource handlers
449
+ self.chat = AsyncChatResource(self)
450
+ self.images = AsyncImagesResource(self)
451
+ self.videos = AsyncVideosResource(self)
452
+ self.embeddings = AsyncEmbeddingsResource(self)
453
+ self.audio = AsyncAudioResource(self)
454
+ self.completions = AsyncCompletionsResource(self)
455
+ self.classifications = AsyncClassificationsResource(self)
456
+ self.question_answering = AsyncQuestionAnsweringResource(self)
457
+ self.models = AsyncModelsResource(self)
458
+ self.feature_extraction = AsyncFeatureExtractionResource(self)
459
+ self.fill_mask = AsyncFillMaskResource(self)
460
+ self.rerank = AsyncRerankResource(self)
461
+
462
+ async def __aenter__(self):
463
+ return self
464
+
465
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
466
+ await self.close()
467
+
468
+ async def close(self):
469
+ """Close the HTTP client"""
470
+ await self._client.aclose()
471
+
472
+ async def _request(
473
+ self,
474
+ method: str,
475
+ endpoint: str,
476
+ json_data: Optional[Dict[str, Any]] = None,
477
+ data: Optional[Dict[str, Any]] = None,
478
+ files: Optional[Dict[str, Any]] = None,
479
+ ) -> httpx.Response:
480
+ """Make an async request to the API with retries."""
481
+ url = build_url(self.base_url, endpoint)
482
+
483
+ base_kwargs: Dict[str, Any] = {
484
+ "method": method,
485
+ "url": url,
486
+ "timeout": self.timeout,
487
+ }
488
+
489
+ if json_data:
490
+ base_kwargs["json"] = json_data
491
+ if data:
492
+ base_kwargs["data"] = data
493
+ if files:
494
+ base_kwargs["files"] = files
495
+
496
+ attempt = 0
497
+ last_exc: Optional[BaseException] = None
498
+ last_response: Optional[httpx.Response] = None
499
+
500
+ while attempt < self.max_retries:
501
+ try:
502
+ response = await self._client.request(**base_kwargs)
503
+ last_response = response
504
+
505
+ if response.status_code >= 400:
506
+ if self._should_retry(method, response, None, attempt) and attempt < (self.max_retries - 1):
507
+ try:
508
+ await response.aread()
509
+ except Exception:
510
+ pass
511
+ delay = self._backoff_delay(attempt)
512
+ await asyncio.sleep(delay)
513
+ attempt += 1
514
+ continue
515
+ else:
516
+ self._handle_error_response(response)
517
+ return response
518
+
519
+ except (httpx.RequestError, httpx.TimeoutException) as e:
520
+ last_exc = e
521
+ if self._should_retry(method, None, e, attempt) and attempt < (self.max_retries - 1):
522
+ delay = self._backoff_delay(attempt)
523
+ await asyncio.sleep(delay)
524
+ attempt += 1
525
+ continue
526
+ raise APIError(str(e))
527
+
528
+ # If we exit loop without returning, raise last known problem
529
+ if last_response is not None:
530
+ self._handle_error_response(last_response)
531
+ if last_exc is not None:
532
+ raise APIError(str(last_exc))
533
+ raise APIError("Unknown error during request")
534
+
535
+ async def _post(
536
+ self,
537
+ endpoint: str,
538
+ json_data: Optional[Dict[str, Any]] = None,
539
+ data: Optional[Dict[str, Any]] = None,
540
+ files: Optional[Dict[str, Any]] = None,
541
+ ) -> httpx.Response:
542
+ """Make an async POST request"""
543
+ return await self._request("POST", endpoint, json_data, data, files)
544
+
545
+ async def _get(self, endpoint: str) -> httpx.Response:
546
+ """Make a GET request"""
547
+ return await self._request("GET", endpoint=endpoint)
548
+
549
+ async def _delete(self, endpoint: str) -> httpx.Response:
550
+ """Make a DELETE request"""
551
+ return await self._request("DELETE", endpoint=endpoint)
552
+
553
+ async def _stream_response(
554
+ self,
555
+ endpoint: str,
556
+ json_data: Optional[Dict[str, Any]] = None,
557
+ data: Optional[Dict[str, Any]] = None,
558
+ files: Optional[Dict[str, Any]] = None,
559
+ ) -> AsyncIterator[Dict[str, Any]]:
560
+ """
561
+ Stream response data from a POST endpoint (async).
562
+
563
+ The attempt to establish the stream will be retried using the same backoff rules.
564
+ Once the stream is established, streaming errors are raised as-is.
565
+ """
566
+ url = build_url(self.base_url, endpoint)
567
+
568
+ base_kwargs = {
569
+ "method": "POST",
570
+ "url": url,
571
+ "timeout": self.timeout,
572
+ "json": json_data,
573
+ "data": data,
574
+ "files": files,
575
+ }
576
+
577
+ attempt = 0
578
+ while attempt < self.max_retries:
579
+ try:
580
+ async with self._client.stream(**base_kwargs) as response:
581
+ if response.status_code >= 400:
582
+ if self._should_retry("POST", response, None, attempt) and attempt < (self.max_retries - 1):
583
+ try:
584
+ await response.aread()
585
+ except Exception:
586
+ pass
587
+ delay = self._backoff_delay(attempt)
588
+ await asyncio.sleep(delay)
589
+ attempt += 1
590
+ continue
591
+ else:
592
+ self._handle_error_response(response)
593
+
594
+ async for raw_line in response.aiter_lines():
595
+ line = raw_line.strip()
596
+ if not line:
597
+ continue
598
+ if line.startswith("data: "):
599
+ data_line = line[6:] # Remove "data: " prefix
600
+ if data_line == "[DONE]":
601
+ return
602
+ try:
603
+ yield json.loads(data_line)
604
+ except json.JSONDecodeError:
605
+ # skip malformed line
606
+ continue
607
+ # stream ended normally
608
+ return
609
+
610
+ except (httpx.RequestError, httpx.TimeoutException) as e:
611
+ if attempt < (self.max_retries - 1):
612
+ delay = self._backoff_delay(attempt)
613
+ await asyncio.sleep(delay)
614
+ attempt += 1
615
+ continue
616
+ raise APIError(str(e))
617
+
618
+ raise APIError("Failed to establish stream after retries")
619
+
620
+ async def _get_stream(self, endpoint: str) -> httpx.Response:
621
+ """Make an async streaming GET request for binary content (e.g. video download)."""
622
+ url = build_url(self.base_url, endpoint)
623
+ request = self._client.build_request("GET", url, timeout=self.timeout)
624
+ response = await self._client.send(request, stream=True)
625
+ if response.status_code >= 400:
626
+ await response.aread()
627
+ self._handle_error_response(response)
628
+ return response
synapsai/exceptions.py ADDED
@@ -0,0 +1,64 @@
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
+ Exception classes for SynapsAI client library
17
+ """
18
+
19
+ from typing import Optional, Dict, Any
20
+
21
+
22
+ class SynapsAIError(Exception):
23
+ """Base exception class for SynapsAI errors"""
24
+ pass
25
+
26
+
27
+ class APIError(SynapsAIError):
28
+ """Exception raised for API errors"""
29
+
30
+ def __init__(
31
+ self,
32
+ message: str,
33
+ status_code: Optional[int] = None,
34
+ response_data: Optional[Dict[str, Any]] = None,
35
+ ):
36
+ super().__init__(message)
37
+ self.message = message
38
+ self.status_code = status_code
39
+ self.response_data = response_data
40
+
41
+
42
+ class AuthenticationError(APIError):
43
+ """Exception raised for authentication errors"""
44
+ pass
45
+
46
+
47
+ class RateLimitError(APIError):
48
+ """Exception raised when rate limit is exceeded"""
49
+ pass
50
+
51
+
52
+ class ValidationError(SynapsAIError):
53
+ """Exception raised for validation errors"""
54
+ pass
55
+
56
+
57
+ class TimeoutError(SynapsAIError):
58
+ """Exception raised for timeout errors"""
59
+ pass
60
+
61
+
62
+ class ConnectionError(SynapsAIError):
63
+ """Exception raised for connection errors"""
64
+ pass