seekrai 0.0.1__py3-none-any.whl → 0.1.1__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.
- seekrai/__init__.py +0 -15
- seekrai/abstract/api_requestor.py +121 -297
- seekrai/client.py +10 -11
- seekrai/constants.py +36 -16
- seekrai/error.py +1 -8
- seekrai/filemanager.py +40 -79
- seekrai/resources/chat/completions.py +13 -13
- seekrai/resources/completions.py +4 -4
- seekrai/resources/embeddings.py +4 -2
- seekrai/resources/files.py +17 -9
- seekrai/resources/finetune.py +57 -82
- seekrai/resources/images.py +2 -2
- seekrai/resources/models.py +115 -15
- seekrai/types/__init__.py +5 -4
- seekrai/types/common.py +1 -2
- seekrai/types/files.py +23 -19
- seekrai/types/finetune.py +20 -26
- seekrai/types/models.py +26 -20
- seekrai/utils/_log.py +3 -3
- seekrai/utils/api_helpers.py +2 -2
- seekrai/utils/tools.py +1 -1
- seekrai-0.1.1.dist-info/METADATA +165 -0
- seekrai-0.1.1.dist-info/RECORD +39 -0
- seekrai/cli/__init__.py +0 -0
- seekrai/cli/api/__init__.py +0 -0
- seekrai/cli/api/chat.py +0 -245
- seekrai/cli/api/completions.py +0 -107
- seekrai/cli/api/files.py +0 -125
- seekrai/cli/api/finetune.py +0 -175
- seekrai/cli/api/images.py +0 -82
- seekrai/cli/api/models.py +0 -42
- seekrai/cli/cli.py +0 -77
- seekrai/legacy/__init__.py +0 -0
- seekrai/legacy/base.py +0 -27
- seekrai/legacy/complete.py +0 -91
- seekrai/legacy/embeddings.py +0 -25
- seekrai/legacy/files.py +0 -140
- seekrai/legacy/finetune.py +0 -173
- seekrai/legacy/images.py +0 -25
- seekrai/legacy/models.py +0 -44
- seekrai-0.0.1.dist-info/METADATA +0 -401
- seekrai-0.0.1.dist-info/RECORD +0 -56
- {seekrai-0.0.1.dist-info → seekrai-0.1.1.dist-info}/LICENSE +0 -0
- {seekrai-0.0.1.dist-info → seekrai-0.1.1.dist-info}/WHEEL +0 -0
- {seekrai-0.0.1.dist-info → seekrai-0.1.1.dist-info}/entry_points.txt +0 -0
seekrai/__init__.py
CHANGED
|
@@ -23,30 +23,15 @@ log: str | None = None # Set to either 'debug' or 'info', controls console logg
|
|
|
23
23
|
|
|
24
24
|
if TYPE_CHECKING:
|
|
25
25
|
import requests
|
|
26
|
-
from aiohttp import ClientSession
|
|
27
26
|
|
|
28
27
|
requestssession: "requests.Session" | Callable[[], "requests.Session"] | None = None
|
|
29
28
|
|
|
30
|
-
aiosession: ContextVar["ClientSession" | None] = ContextVar(
|
|
31
|
-
"aiohttp-session", default=None
|
|
32
|
-
)
|
|
33
|
-
|
|
34
29
|
from seekrai.client import AsyncClient, AsyncSeekrFlow, Client, SeekrFlow
|
|
35
30
|
|
|
36
31
|
|
|
37
32
|
api_key: str | None = None # To be deprecated in the next major release
|
|
38
33
|
|
|
39
|
-
# Legacy functions
|
|
40
|
-
from seekrai.legacy.complete import AsyncComplete, Complete, Completion
|
|
41
|
-
from seekrai.legacy.embeddings import Embeddings
|
|
42
|
-
from seekrai.legacy.files import Files
|
|
43
|
-
from seekrai.legacy.finetune import Finetune
|
|
44
|
-
from seekrai.legacy.images import Image
|
|
45
|
-
from seekrai.legacy.models import Models
|
|
46
|
-
|
|
47
|
-
|
|
48
34
|
__all__ = [
|
|
49
|
-
"aiosession",
|
|
50
35
|
"constants",
|
|
51
36
|
"version",
|
|
52
37
|
"SeekrFlow",
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import asyncio
|
|
4
3
|
import email.utils
|
|
5
4
|
import json
|
|
6
5
|
import sys
|
|
@@ -10,34 +9,30 @@ from json import JSONDecodeError
|
|
|
10
9
|
from random import random
|
|
11
10
|
from typing import (
|
|
12
11
|
Any,
|
|
13
|
-
AsyncContextManager,
|
|
14
12
|
AsyncGenerator,
|
|
13
|
+
AsyncIterator,
|
|
15
14
|
Dict,
|
|
16
15
|
Iterator,
|
|
16
|
+
Mapping,
|
|
17
17
|
Tuple,
|
|
18
18
|
overload,
|
|
19
19
|
)
|
|
20
20
|
from urllib.parse import urlencode, urlsplit, urlunsplit
|
|
21
21
|
|
|
22
|
-
import aiohttp
|
|
23
|
-
import requests
|
|
24
|
-
from tqdm.utils import CallbackIOWrapper
|
|
25
|
-
|
|
26
22
|
|
|
27
23
|
if sys.version_info >= (3, 8):
|
|
28
24
|
from typing import Literal
|
|
29
25
|
else:
|
|
30
26
|
from typing_extensions import Literal
|
|
31
27
|
|
|
32
|
-
import
|
|
28
|
+
import httpx
|
|
29
|
+
|
|
33
30
|
from seekrai import error, utils
|
|
34
31
|
from seekrai.constants import (
|
|
35
32
|
BASE_URL,
|
|
36
33
|
INITIAL_RETRY_DELAY,
|
|
37
|
-
MAX_CONNECTION_RETRIES,
|
|
38
34
|
MAX_RETRIES,
|
|
39
35
|
MAX_RETRY_DELAY,
|
|
40
|
-
MAX_SESSION_LIFETIME_SECS,
|
|
41
36
|
TIMEOUT_SECS,
|
|
42
37
|
)
|
|
43
38
|
from seekrai.seekrflow_response import SeekrFlowResponse
|
|
@@ -58,43 +53,32 @@ def _build_api_url(url: str, query: str) -> str:
|
|
|
58
53
|
return str(urlunsplit((scheme, netloc, path, query, fragment)))
|
|
59
54
|
|
|
60
55
|
|
|
61
|
-
def
|
|
62
|
-
if
|
|
63
|
-
if
|
|
64
|
-
return seekrai.requestssession
|
|
65
|
-
return seekrai.requestssession()
|
|
66
|
-
s = requests.Session()
|
|
67
|
-
s.mount(
|
|
68
|
-
"https://",
|
|
69
|
-
requests.adapters.HTTPAdapter(max_retries=max_retries),
|
|
70
|
-
)
|
|
71
|
-
return s
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
def parse_stream_helper(line: bytes) -> str | None:
|
|
75
|
-
if line and line.startswith(b"data:"):
|
|
76
|
-
if line.startswith(b"data: "):
|
|
56
|
+
def parse_stream_helper(line: str) -> str | None:
|
|
57
|
+
if line and line.startswith("data:"):
|
|
58
|
+
if line.startswith("data: "):
|
|
77
59
|
# SSE event may be valid when it contains whitespace
|
|
78
|
-
line = line[len(
|
|
60
|
+
line = line[len("data: ") :]
|
|
79
61
|
else:
|
|
80
|
-
line = line[len(
|
|
81
|
-
if line.strip() ==
|
|
62
|
+
line = line[len("data:") :]
|
|
63
|
+
if line.strip() == "[DONE]":
|
|
82
64
|
# return here will cause GeneratorExit exception in urllib3
|
|
83
65
|
# and it will close http connection with TCP Reset
|
|
84
66
|
return None
|
|
85
67
|
else:
|
|
86
|
-
return line
|
|
68
|
+
return line
|
|
87
69
|
return None
|
|
88
70
|
|
|
89
71
|
|
|
90
|
-
def parse_stream(rbody: Iterator[
|
|
72
|
+
def parse_stream(rbody: Iterator[str]) -> Iterator[str]:
|
|
91
73
|
for line in rbody:
|
|
92
74
|
_line = parse_stream_helper(line)
|
|
93
75
|
if _line is not None:
|
|
94
76
|
yield _line
|
|
95
77
|
|
|
96
78
|
|
|
97
|
-
async def parse_stream_async(
|
|
79
|
+
async def parse_stream_async(
|
|
80
|
+
rbody: AsyncIterator[str],
|
|
81
|
+
) -> AsyncGenerator[str, Any]:
|
|
98
82
|
async for line in rbody:
|
|
99
83
|
_line = parse_stream_helper(line)
|
|
100
84
|
if _line is not None:
|
|
@@ -169,42 +153,12 @@ class APIRequestor:
|
|
|
169
153
|
timeout = sleep_seconds * jitter
|
|
170
154
|
return timeout if timeout >= 0 else 0
|
|
171
155
|
|
|
172
|
-
def _retry_request(
|
|
173
|
-
self,
|
|
174
|
-
options: SeekrFlowRequest,
|
|
175
|
-
remaining_retries: int,
|
|
176
|
-
response_headers: Dict[str, Any] | None,
|
|
177
|
-
*,
|
|
178
|
-
stream: bool,
|
|
179
|
-
request_timeout: float | Tuple[float, float] | None = None,
|
|
180
|
-
) -> requests.Response:
|
|
181
|
-
remaining = remaining_retries - 1
|
|
182
|
-
if remaining == 1:
|
|
183
|
-
utils.log_debug("1 retry left")
|
|
184
|
-
else:
|
|
185
|
-
utils.log_debug(f"{remaining} retries left")
|
|
186
|
-
|
|
187
|
-
timeout = self._calculate_retry_timeout(remaining, response_headers)
|
|
188
|
-
("Retrying request to %s in %f seconds", options.url, timeout)
|
|
189
|
-
|
|
190
|
-
# In a synchronous context we are blocking the entire thread. Up to the library user to run the client in a
|
|
191
|
-
# different thread if necessary.
|
|
192
|
-
time.sleep(timeout)
|
|
193
|
-
|
|
194
|
-
return self.request_raw(
|
|
195
|
-
options=options,
|
|
196
|
-
stream=stream,
|
|
197
|
-
request_timeout=request_timeout,
|
|
198
|
-
remaining_retries=remaining,
|
|
199
|
-
)
|
|
200
|
-
|
|
201
156
|
@overload
|
|
202
157
|
def request(
|
|
203
158
|
self,
|
|
204
159
|
options: SeekrFlowRequest,
|
|
205
160
|
stream: Literal[True],
|
|
206
|
-
|
|
207
|
-
request_timeout: float | Tuple[float, float] | None = ...,
|
|
161
|
+
request_timeout: float | None = ...,
|
|
208
162
|
) -> Tuple[Iterator[SeekrFlowResponse], bool, str]:
|
|
209
163
|
pass
|
|
210
164
|
|
|
@@ -213,8 +167,7 @@ class APIRequestor:
|
|
|
213
167
|
self,
|
|
214
168
|
options: SeekrFlowRequest,
|
|
215
169
|
stream: Literal[False] = ...,
|
|
216
|
-
|
|
217
|
-
request_timeout: float | Tuple[float, float] | None = ...,
|
|
170
|
+
request_timeout: float | None = ...,
|
|
218
171
|
) -> Tuple[SeekrFlowResponse, bool, str]:
|
|
219
172
|
pass
|
|
220
173
|
|
|
@@ -223,8 +176,7 @@ class APIRequestor:
|
|
|
223
176
|
self,
|
|
224
177
|
options: SeekrFlowRequest,
|
|
225
178
|
stream: bool = ...,
|
|
226
|
-
|
|
227
|
-
request_timeout: float | Tuple[float, float] | None = ...,
|
|
179
|
+
request_timeout: float | None = ...,
|
|
228
180
|
) -> Tuple[SeekrFlowResponse | Iterator[SeekrFlowResponse], bool, str]:
|
|
229
181
|
pass
|
|
230
182
|
|
|
@@ -232,29 +184,22 @@ class APIRequestor:
|
|
|
232
184
|
self,
|
|
233
185
|
options: SeekrFlowRequest,
|
|
234
186
|
stream: bool = False,
|
|
235
|
-
|
|
236
|
-
request_timeout: float | Tuple[float, float] | None = None,
|
|
187
|
+
request_timeout: float | None = None,
|
|
237
188
|
) -> Tuple[
|
|
238
189
|
SeekrFlowResponse | Iterator[SeekrFlowResponse],
|
|
239
190
|
bool,
|
|
240
191
|
str | None,
|
|
241
192
|
]:
|
|
242
|
-
result = self.request_raw(
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
stream=stream,
|
|
246
|
-
request_timeout=request_timeout,
|
|
247
|
-
)
|
|
248
|
-
|
|
249
|
-
resp, got_stream = self._interpret_response(result, stream)
|
|
250
|
-
return resp, got_stream, self.api_key
|
|
193
|
+
result = self.request_raw(options, stream, request_timeout)
|
|
194
|
+
resp = self._interpret_response(result, stream)
|
|
195
|
+
return resp, stream, self.api_key
|
|
251
196
|
|
|
252
197
|
@overload
|
|
253
198
|
async def arequest(
|
|
254
199
|
self,
|
|
255
200
|
options: SeekrFlowRequest,
|
|
256
201
|
stream: Literal[True],
|
|
257
|
-
request_timeout: float |
|
|
202
|
+
request_timeout: float | None = ...,
|
|
258
203
|
) -> Tuple[AsyncGenerator[SeekrFlowResponse, None], bool, str]:
|
|
259
204
|
pass
|
|
260
205
|
|
|
@@ -264,7 +209,7 @@ class APIRequestor:
|
|
|
264
209
|
options: SeekrFlowRequest,
|
|
265
210
|
*,
|
|
266
211
|
stream: Literal[True],
|
|
267
|
-
request_timeout: float |
|
|
212
|
+
request_timeout: float | None = ...,
|
|
268
213
|
) -> Tuple[AsyncGenerator[SeekrFlowResponse, None], bool, str]:
|
|
269
214
|
pass
|
|
270
215
|
|
|
@@ -273,7 +218,7 @@ class APIRequestor:
|
|
|
273
218
|
self,
|
|
274
219
|
options: SeekrFlowRequest,
|
|
275
220
|
stream: Literal[False] = ...,
|
|
276
|
-
request_timeout: float |
|
|
221
|
+
request_timeout: float | None = ...,
|
|
277
222
|
) -> Tuple[SeekrFlowResponse, bool, str]:
|
|
278
223
|
pass
|
|
279
224
|
|
|
@@ -282,7 +227,7 @@ class APIRequestor:
|
|
|
282
227
|
self,
|
|
283
228
|
options: SeekrFlowRequest,
|
|
284
229
|
stream: bool = ...,
|
|
285
|
-
request_timeout: float |
|
|
230
|
+
request_timeout: float | None = ...,
|
|
286
231
|
) -> Tuple[SeekrFlowResponse | AsyncGenerator[SeekrFlowResponse, None], bool, str]:
|
|
287
232
|
pass
|
|
288
233
|
|
|
@@ -290,43 +235,49 @@ class APIRequestor:
|
|
|
290
235
|
self,
|
|
291
236
|
options: SeekrFlowRequest,
|
|
292
237
|
stream: bool = False,
|
|
293
|
-
request_timeout: float |
|
|
294
|
-
) -> Tuple[
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
238
|
+
request_timeout: float | None = None,
|
|
239
|
+
) -> Tuple[
|
|
240
|
+
SeekrFlowResponse | AsyncGenerator[SeekrFlowResponse, None], bool, str | None
|
|
241
|
+
]:
|
|
242
|
+
abs_url, headers, data = self._prepare_request_raw(options, False)
|
|
243
|
+
client = httpx.AsyncClient(http2=True)
|
|
244
|
+
req = client.build_request(
|
|
245
|
+
options.method,
|
|
246
|
+
abs_url,
|
|
247
|
+
headers=headers,
|
|
248
|
+
data=data, # type: ignore
|
|
249
|
+
files=options.files,
|
|
250
|
+
timeout=request_timeout or self.timeout,
|
|
251
|
+
)
|
|
298
252
|
try:
|
|
299
|
-
result = await
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
253
|
+
result = await client.send(req, stream=stream)
|
|
254
|
+
except httpx.TimeoutException as e:
|
|
255
|
+
utils.log_debug("Encountered httpx.TimeoutException")
|
|
256
|
+
|
|
257
|
+
raise error.Timeout("Request timed out: {}".format(e)) from e
|
|
258
|
+
except httpx.RequestError as e:
|
|
259
|
+
utils.log_debug("Encountered httpx.RequestError")
|
|
260
|
+
|
|
261
|
+
raise error.APIConnectionError(
|
|
262
|
+
"Error communicating with API: {}".format(e)
|
|
263
|
+
) from e
|
|
264
|
+
|
|
265
|
+
# retry on 5XX error or rate-limit
|
|
266
|
+
if 500 <= result.status_code < 600 or result.status_code == 429:
|
|
267
|
+
utils.log_debug(
|
|
268
|
+
f"Encountered httpx.HTTPError. Error code: {result.status_code}"
|
|
303
269
|
)
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
try:
|
|
316
|
-
async for r in resp:
|
|
317
|
-
yield r
|
|
318
|
-
finally:
|
|
319
|
-
# Close the request before exiting session context. Important to do it here
|
|
320
|
-
# as if stream is not fully exhausted, we need to close the request nevertheless.
|
|
321
|
-
result.release()
|
|
322
|
-
await ctx.__aexit__(None, None, None)
|
|
323
|
-
|
|
324
|
-
return wrap_resp(), got_stream, self.api_key # type: ignore
|
|
325
|
-
else:
|
|
326
|
-
# Close the request before exiting session context.
|
|
327
|
-
result.release()
|
|
328
|
-
await ctx.__aexit__(None, None, None)
|
|
329
|
-
return resp, got_stream, self.api_key # type: ignore
|
|
270
|
+
|
|
271
|
+
utils.log_debug(
|
|
272
|
+
"SeekrFlow API response",
|
|
273
|
+
path=abs_url,
|
|
274
|
+
response_code=result.status_code,
|
|
275
|
+
processing_ms=result.headers.get("x-total-time"),
|
|
276
|
+
request_id=result.headers.get("CF-RAY"),
|
|
277
|
+
)
|
|
278
|
+
resp = await self._interpret_async_response(result, stream)
|
|
279
|
+
|
|
280
|
+
return resp, stream, self.api_key
|
|
330
281
|
|
|
331
282
|
@classmethod
|
|
332
283
|
def handle_error_response(
|
|
@@ -425,12 +376,11 @@ class APIRequestor:
|
|
|
425
376
|
self,
|
|
426
377
|
options: SeekrFlowRequest,
|
|
427
378
|
absolute: bool = False,
|
|
428
|
-
) -> Tuple[str, Dict[str, str],
|
|
379
|
+
) -> Tuple[str, Dict[str, str], Mapping[str, Any] | None | str]:
|
|
429
380
|
abs_url = options.url if absolute else "%s%s" % (self.api_base, options.url)
|
|
430
381
|
headers = self._validate_headers(options.headers or self.supplied_headers)
|
|
431
382
|
|
|
432
|
-
data = None
|
|
433
|
-
data_bytes = None
|
|
383
|
+
data: Mapping[str, Any] | None | str = None
|
|
434
384
|
if options.method.lower() == "get" or options.method.lower() == "delete":
|
|
435
385
|
if options.params:
|
|
436
386
|
encoded_params = urlencode(
|
|
@@ -438,11 +388,9 @@ class APIRequestor:
|
|
|
438
388
|
)
|
|
439
389
|
abs_url = _build_api_url(abs_url, encoded_params)
|
|
440
390
|
elif options.method.lower() in {"post", "put"}:
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
data_bytes = json.dumps(options.params).encode()
|
|
445
|
-
headers["Content-Type"] = "application/json"
|
|
391
|
+
data = options.params
|
|
392
|
+
if options.params and not options.files:
|
|
393
|
+
data = json.dumps(data)
|
|
446
394
|
|
|
447
395
|
else:
|
|
448
396
|
raise error.APIConnectionError(
|
|
@@ -458,87 +406,44 @@ class APIRequestor:
|
|
|
458
406
|
"Request to SeekrFlow API",
|
|
459
407
|
method=options.method,
|
|
460
408
|
path=abs_url,
|
|
461
|
-
post_data=
|
|
409
|
+
post_data=data,
|
|
462
410
|
headers=json.dumps(headers),
|
|
463
411
|
)
|
|
464
412
|
|
|
465
|
-
return abs_url, headers,
|
|
413
|
+
return abs_url, headers, data
|
|
466
414
|
|
|
467
415
|
def request_raw(
|
|
468
416
|
self,
|
|
469
417
|
options: SeekrFlowRequest,
|
|
470
|
-
remaining_retries: int,
|
|
471
|
-
*,
|
|
472
418
|
stream: bool = False,
|
|
473
|
-
request_timeout: float |
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
_thread_context.session = _make_session(MAX_CONNECTION_RETRIES)
|
|
486
|
-
_thread_context.session_create_time = time.time()
|
|
419
|
+
request_timeout: float | None = None,
|
|
420
|
+
) -> httpx.Response:
|
|
421
|
+
abs_url, headers, data = self._prepare_request_raw(options, False)
|
|
422
|
+
client = httpx.Client(http2=True)
|
|
423
|
+
req = client.build_request(
|
|
424
|
+
options.method,
|
|
425
|
+
abs_url,
|
|
426
|
+
headers=headers,
|
|
427
|
+
data=data, # type: ignore
|
|
428
|
+
files=options.files,
|
|
429
|
+
timeout=request_timeout or self.timeout,
|
|
430
|
+
)
|
|
487
431
|
try:
|
|
488
|
-
result =
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
headers=headers,
|
|
492
|
-
data=data,
|
|
493
|
-
files=options.files,
|
|
494
|
-
stream=stream,
|
|
495
|
-
timeout=request_timeout or self.timeout,
|
|
496
|
-
proxies=_thread_context.session.proxies,
|
|
497
|
-
allow_redirects=options.allow_redirects,
|
|
498
|
-
)
|
|
499
|
-
except requests.exceptions.Timeout as e:
|
|
500
|
-
utils.log_debug("Encountered requests.exceptions.Timeout")
|
|
501
|
-
|
|
502
|
-
if remaining_retries > 0:
|
|
503
|
-
return self._retry_request(
|
|
504
|
-
options,
|
|
505
|
-
remaining_retries=remaining_retries,
|
|
506
|
-
response_headers=dict(result.headers),
|
|
507
|
-
stream=stream,
|
|
508
|
-
request_timeout=request_timeout,
|
|
509
|
-
)
|
|
432
|
+
result = client.send(req, stream=stream)
|
|
433
|
+
except httpx.TimeoutException as e:
|
|
434
|
+
utils.log_debug("Encountered httpx.TimeoutException")
|
|
510
435
|
|
|
511
436
|
raise error.Timeout("Request timed out: {}".format(e)) from e
|
|
512
|
-
except
|
|
513
|
-
utils.log_debug("Encountered
|
|
514
|
-
|
|
515
|
-
if remaining_retries > 0:
|
|
516
|
-
return self._retry_request(
|
|
517
|
-
options,
|
|
518
|
-
remaining_retries=remaining_retries,
|
|
519
|
-
response_headers=dict(result.headers),
|
|
520
|
-
stream=stream,
|
|
521
|
-
request_timeout=request_timeout,
|
|
522
|
-
)
|
|
437
|
+
except httpx.RequestError as e:
|
|
438
|
+
utils.log_debug("Encountered httpx.HTTPError")
|
|
523
439
|
|
|
524
440
|
raise error.APIConnectionError(
|
|
525
441
|
"Error communicating with API: {}".format(e)
|
|
526
442
|
) from e
|
|
527
443
|
|
|
528
444
|
# retry on 5XX error or rate-limit
|
|
529
|
-
if
|
|
530
|
-
|
|
531
|
-
f"Encountered requests.exceptions.HTTPError. Error code: {result.status_code}"
|
|
532
|
-
)
|
|
533
|
-
|
|
534
|
-
if remaining_retries > 0:
|
|
535
|
-
return self._retry_request(
|
|
536
|
-
options,
|
|
537
|
-
remaining_retries=remaining_retries,
|
|
538
|
-
response_headers=dict(result.headers),
|
|
539
|
-
stream=stream,
|
|
540
|
-
request_timeout=request_timeout,
|
|
541
|
-
)
|
|
445
|
+
if result.status_code > 300:
|
|
446
|
+
raise httpx.HTTPError("Error communicating with API: {}".format(result))
|
|
542
447
|
|
|
543
448
|
utils.log_debug(
|
|
544
449
|
"SeekrFlow API response",
|
|
@@ -547,113 +452,53 @@ class APIRequestor:
|
|
|
547
452
|
processing_ms=result.headers.get("x-total-time"),
|
|
548
453
|
request_id=result.headers.get("CF-RAY"),
|
|
549
454
|
)
|
|
550
|
-
|
|
551
|
-
return result # type: ignore
|
|
552
|
-
|
|
553
|
-
async def arequest_raw(
|
|
554
|
-
self,
|
|
555
|
-
options: SeekrFlowRequest,
|
|
556
|
-
session: aiohttp.ClientSession,
|
|
557
|
-
*,
|
|
558
|
-
request_timeout: float | Tuple[float, float] | None = None,
|
|
559
|
-
absolute: bool = False,
|
|
560
|
-
) -> aiohttp.ClientResponse:
|
|
561
|
-
abs_url, headers, data = self._prepare_request_raw(options, absolute)
|
|
562
|
-
|
|
563
|
-
if isinstance(request_timeout, tuple):
|
|
564
|
-
timeout = aiohttp.ClientTimeout(
|
|
565
|
-
connect=request_timeout[0],
|
|
566
|
-
total=request_timeout[1],
|
|
567
|
-
)
|
|
568
|
-
else:
|
|
569
|
-
timeout = aiohttp.ClientTimeout(total=request_timeout or self.timeout)
|
|
570
|
-
|
|
571
|
-
if options.files:
|
|
572
|
-
data, content_type = requests.models.RequestEncodingMixin._encode_files( # type: ignore
|
|
573
|
-
options.files, data
|
|
574
|
-
)
|
|
575
|
-
headers["Content-Type"] = content_type
|
|
576
|
-
|
|
577
|
-
request_kwargs = {
|
|
578
|
-
"headers": headers,
|
|
579
|
-
"data": data,
|
|
580
|
-
"timeout": timeout,
|
|
581
|
-
"allow_redirects": options.allow_redirects,
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
try:
|
|
585
|
-
result = await session.request(
|
|
586
|
-
method=options.method, url=abs_url, **request_kwargs
|
|
587
|
-
)
|
|
588
|
-
utils.log_debug(
|
|
589
|
-
"SeekrFlow API response",
|
|
590
|
-
path=abs_url,
|
|
591
|
-
response_code=result.status,
|
|
592
|
-
processing_ms=result.headers.get("x-total-time"),
|
|
593
|
-
request_id=result.headers.get("CF-RAY"),
|
|
594
|
-
)
|
|
595
|
-
# Don't read the whole stream for debug logging unless necessary.
|
|
596
|
-
if seekrai.log == "debug":
|
|
597
|
-
utils.log_debug(
|
|
598
|
-
"API response body", body=result.content, headers=result.headers
|
|
599
|
-
)
|
|
600
|
-
return result
|
|
601
|
-
except (aiohttp.ServerTimeoutError, asyncio.TimeoutError) as e:
|
|
602
|
-
raise error.Timeout("Request timed out") from e
|
|
603
|
-
except aiohttp.ClientError as e:
|
|
604
|
-
raise error.APIConnectionError("Error communicating with SeekrFlow") from e
|
|
455
|
+
return result
|
|
605
456
|
|
|
606
457
|
def _interpret_response(
|
|
607
|
-
self, result:
|
|
608
|
-
) ->
|
|
458
|
+
self, result: httpx.Response, stream: bool
|
|
459
|
+
) -> SeekrFlowResponse | Iterator[SeekrFlowResponse]:
|
|
609
460
|
"""Returns the response(s) and a bool indicating whether it is a stream."""
|
|
461
|
+
|
|
610
462
|
if stream and "text/event-stream" in result.headers.get("Content-Type", ""):
|
|
611
|
-
|
|
463
|
+
iterator = (
|
|
612
464
|
self._interpret_response_line(
|
|
613
465
|
line, result.status_code, result.headers, stream=True
|
|
614
466
|
)
|
|
615
|
-
for line in parse_stream(result.
|
|
616
|
-
)
|
|
467
|
+
for line in parse_stream(result.iter_text())
|
|
468
|
+
)
|
|
469
|
+
return iterator
|
|
617
470
|
else:
|
|
618
|
-
return (
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
stream=False,
|
|
624
|
-
),
|
|
625
|
-
False,
|
|
471
|
+
return self._interpret_response_line(
|
|
472
|
+
result.content.decode("utf-8"),
|
|
473
|
+
result.status_code,
|
|
474
|
+
result.headers,
|
|
475
|
+
stream=False,
|
|
626
476
|
)
|
|
627
477
|
|
|
628
478
|
async def _interpret_async_response(
|
|
629
|
-
self, result:
|
|
630
|
-
) ->
|
|
631
|
-
tuple[AsyncGenerator[SeekrFlowResponse, None], bool]
|
|
632
|
-
| tuple[SeekrFlowResponse, bool]
|
|
633
|
-
):
|
|
479
|
+
self, result: httpx.Response, stream: bool
|
|
480
|
+
) -> AsyncGenerator[SeekrFlowResponse, None] | SeekrFlowResponse:
|
|
634
481
|
"""Returns the response(s) and a bool indicating whether it is a stream."""
|
|
635
482
|
if stream and "text/event-stream" in result.headers.get("Content-Type", ""):
|
|
636
|
-
|
|
483
|
+
iterator = (
|
|
637
484
|
self._interpret_response_line(
|
|
638
|
-
line, result.
|
|
485
|
+
line, result.status_code, result.headers, stream=True
|
|
639
486
|
)
|
|
640
|
-
async for line in parse_stream_async(result.
|
|
641
|
-
)
|
|
487
|
+
async for line in parse_stream_async(result.aiter_text())
|
|
488
|
+
)
|
|
489
|
+
return iterator
|
|
642
490
|
else:
|
|
643
491
|
try:
|
|
644
|
-
|
|
645
|
-
except
|
|
492
|
+
result.read()
|
|
493
|
+
except httpx.TimeoutException as e:
|
|
646
494
|
raise error.Timeout("Request timed out") from e
|
|
647
|
-
except
|
|
495
|
+
except httpx.HTTPError as e:
|
|
648
496
|
utils.log_warn(e, body=result.content)
|
|
649
|
-
return (
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
stream=False,
|
|
655
|
-
),
|
|
656
|
-
False,
|
|
497
|
+
return self._interpret_response_line(
|
|
498
|
+
(result.read()).decode("utf-8"),
|
|
499
|
+
result.status_code,
|
|
500
|
+
result.headers,
|
|
501
|
+
stream=False,
|
|
657
502
|
)
|
|
658
503
|
|
|
659
504
|
def _interpret_response_line(
|
|
@@ -687,24 +532,3 @@ class APIRequestor:
|
|
|
687
532
|
if not 200 <= rcode < 300:
|
|
688
533
|
raise self.handle_error_response(resp, rcode, stream_error=stream)
|
|
689
534
|
return resp
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
class AioHTTPSession(AsyncContextManager[aiohttp.ClientSession]):
|
|
693
|
-
def __init__(self) -> None:
|
|
694
|
-
self._session: aiohttp.ClientSession | None = None
|
|
695
|
-
self._should_close_session: bool = False
|
|
696
|
-
|
|
697
|
-
async def __aenter__(self) -> aiohttp.ClientSession:
|
|
698
|
-
self._session = seekrai.aiosession.get()
|
|
699
|
-
if self._session is None:
|
|
700
|
-
self._session = await aiohttp.ClientSession().__aenter__()
|
|
701
|
-
self._should_close_session = True
|
|
702
|
-
|
|
703
|
-
return self._session
|
|
704
|
-
|
|
705
|
-
async def __aexit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None:
|
|
706
|
-
if self._session is None:
|
|
707
|
-
raise RuntimeError("Session is not initialized")
|
|
708
|
-
|
|
709
|
-
if self._should_close_session:
|
|
710
|
-
await self._session.__aexit__(exc_type, exc_value, traceback)
|