synapsai-python 0.1.0__py3-none-any.whl → 0.1.2__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 +3 -2
- synapsai/cli.py +168 -0
- synapsai/client.py +245 -84
- synapsai/resources/__init__.py +20 -1
- synapsai/resources/agents.py +203 -0
- synapsai/resources/chat.py +157 -58
- synapsai/resources/model_artifacts.py +209 -0
- synapsai/resources/responses.py +179 -0
- synapsai/resources/vector_stores.py +473 -0
- synapsai/types/__init__.py +40 -1
- synapsai/types/agents.py +80 -0
- synapsai/types/completion.py +25 -2
- synapsai/types/model_artifacts.py +68 -0
- synapsai/types/responses.py +83 -0
- synapsai/types/vector_stores.py +134 -0
- {synapsai_python-0.1.0.dist-info → synapsai_python-0.1.2.dist-info}/METADATA +30 -4
- {synapsai_python-0.1.0.dist-info → synapsai_python-0.1.2.dist-info}/RECORD +21 -11
- {synapsai_python-0.1.0.dist-info → synapsai_python-0.1.2.dist-info}/WHEEL +1 -1
- synapsai_python-0.1.2.dist-info/entry_points.txt +2 -0
- {synapsai_python-0.1.0.dist-info → synapsai_python-0.1.2.dist-info}/licenses/LICENSE +0 -0
- {synapsai_python-0.1.0.dist-info → synapsai_python-0.1.2.dist-info}/top_level.txt +0 -0
synapsai/__init__.py
CHANGED
|
@@ -5,12 +5,13 @@ A scalable and maintainable Python client library for interacting with SynapsAI
|
|
|
5
5
|
compatible with OpenAI API patterns.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
from .client import SynapsAI, AsyncSynapsAI
|
|
8
|
+
from .client import SynapsAI, AsyncSynapsAI, DEFAULT_UPLOAD_BASE_URL
|
|
9
9
|
from .types import *
|
|
10
10
|
from .resources import *
|
|
11
11
|
|
|
12
|
-
__version__ = "0.1.
|
|
12
|
+
__version__ = "0.1.2"
|
|
13
13
|
__all__ = [
|
|
14
14
|
"SynapsAI",
|
|
15
15
|
"AsyncSynapsAI",
|
|
16
|
+
"DEFAULT_UPLOAD_BASE_URL",
|
|
16
17
|
]
|
synapsai/cli.py
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
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
|
+
"""Command-line interface for SynapsAI uploads."""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import json
|
|
20
|
+
import os
|
|
21
|
+
from typing import List, Optional
|
|
22
|
+
|
|
23
|
+
import click
|
|
24
|
+
|
|
25
|
+
from .client import SynapsAI
|
|
26
|
+
from .exceptions import APIError
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _resolve_api_key(api_key: Optional[str]) -> str:
|
|
30
|
+
value = api_key or os.environ.get("SYNAPSAI_API_KEY")
|
|
31
|
+
if not value:
|
|
32
|
+
raise click.ClickException(
|
|
33
|
+
"API key required. Pass --api-key or set SYNAPSAI_API_KEY."
|
|
34
|
+
)
|
|
35
|
+
return value
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _normalize_base_url(host: str) -> str:
|
|
39
|
+
value = host.strip().rstrip("/")
|
|
40
|
+
if not value.startswith("http://") and not value.startswith("https://"):
|
|
41
|
+
value = f"https://{value}"
|
|
42
|
+
if not value.endswith("/v1"):
|
|
43
|
+
value = f"{value}/v1"
|
|
44
|
+
return value
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@click.group()
|
|
48
|
+
@click.version_option(version="0.1.0", prog_name="synapsai")
|
|
49
|
+
def main() -> None:
|
|
50
|
+
"""SynapsAI Cloud CLI."""
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@main.command("upload-model")
|
|
54
|
+
@click.argument("path", type=click.Path(exists=True, file_okay=True, dir_okay=True))
|
|
55
|
+
@click.option("--artifact-id", required=True, help="Existing model artifact id to upload into.")
|
|
56
|
+
@click.option(
|
|
57
|
+
"--host",
|
|
58
|
+
default=None,
|
|
59
|
+
help="API host (default: SYNAPSAI_API_BASE or https://api.synapsai.cloud/v1).",
|
|
60
|
+
)
|
|
61
|
+
@click.option("--api-key", default=None, help="API key (default: SYNAPSAI_API_KEY).")
|
|
62
|
+
@click.option("--timeout", default=3600.0, show_default=True, type=float, help="Request timeout seconds.")
|
|
63
|
+
def upload_model(
|
|
64
|
+
path: str,
|
|
65
|
+
artifact_id: str,
|
|
66
|
+
host: Optional[str],
|
|
67
|
+
api_key: Optional[str],
|
|
68
|
+
timeout: float,
|
|
69
|
+
) -> None:
|
|
70
|
+
"""Upload model files into an existing artifact on the API.
|
|
71
|
+
|
|
72
|
+
PATH may be a single file or a directory of model weights/config files.
|
|
73
|
+
Starts an upload session, streams files, then completes ingest.
|
|
74
|
+
Uses the same host as the rest of the API (``SYNAPSAI_API_BASE`` or
|
|
75
|
+
``https://api.synapsai.cloud/v1``).
|
|
76
|
+
"""
|
|
77
|
+
base_url = _normalize_base_url(host) if host else None
|
|
78
|
+
client = SynapsAI(api_key=_resolve_api_key(api_key), base_url=base_url, timeout=timeout)
|
|
79
|
+
|
|
80
|
+
def on_progress(relative: str, index: int, total: int) -> None:
|
|
81
|
+
click.echo(f"[{index}/{total}] uploading {relative}")
|
|
82
|
+
|
|
83
|
+
try:
|
|
84
|
+
artifact = client.model_artifacts.upload(
|
|
85
|
+
path,
|
|
86
|
+
artifact_id=artifact_id,
|
|
87
|
+
on_progress=on_progress,
|
|
88
|
+
)
|
|
89
|
+
except (APIError, OSError, ValueError) as exc:
|
|
90
|
+
raise click.ClickException(str(exc)) from exc
|
|
91
|
+
|
|
92
|
+
click.echo(
|
|
93
|
+
json.dumps(
|
|
94
|
+
{
|
|
95
|
+
"id": artifact.id,
|
|
96
|
+
"display_name": artifact.display_name,
|
|
97
|
+
"pipeline": artifact.pipeline,
|
|
98
|
+
"status": artifact.status,
|
|
99
|
+
"size_gb": artifact.size_gb,
|
|
100
|
+
},
|
|
101
|
+
indent=2,
|
|
102
|
+
)
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@main.command("upload-vector-store-files")
|
|
107
|
+
@click.argument("vector_store_id")
|
|
108
|
+
@click.argument("paths", nargs=-1, required=True, type=click.Path(exists=True))
|
|
109
|
+
@click.option(
|
|
110
|
+
"--host",
|
|
111
|
+
default=None,
|
|
112
|
+
help="API host (default: SYNAPSAI_API_BASE or https://api.synapsai.cloud/v1).",
|
|
113
|
+
)
|
|
114
|
+
@click.option("--api-key", default=None, help="API key (default: SYNAPSAI_API_KEY).")
|
|
115
|
+
@click.option(
|
|
116
|
+
"--attributes",
|
|
117
|
+
default=None,
|
|
118
|
+
help="Optional JSON object applied as metadata attributes on each upload.",
|
|
119
|
+
)
|
|
120
|
+
@click.option("--timeout", default=600.0, show_default=True, type=float, help="Request timeout seconds.")
|
|
121
|
+
def upload_vector_store_files(
|
|
122
|
+
vector_store_id: str,
|
|
123
|
+
paths: List[str],
|
|
124
|
+
host: Optional[str],
|
|
125
|
+
api_key: Optional[str],
|
|
126
|
+
attributes: Optional[str],
|
|
127
|
+
timeout: float,
|
|
128
|
+
) -> None:
|
|
129
|
+
"""Upload files or directories into a vector store.
|
|
130
|
+
|
|
131
|
+
Each PATH may be an individual file or a directory (recursively uploaded).
|
|
132
|
+
"""
|
|
133
|
+
parsed_attributes = None
|
|
134
|
+
if attributes:
|
|
135
|
+
try:
|
|
136
|
+
parsed_attributes = json.loads(attributes)
|
|
137
|
+
except json.JSONDecodeError as exc:
|
|
138
|
+
raise click.ClickException("--attributes must be valid JSON") from exc
|
|
139
|
+
if not isinstance(parsed_attributes, dict):
|
|
140
|
+
raise click.ClickException("--attributes must be a JSON object")
|
|
141
|
+
|
|
142
|
+
base_url = None
|
|
143
|
+
if host:
|
|
144
|
+
base_url = _normalize_base_url(host)
|
|
145
|
+
|
|
146
|
+
client = SynapsAI(
|
|
147
|
+
api_key=_resolve_api_key(api_key),
|
|
148
|
+
base_url=base_url,
|
|
149
|
+
timeout=timeout,
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
try:
|
|
153
|
+
results = client.vector_stores.files.upload_paths(
|
|
154
|
+
vector_store_id,
|
|
155
|
+
list(paths),
|
|
156
|
+
attributes=parsed_attributes,
|
|
157
|
+
)
|
|
158
|
+
except (APIError, OSError, ValueError) as exc:
|
|
159
|
+
raise click.ClickException(str(exc)) from exc
|
|
160
|
+
|
|
161
|
+
for item in results:
|
|
162
|
+
click.echo(f"{item.id}\t{item.status}\t{item.usage_bytes}")
|
|
163
|
+
|
|
164
|
+
click.echo(f"Uploaded {len(results)} file(s) to vector store {vector_store_id}")
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
if __name__ == "__main__":
|
|
168
|
+
main()
|
synapsai/client.py
CHANGED
|
@@ -49,6 +49,14 @@ from .resources import (
|
|
|
49
49
|
AsyncFillMaskResource,
|
|
50
50
|
RerankResource,
|
|
51
51
|
AsyncRerankResource,
|
|
52
|
+
VectorStoresResource,
|
|
53
|
+
AsyncVectorStoresResource,
|
|
54
|
+
ModelArtifactsResource,
|
|
55
|
+
AsyncModelArtifactsResource,
|
|
56
|
+
AgentsResource,
|
|
57
|
+
AsyncAgentsResource,
|
|
58
|
+
ResponsesResource,
|
|
59
|
+
AsyncResponsesResource,
|
|
52
60
|
)
|
|
53
61
|
from .exceptions import APIError, AuthenticationError
|
|
54
62
|
from .utils import build_url
|
|
@@ -56,6 +64,9 @@ from .logging import get_logger
|
|
|
56
64
|
|
|
57
65
|
logger = get_logger(__name__)
|
|
58
66
|
|
|
67
|
+
DEFAULT_BASE_URL = "https://api.synapsai.cloud/v1"
|
|
68
|
+
|
|
69
|
+
|
|
59
70
|
class BaseClient:
|
|
60
71
|
"""Base client with common functionality"""
|
|
61
72
|
|
|
@@ -91,7 +102,7 @@ class BaseClient:
|
|
|
91
102
|
base_url = os.environ.get("SYNAPSAI_API_BASE")
|
|
92
103
|
|
|
93
104
|
if base_url is None:
|
|
94
|
-
base_url =
|
|
105
|
+
base_url = DEFAULT_BASE_URL
|
|
95
106
|
|
|
96
107
|
self.api_key = api_key
|
|
97
108
|
self.base_url = base_url.rstrip("/")
|
|
@@ -208,6 +219,10 @@ class SynapsAI(BaseClient):
|
|
|
208
219
|
feature_extraction: FeatureExtractionResource
|
|
209
220
|
fill_mask: FillMaskResource
|
|
210
221
|
rerank: RerankResource
|
|
222
|
+
vector_stores: VectorStoresResource
|
|
223
|
+
model_artifacts: ModelArtifactsResource
|
|
224
|
+
agents: AgentsResource
|
|
225
|
+
responses: ResponsesResource
|
|
211
226
|
|
|
212
227
|
def __init__(self, api_key: Optional[str] = None, **kwargs):
|
|
213
228
|
super().__init__(api_key=api_key, **kwargs)
|
|
@@ -232,6 +247,10 @@ class SynapsAI(BaseClient):
|
|
|
232
247
|
self.feature_extraction = FeatureExtractionResource(self)
|
|
233
248
|
self.fill_mask = FillMaskResource(self)
|
|
234
249
|
self.rerank = RerankResource(self)
|
|
250
|
+
self.vector_stores = VectorStoresResource(self)
|
|
251
|
+
self.model_artifacts = ModelArtifactsResource(self)
|
|
252
|
+
self.agents = AgentsResource(self)
|
|
253
|
+
self.responses = ResponsesResource(self)
|
|
235
254
|
|
|
236
255
|
def __enter__(self):
|
|
237
256
|
return self
|
|
@@ -243,13 +262,56 @@ class SynapsAI(BaseClient):
|
|
|
243
262
|
"""Close the HTTP client"""
|
|
244
263
|
self._client.close()
|
|
245
264
|
|
|
265
|
+
def _prepare_request_kwargs(
|
|
266
|
+
self,
|
|
267
|
+
method: str,
|
|
268
|
+
endpoint: str,
|
|
269
|
+
json_data: Optional[Dict[str, Any]] = None,
|
|
270
|
+
data: Optional[Any] = None,
|
|
271
|
+
files: Optional[Any] = None,
|
|
272
|
+
content: Optional[Any] = None,
|
|
273
|
+
params: Optional[Dict[str, Any]] = None,
|
|
274
|
+
headers: Optional[Dict[str, Any]] = None,
|
|
275
|
+
stream: bool = False,
|
|
276
|
+
) -> Dict[str, Any]:
|
|
277
|
+
"""Build httpx request kwargs, clearing Content-Type for multipart/raw bodies."""
|
|
278
|
+
url = build_url(self.base_url, endpoint)
|
|
279
|
+
kwargs: Dict[str, Any] = {
|
|
280
|
+
"method": method,
|
|
281
|
+
"url": url,
|
|
282
|
+
"timeout": self.timeout,
|
|
283
|
+
}
|
|
284
|
+
if json_data is not None:
|
|
285
|
+
kwargs["json"] = json_data
|
|
286
|
+
if data is not None:
|
|
287
|
+
kwargs["data"] = data
|
|
288
|
+
if files is not None:
|
|
289
|
+
kwargs["files"] = files
|
|
290
|
+
if content is not None:
|
|
291
|
+
kwargs["content"] = content
|
|
292
|
+
if params is not None:
|
|
293
|
+
kwargs["params"] = params
|
|
294
|
+
if stream:
|
|
295
|
+
kwargs["stream"] = True
|
|
296
|
+
|
|
297
|
+
request_headers = dict(headers or {})
|
|
298
|
+
if files is not None or content is not None:
|
|
299
|
+
# Let httpx set multipart/raw content-type; drop JSON default.
|
|
300
|
+
request_headers.setdefault("Content-Type", None)
|
|
301
|
+
if request_headers:
|
|
302
|
+
kwargs["headers"] = request_headers
|
|
303
|
+
return kwargs
|
|
304
|
+
|
|
246
305
|
def _request(
|
|
247
306
|
self,
|
|
248
307
|
method: str,
|
|
249
308
|
endpoint: str,
|
|
250
309
|
json_data: Optional[Dict[str, Any]] = None,
|
|
251
|
-
data: Optional[
|
|
252
|
-
files: Optional[
|
|
310
|
+
data: Optional[Any] = None,
|
|
311
|
+
files: Optional[Any] = None,
|
|
312
|
+
content: Optional[Any] = None,
|
|
313
|
+
params: Optional[Dict[str, Any]] = None,
|
|
314
|
+
headers: Optional[Dict[str, Any]] = None,
|
|
253
315
|
stream: bool = False,
|
|
254
316
|
) -> httpx.Response:
|
|
255
317
|
"""
|
|
@@ -257,24 +319,17 @@ class SynapsAI(BaseClient):
|
|
|
257
319
|
|
|
258
320
|
Retries on network errors, timeouts, and server-side errors (429, 5xx).
|
|
259
321
|
"""
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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
|
|
322
|
+
base_kwargs = self._prepare_request_kwargs(
|
|
323
|
+
method=method,
|
|
324
|
+
endpoint=endpoint,
|
|
325
|
+
json_data=json_data,
|
|
326
|
+
data=data,
|
|
327
|
+
files=files,
|
|
328
|
+
content=content,
|
|
329
|
+
params=params,
|
|
330
|
+
headers=headers,
|
|
331
|
+
stream=stream,
|
|
332
|
+
)
|
|
278
333
|
|
|
279
334
|
attempt = 0
|
|
280
335
|
last_exc: Optional[BaseException] = None
|
|
@@ -325,26 +380,71 @@ class SynapsAI(BaseClient):
|
|
|
325
380
|
self,
|
|
326
381
|
endpoint: str,
|
|
327
382
|
json_data: Optional[Dict[str, Any]] = None,
|
|
328
|
-
data: Optional[
|
|
329
|
-
files: Optional[
|
|
383
|
+
data: Optional[Any] = None,
|
|
384
|
+
files: Optional[Any] = None,
|
|
385
|
+
content: Optional[Any] = None,
|
|
386
|
+
params: Optional[Dict[str, Any]] = None,
|
|
387
|
+
headers: Optional[Dict[str, Any]] = None,
|
|
330
388
|
) -> httpx.Response:
|
|
331
389
|
"""Make a POST request"""
|
|
332
|
-
return self._request(
|
|
333
|
-
|
|
334
|
-
|
|
390
|
+
return self._request(
|
|
391
|
+
"POST",
|
|
392
|
+
endpoint,
|
|
393
|
+
json_data=json_data,
|
|
394
|
+
data=data,
|
|
395
|
+
files=files,
|
|
396
|
+
content=content,
|
|
397
|
+
params=params,
|
|
398
|
+
headers=headers,
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
def _put(
|
|
402
|
+
self,
|
|
403
|
+
endpoint: str,
|
|
404
|
+
json_data: Optional[Dict[str, Any]] = None,
|
|
405
|
+
data: Optional[Any] = None,
|
|
406
|
+
files: Optional[Any] = None,
|
|
407
|
+
content: Optional[Any] = None,
|
|
408
|
+
params: Optional[Dict[str, Any]] = None,
|
|
409
|
+
headers: Optional[Dict[str, Any]] = None,
|
|
410
|
+
) -> httpx.Response:
|
|
411
|
+
"""Make a PUT request"""
|
|
412
|
+
return self._request(
|
|
413
|
+
"PUT",
|
|
414
|
+
endpoint,
|
|
415
|
+
json_data=json_data,
|
|
416
|
+
data=data,
|
|
417
|
+
files=files,
|
|
418
|
+
content=content,
|
|
419
|
+
params=params,
|
|
420
|
+
headers=headers,
|
|
421
|
+
)
|
|
422
|
+
|
|
423
|
+
def _get(
|
|
424
|
+
self,
|
|
425
|
+
endpoint: str,
|
|
426
|
+
params: Optional[Dict[str, Any]] = None,
|
|
427
|
+
) -> httpx.Response:
|
|
335
428
|
"""Make a GET request"""
|
|
336
|
-
return self._request("GET", endpoint)
|
|
429
|
+
return self._request("GET", endpoint, params=params)
|
|
337
430
|
|
|
338
|
-
def _delete(
|
|
431
|
+
def _delete(
|
|
432
|
+
self,
|
|
433
|
+
endpoint: str,
|
|
434
|
+
params: Optional[Dict[str, Any]] = None,
|
|
435
|
+
) -> httpx.Response:
|
|
339
436
|
"""Make a DELETE request"""
|
|
340
|
-
return self._request("DELETE", endpoint)
|
|
437
|
+
return self._request("DELETE", endpoint, params=params)
|
|
341
438
|
|
|
342
439
|
def _stream_response(
|
|
343
440
|
self,
|
|
344
441
|
endpoint: str,
|
|
345
442
|
json_data: Optional[Dict[str, Any]] = None,
|
|
346
|
-
data: Optional[
|
|
347
|
-
files: Optional[
|
|
443
|
+
data: Optional[Any] = None,
|
|
444
|
+
files: Optional[Any] = None,
|
|
445
|
+
content: Optional[Any] = None,
|
|
446
|
+
params: Optional[Dict[str, Any]] = None,
|
|
447
|
+
headers: Optional[Dict[str, Any]] = None,
|
|
348
448
|
) -> Iterator[Dict[str, Any]]:
|
|
349
449
|
"""
|
|
350
450
|
Stream response data from a POST endpoint.
|
|
@@ -352,15 +452,16 @@ class SynapsAI(BaseClient):
|
|
|
352
452
|
The attempt to establish the stream will be retried using the same backoff rules.
|
|
353
453
|
Once the stream is established, streaming errors are raised as-is.
|
|
354
454
|
"""
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
455
|
+
base_kwargs = self._prepare_request_kwargs(
|
|
456
|
+
method="POST",
|
|
457
|
+
endpoint=endpoint,
|
|
458
|
+
json_data=json_data,
|
|
459
|
+
data=data,
|
|
460
|
+
files=files,
|
|
461
|
+
content=content,
|
|
462
|
+
params=params,
|
|
463
|
+
headers=headers,
|
|
464
|
+
)
|
|
364
465
|
|
|
365
466
|
attempt = 0
|
|
366
467
|
while attempt < self.max_retries:
|
|
@@ -385,8 +486,10 @@ class SynapsAI(BaseClient):
|
|
|
385
486
|
line = line.strip()
|
|
386
487
|
if not line:
|
|
387
488
|
continue
|
|
388
|
-
if line.startswith("data:
|
|
389
|
-
data_line = line[
|
|
489
|
+
if line.startswith("data:"):
|
|
490
|
+
data_line = line[5:]
|
|
491
|
+
if data_line.startswith(" "):
|
|
492
|
+
data_line = data_line[1:]
|
|
390
493
|
if data_line == "[DONE]":
|
|
391
494
|
return
|
|
392
495
|
try:
|
|
@@ -410,10 +513,10 @@ class SynapsAI(BaseClient):
|
|
|
410
513
|
|
|
411
514
|
raise APIError("Failed to establish stream after retries")
|
|
412
515
|
|
|
413
|
-
def _get_stream(self, endpoint: str) -> httpx.Response:
|
|
516
|
+
def _get_stream(self, endpoint: str, params: Optional[Dict[str, Any]] = None) -> httpx.Response:
|
|
414
517
|
"""Make a streaming GET request for binary content (e.g. video download)."""
|
|
415
518
|
url = build_url(self.base_url, endpoint)
|
|
416
|
-
request = self._client.build_request("GET", url, timeout=self.timeout)
|
|
519
|
+
request = self._client.build_request("GET", url, timeout=self.timeout, params=params)
|
|
417
520
|
response = self._client.send(request, stream=True)
|
|
418
521
|
if response.status_code >= 400:
|
|
419
522
|
response.read()
|
|
@@ -435,6 +538,10 @@ class AsyncSynapsAI(BaseClient):
|
|
|
435
538
|
feature_extraction: AsyncFeatureExtractionResource
|
|
436
539
|
fill_mask: AsyncFillMaskResource
|
|
437
540
|
rerank: AsyncRerankResource
|
|
541
|
+
vector_stores: AsyncVectorStoresResource
|
|
542
|
+
model_artifacts: AsyncModelArtifactsResource
|
|
543
|
+
agents: AsyncAgentsResource
|
|
544
|
+
responses: AsyncResponsesResource
|
|
438
545
|
|
|
439
546
|
def __init__(self, api_key: Optional[str] = None, **kwargs):
|
|
440
547
|
super().__init__(api_key=api_key, **kwargs)
|
|
@@ -458,6 +565,10 @@ class AsyncSynapsAI(BaseClient):
|
|
|
458
565
|
self.feature_extraction = AsyncFeatureExtractionResource(self)
|
|
459
566
|
self.fill_mask = AsyncFillMaskResource(self)
|
|
460
567
|
self.rerank = AsyncRerankResource(self)
|
|
568
|
+
self.vector_stores = AsyncVectorStoresResource(self)
|
|
569
|
+
self.model_artifacts = AsyncModelArtifactsResource(self)
|
|
570
|
+
self.agents = AsyncAgentsResource(self)
|
|
571
|
+
self.responses = AsyncResponsesResource(self)
|
|
461
572
|
|
|
462
573
|
async def __aenter__(self):
|
|
463
574
|
return self
|
|
@@ -474,24 +585,23 @@ class AsyncSynapsAI(BaseClient):
|
|
|
474
585
|
method: str,
|
|
475
586
|
endpoint: str,
|
|
476
587
|
json_data: Optional[Dict[str, Any]] = None,
|
|
477
|
-
data: Optional[
|
|
478
|
-
files: Optional[
|
|
588
|
+
data: Optional[Any] = None,
|
|
589
|
+
files: Optional[Any] = None,
|
|
590
|
+
content: Optional[Any] = None,
|
|
591
|
+
params: Optional[Dict[str, Any]] = None,
|
|
592
|
+
headers: Optional[Dict[str, Any]] = None,
|
|
479
593
|
) -> httpx.Response:
|
|
480
594
|
"""Make an async request to the API with retries."""
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
if data:
|
|
492
|
-
base_kwargs["data"] = data
|
|
493
|
-
if files:
|
|
494
|
-
base_kwargs["files"] = files
|
|
595
|
+
base_kwargs = self._prepare_request_kwargs(
|
|
596
|
+
method=method,
|
|
597
|
+
endpoint=endpoint,
|
|
598
|
+
json_data=json_data,
|
|
599
|
+
data=data,
|
|
600
|
+
files=files,
|
|
601
|
+
content=content,
|
|
602
|
+
params=params,
|
|
603
|
+
headers=headers,
|
|
604
|
+
)
|
|
495
605
|
|
|
496
606
|
attempt = 0
|
|
497
607
|
last_exc: Optional[BaseException] = None
|
|
@@ -536,26 +646,71 @@ class AsyncSynapsAI(BaseClient):
|
|
|
536
646
|
self,
|
|
537
647
|
endpoint: str,
|
|
538
648
|
json_data: Optional[Dict[str, Any]] = None,
|
|
539
|
-
data: Optional[
|
|
540
|
-
files: Optional[
|
|
649
|
+
data: Optional[Any] = None,
|
|
650
|
+
files: Optional[Any] = None,
|
|
651
|
+
content: Optional[Any] = None,
|
|
652
|
+
params: Optional[Dict[str, Any]] = None,
|
|
653
|
+
headers: Optional[Dict[str, Any]] = None,
|
|
541
654
|
) -> httpx.Response:
|
|
542
655
|
"""Make an async POST request"""
|
|
543
|
-
return await self._request(
|
|
544
|
-
|
|
545
|
-
|
|
656
|
+
return await self._request(
|
|
657
|
+
"POST",
|
|
658
|
+
endpoint,
|
|
659
|
+
json_data=json_data,
|
|
660
|
+
data=data,
|
|
661
|
+
files=files,
|
|
662
|
+
content=content,
|
|
663
|
+
params=params,
|
|
664
|
+
headers=headers,
|
|
665
|
+
)
|
|
666
|
+
|
|
667
|
+
async def _put(
|
|
668
|
+
self,
|
|
669
|
+
endpoint: str,
|
|
670
|
+
json_data: Optional[Dict[str, Any]] = None,
|
|
671
|
+
data: Optional[Any] = None,
|
|
672
|
+
files: Optional[Any] = None,
|
|
673
|
+
content: Optional[Any] = None,
|
|
674
|
+
params: Optional[Dict[str, Any]] = None,
|
|
675
|
+
headers: Optional[Dict[str, Any]] = None,
|
|
676
|
+
) -> httpx.Response:
|
|
677
|
+
"""Make an async PUT request"""
|
|
678
|
+
return await self._request(
|
|
679
|
+
"PUT",
|
|
680
|
+
endpoint,
|
|
681
|
+
json_data=json_data,
|
|
682
|
+
data=data,
|
|
683
|
+
files=files,
|
|
684
|
+
content=content,
|
|
685
|
+
params=params,
|
|
686
|
+
headers=headers,
|
|
687
|
+
)
|
|
688
|
+
|
|
689
|
+
async def _get(
|
|
690
|
+
self,
|
|
691
|
+
endpoint: str,
|
|
692
|
+
params: Optional[Dict[str, Any]] = None,
|
|
693
|
+
) -> httpx.Response:
|
|
546
694
|
"""Make a GET request"""
|
|
547
|
-
return await self._request("GET", endpoint=endpoint)
|
|
695
|
+
return await self._request("GET", endpoint=endpoint, params=params)
|
|
548
696
|
|
|
549
|
-
async def _delete(
|
|
697
|
+
async def _delete(
|
|
698
|
+
self,
|
|
699
|
+
endpoint: str,
|
|
700
|
+
params: Optional[Dict[str, Any]] = None,
|
|
701
|
+
) -> httpx.Response:
|
|
550
702
|
"""Make a DELETE request"""
|
|
551
|
-
return await self._request("DELETE", endpoint=endpoint)
|
|
703
|
+
return await self._request("DELETE", endpoint=endpoint, params=params)
|
|
552
704
|
|
|
553
705
|
async def _stream_response(
|
|
554
706
|
self,
|
|
555
707
|
endpoint: str,
|
|
556
708
|
json_data: Optional[Dict[str, Any]] = None,
|
|
557
|
-
data: Optional[
|
|
558
|
-
files: Optional[
|
|
709
|
+
data: Optional[Any] = None,
|
|
710
|
+
files: Optional[Any] = None,
|
|
711
|
+
content: Optional[Any] = None,
|
|
712
|
+
params: Optional[Dict[str, Any]] = None,
|
|
713
|
+
headers: Optional[Dict[str, Any]] = None,
|
|
559
714
|
) -> AsyncIterator[Dict[str, Any]]:
|
|
560
715
|
"""
|
|
561
716
|
Stream response data from a POST endpoint (async).
|
|
@@ -563,16 +718,16 @@ class AsyncSynapsAI(BaseClient):
|
|
|
563
718
|
The attempt to establish the stream will be retried using the same backoff rules.
|
|
564
719
|
Once the stream is established, streaming errors are raised as-is.
|
|
565
720
|
"""
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
721
|
+
base_kwargs = self._prepare_request_kwargs(
|
|
722
|
+
method="POST",
|
|
723
|
+
endpoint=endpoint,
|
|
724
|
+
json_data=json_data,
|
|
725
|
+
data=data,
|
|
726
|
+
files=files,
|
|
727
|
+
content=content,
|
|
728
|
+
params=params,
|
|
729
|
+
headers=headers,
|
|
730
|
+
)
|
|
576
731
|
|
|
577
732
|
attempt = 0
|
|
578
733
|
while attempt < self.max_retries:
|
|
@@ -595,8 +750,10 @@ class AsyncSynapsAI(BaseClient):
|
|
|
595
750
|
line = raw_line.strip()
|
|
596
751
|
if not line:
|
|
597
752
|
continue
|
|
598
|
-
if line.startswith("data:
|
|
599
|
-
data_line = line[
|
|
753
|
+
if line.startswith("data:"):
|
|
754
|
+
data_line = line[5:]
|
|
755
|
+
if data_line.startswith(" "):
|
|
756
|
+
data_line = data_line[1:]
|
|
600
757
|
if data_line == "[DONE]":
|
|
601
758
|
return
|
|
602
759
|
try:
|
|
@@ -617,10 +774,14 @@ class AsyncSynapsAI(BaseClient):
|
|
|
617
774
|
|
|
618
775
|
raise APIError("Failed to establish stream after retries")
|
|
619
776
|
|
|
620
|
-
async def _get_stream(
|
|
777
|
+
async def _get_stream(
|
|
778
|
+
self,
|
|
779
|
+
endpoint: str,
|
|
780
|
+
params: Optional[Dict[str, Any]] = None,
|
|
781
|
+
) -> httpx.Response:
|
|
621
782
|
"""Make an async streaming GET request for binary content (e.g. video download)."""
|
|
622
783
|
url = build_url(self.base_url, endpoint)
|
|
623
|
-
request = self._client.build_request("GET", url, timeout=self.timeout)
|
|
784
|
+
request = self._client.build_request("GET", url, timeout=self.timeout, params=params)
|
|
624
785
|
response = await self._client.send(request, stream=True)
|
|
625
786
|
if response.status_code >= 400:
|
|
626
787
|
await response.aread()
|