GPTRouter 0.3.3__tar.gz → 0.3.4__tar.gz
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.
- {GPTRouter-0.3.3 → GPTRouter-0.3.4}/GPTRouter.egg-info/PKG-INFO +1 -1
- {GPTRouter-0.3.3 → GPTRouter-0.3.4}/PKG-INFO +1 -1
- GPTRouter-0.3.4/gpt_router/client.py +383 -0
- {GPTRouter-0.3.3 → GPTRouter-0.3.4}/pyproject.toml +1 -0
- {GPTRouter-0.3.3 → GPTRouter-0.3.4}/setup.py +1 -1
- GPTRouter-0.3.3/gpt_router/client.py +0 -282
- {GPTRouter-0.3.3 → GPTRouter-0.3.4}/GPTRouter.egg-info/SOURCES.txt +0 -0
- {GPTRouter-0.3.3 → GPTRouter-0.3.4}/GPTRouter.egg-info/dependency_links.txt +0 -0
- {GPTRouter-0.3.3 → GPTRouter-0.3.4}/GPTRouter.egg-info/requires.txt +0 -0
- {GPTRouter-0.3.3 → GPTRouter-0.3.4}/GPTRouter.egg-info/top_level.txt +0 -0
- {GPTRouter-0.3.3 → GPTRouter-0.3.4}/LICENSE +0 -0
- {GPTRouter-0.3.3 → GPTRouter-0.3.4}/README.md +0 -0
- {GPTRouter-0.3.3 → GPTRouter-0.3.4}/gpt_router/__init__.py +0 -0
- {GPTRouter-0.3.3 → GPTRouter-0.3.4}/gpt_router/constants.py +0 -0
- {GPTRouter-0.3.3 → GPTRouter-0.3.4}/gpt_router/enums.py +0 -0
- {GPTRouter-0.3.3 → GPTRouter-0.3.4}/gpt_router/exceptions.py +0 -0
- {GPTRouter-0.3.3 → GPTRouter-0.3.4}/gpt_router/models.py +0 -0
- {GPTRouter-0.3.3 → GPTRouter-0.3.4}/setup.cfg +0 -0
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from pydantic import parse_obj_as
|
|
3
|
+
import logging
|
|
4
|
+
import json
|
|
5
|
+
import httpx
|
|
6
|
+
from typing import Generator, List, Optional, Union
|
|
7
|
+
|
|
8
|
+
from gpt_router.models import (
|
|
9
|
+
GPTRouterMetadata,
|
|
10
|
+
ImageGenerationRequest,
|
|
11
|
+
ImageGenerationResponse,
|
|
12
|
+
ModelGenerationRequest,
|
|
13
|
+
GenerationResponse,
|
|
14
|
+
ChunkedGenerationResponse,
|
|
15
|
+
)
|
|
16
|
+
from gpt_router.exceptions import (
|
|
17
|
+
GPTRouterApiTimeoutError,
|
|
18
|
+
GPTRouterBadRequestError,
|
|
19
|
+
GPTRouterStreamingError,
|
|
20
|
+
GPTRouterForbiddenError,
|
|
21
|
+
GPTRouterInternalServerError,
|
|
22
|
+
GPTRouterNotAvailableError,
|
|
23
|
+
GPTRouterTooManyRequestsError,
|
|
24
|
+
GPTRouterUnauthorizedError,
|
|
25
|
+
)
|
|
26
|
+
from gpt_router.constants import DEFAULT_REQUEST_TIMEOUT
|
|
27
|
+
|
|
28
|
+
from tenacity import (
|
|
29
|
+
Retrying,
|
|
30
|
+
AsyncRetrying,
|
|
31
|
+
retry_if_exception_type,
|
|
32
|
+
stop_after_attempt,
|
|
33
|
+
wait_fixed,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
logger = logging.getLogger(__name__)
|
|
37
|
+
|
|
38
|
+
STATUS_CODE_EXCEPTION_MAPPING = {
|
|
39
|
+
400: GPTRouterBadRequestError,
|
|
40
|
+
406: GPTRouterNotAvailableError,
|
|
41
|
+
401: GPTRouterUnauthorizedError,
|
|
42
|
+
403: GPTRouterForbiddenError,
|
|
43
|
+
429: GPTRouterTooManyRequestsError,
|
|
44
|
+
500: GPTRouterInternalServerError,
|
|
45
|
+
503: GPTRouterNotAvailableError,
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class ServerError(Exception):
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class GPTRouterClient:
|
|
54
|
+
models = None
|
|
55
|
+
request_timeout = DEFAULT_REQUEST_TIMEOUT
|
|
56
|
+
|
|
57
|
+
def __init__(
|
|
58
|
+
self,
|
|
59
|
+
base_url,
|
|
60
|
+
api_key,
|
|
61
|
+
request_timeout: int = 60,
|
|
62
|
+
stream_read_timeout: Optional[int] = None,
|
|
63
|
+
additional_metadata: Optional[GPTRouterMetadata] = None,
|
|
64
|
+
):
|
|
65
|
+
self.base_url = base_url
|
|
66
|
+
self.api_key = api_key
|
|
67
|
+
self.request_timeout = request_timeout
|
|
68
|
+
self.additional_metadata = additional_metadata
|
|
69
|
+
self.stream_read_timeout = (
|
|
70
|
+
request_timeout if stream_read_timeout is None else stream_read_timeout
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
def add_metadata_info(
|
|
74
|
+
self,
|
|
75
|
+
payload: dict,
|
|
76
|
+
model_router_metadata: Optional[GPTRouterMetadata] = None,
|
|
77
|
+
):
|
|
78
|
+
metadata = {}
|
|
79
|
+
if self.additional_metadata:
|
|
80
|
+
metadata.update(self.additional_metadata.dict())
|
|
81
|
+
if model_router_metadata:
|
|
82
|
+
metadata.update(model_router_metadata.dict())
|
|
83
|
+
|
|
84
|
+
payload.update(
|
|
85
|
+
{
|
|
86
|
+
"metadata": metadata,
|
|
87
|
+
"tag": metadata.get("tag"),
|
|
88
|
+
"createdByUserId": metadata.get("created_by_user_id"),
|
|
89
|
+
"historyId": (
|
|
90
|
+
str(metadata["history_id"]) if metadata.get("history_id") else None
|
|
91
|
+
),
|
|
92
|
+
}
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
payload = {k: v for k, v in payload.items() if v is not None}
|
|
96
|
+
return payload
|
|
97
|
+
|
|
98
|
+
async def _async_api_call(self, *, path: str, method: str, payload: dict):
|
|
99
|
+
try:
|
|
100
|
+
async with httpx.AsyncClient() as client:
|
|
101
|
+
response = await client.request(
|
|
102
|
+
method,
|
|
103
|
+
url=self.base_url.rstrip("/")
|
|
104
|
+
+ ("/api" if not self.base_url.endswith("/api") else "")
|
|
105
|
+
+ path,
|
|
106
|
+
headers={
|
|
107
|
+
"content-type": "application/json",
|
|
108
|
+
"ws-secret": self.api_key,
|
|
109
|
+
},
|
|
110
|
+
json=payload,
|
|
111
|
+
timeout=self.request_timeout,
|
|
112
|
+
)
|
|
113
|
+
if response.status_code == 200:
|
|
114
|
+
return response.json()
|
|
115
|
+
elif response.status_code == 202 or response.status_code == 204:
|
|
116
|
+
return None
|
|
117
|
+
else:
|
|
118
|
+
error_class = STATUS_CODE_EXCEPTION_MAPPING.get(
|
|
119
|
+
response.status_code, Exception
|
|
120
|
+
)
|
|
121
|
+
raise error_class(response.json())
|
|
122
|
+
except httpx.TimeoutException as err:
|
|
123
|
+
logger.error(f"Timeout error: {err}")
|
|
124
|
+
raise GPTRouterApiTimeoutError("Api Request timed out")
|
|
125
|
+
|
|
126
|
+
def _api_call(self, *, path: str, method: str, payload: dict):
|
|
127
|
+
try:
|
|
128
|
+
with httpx.Client() as client:
|
|
129
|
+
response = client.request(
|
|
130
|
+
method,
|
|
131
|
+
url=self.base_url.rstrip("/")
|
|
132
|
+
+ ("/api" if not self.base_url.endswith("/api") else "")
|
|
133
|
+
+ path,
|
|
134
|
+
headers={
|
|
135
|
+
"content-type": "application/json",
|
|
136
|
+
"ws-secret": self.api_key,
|
|
137
|
+
},
|
|
138
|
+
json=payload,
|
|
139
|
+
timeout=self.request_timeout,
|
|
140
|
+
)
|
|
141
|
+
if response.status_code == 200:
|
|
142
|
+
return response.json()
|
|
143
|
+
elif response.status_code == 202 or response.status_code == 204:
|
|
144
|
+
return None
|
|
145
|
+
else:
|
|
146
|
+
error_class = STATUS_CODE_EXCEPTION_MAPPING.get(
|
|
147
|
+
response.status_code, Exception
|
|
148
|
+
)
|
|
149
|
+
raise error_class(response.json())
|
|
150
|
+
except httpx.TimeoutException as err:
|
|
151
|
+
logger.error(f"Timeout error: {err}")
|
|
152
|
+
raise GPTRouterApiTimeoutError("Api Request timed out")
|
|
153
|
+
|
|
154
|
+
async def astream_events(
|
|
155
|
+
self, *, path: str, method: str, payload: dict, retry_count: int = 1
|
|
156
|
+
):
|
|
157
|
+
async_retrying = AsyncRetrying(
|
|
158
|
+
stop=stop_after_attempt(retry_count),
|
|
159
|
+
wait=wait_fixed(0.5),
|
|
160
|
+
retry=(
|
|
161
|
+
retry_if_exception_type(httpx.ReadTimeout)
|
|
162
|
+
| retry_if_exception_type(ServerError)
|
|
163
|
+
| retry_if_exception_type(httpx.ReadError)
|
|
164
|
+
),
|
|
165
|
+
before_sleep=lambda retry_state: logger.warn(
|
|
166
|
+
f"Read timeout. Retrying... (Attempt {retry_state.attempt_number} of {retry_count})"
|
|
167
|
+
),
|
|
168
|
+
)
|
|
169
|
+
attempt_number = 0
|
|
170
|
+
async for attempt in async_retrying:
|
|
171
|
+
attempt_number += 1
|
|
172
|
+
|
|
173
|
+
if attempt_number < retry_count:
|
|
174
|
+
read_timeout = self.stream_read_timeout
|
|
175
|
+
else:
|
|
176
|
+
read_timeout = self.request_timeout
|
|
177
|
+
try:
|
|
178
|
+
async with httpx.AsyncClient() as client:
|
|
179
|
+
async with client.stream(
|
|
180
|
+
method,
|
|
181
|
+
url=self.base_url.rstrip("/")
|
|
182
|
+
+ ("/api" if not self.base_url.endswith("/api") else "")
|
|
183
|
+
+ path,
|
|
184
|
+
data=json.dumps(payload),
|
|
185
|
+
headers={
|
|
186
|
+
"Content-type": "application/json",
|
|
187
|
+
"ws-secret": self.api_key,
|
|
188
|
+
},
|
|
189
|
+
timeout=httpx.Timeout(self.request_timeout, read=read_timeout),
|
|
190
|
+
) as response:
|
|
191
|
+
if response.status_code >= 500:
|
|
192
|
+
raise ServerError("Server error")
|
|
193
|
+
response.raise_for_status()
|
|
194
|
+
async for line in response.aiter_lines():
|
|
195
|
+
try:
|
|
196
|
+
if line.strip() == "":
|
|
197
|
+
continue
|
|
198
|
+
|
|
199
|
+
line_type, line_data = (
|
|
200
|
+
segment.strip() for segment in line.split(":", 1)
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
if line_type != "data":
|
|
204
|
+
continue
|
|
205
|
+
|
|
206
|
+
data: dict = json.loads(line_data.strip())
|
|
207
|
+
if data["event"] == "error":
|
|
208
|
+
raise GPTRouterStreamingError(data)
|
|
209
|
+
yield parse_obj_as(ChunkedGenerationResponse, data)
|
|
210
|
+
except GPTRouterStreamingError as e:
|
|
211
|
+
raise e
|
|
212
|
+
except Exception:
|
|
213
|
+
continue
|
|
214
|
+
|
|
215
|
+
return
|
|
216
|
+
except (httpx.ReadTimeout, ServerError, httpx.ReadError) as err:
|
|
217
|
+
logger.error(str(err))
|
|
218
|
+
if attempt_number == retry_count:
|
|
219
|
+
logger.error("All retry attempts failed")
|
|
220
|
+
raise
|
|
221
|
+
except httpx.HTTPStatusError as err:
|
|
222
|
+
raise Exception(
|
|
223
|
+
f"HTTP Error {err.response.status_code}: {err.response.text}"
|
|
224
|
+
) from err
|
|
225
|
+
except httpx.TimeoutException as err:
|
|
226
|
+
raise TimeoutError("Request timed out") from err
|
|
227
|
+
|
|
228
|
+
def stream_events(
|
|
229
|
+
self, *, path: str, method: str, payload: dict, retry_count: int = 1
|
|
230
|
+
) -> Generator[ChunkedGenerationResponse]:
|
|
231
|
+
retrying = Retrying(
|
|
232
|
+
stop=stop_after_attempt(retry_count),
|
|
233
|
+
wait=wait_fixed(0.5),
|
|
234
|
+
retry=(
|
|
235
|
+
retry_if_exception_type(httpx.ReadTimeout)
|
|
236
|
+
| retry_if_exception_type(ServerError)
|
|
237
|
+
| retry_if_exception_type(httpx.ReadError)
|
|
238
|
+
),
|
|
239
|
+
before_sleep=lambda retry_state: logger.warn(
|
|
240
|
+
f"Read timeout. Retrying... (Attempt {retry_state.attempt_number} of {retry_count})"
|
|
241
|
+
),
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
attempt_number = 0
|
|
245
|
+
for attempt in retrying:
|
|
246
|
+
attempt_number += 1
|
|
247
|
+
|
|
248
|
+
if attempt_number < retry_count:
|
|
249
|
+
read_timeout = self.stream_read_timeout
|
|
250
|
+
else:
|
|
251
|
+
read_timeout = self.request_timeout
|
|
252
|
+
|
|
253
|
+
try:
|
|
254
|
+
with httpx.Client() as client:
|
|
255
|
+
with client.stream(
|
|
256
|
+
method=method,
|
|
257
|
+
url=self.base_url.rstrip("/")
|
|
258
|
+
+ ("/api" if not self.base_url.endswith("/api") else "")
|
|
259
|
+
+ path,
|
|
260
|
+
data=json.dumps(payload),
|
|
261
|
+
headers={
|
|
262
|
+
"Content-type": "application/json",
|
|
263
|
+
"ws-secret": self.api_key,
|
|
264
|
+
},
|
|
265
|
+
timeout=httpx.Timeout(self.request_timeout, read=read_timeout),
|
|
266
|
+
) as response:
|
|
267
|
+
if response.status_code >= 500:
|
|
268
|
+
raise ServerError("Server error")
|
|
269
|
+
response.raise_for_status()
|
|
270
|
+
for line in response.iter_lines():
|
|
271
|
+
try:
|
|
272
|
+
if line.strip() == "":
|
|
273
|
+
continue
|
|
274
|
+
|
|
275
|
+
line_type, line_data = (
|
|
276
|
+
segment.strip() for segment in line.split(":", 1)
|
|
277
|
+
)
|
|
278
|
+
if line_type != "data":
|
|
279
|
+
continue
|
|
280
|
+
|
|
281
|
+
data = json.loads(line_data.strip())
|
|
282
|
+
if data["event"].lower() == "error":
|
|
283
|
+
raise GPTRouterStreamingError(data["message"])
|
|
284
|
+
yield parse_obj_as(ChunkedGenerationResponse, data)
|
|
285
|
+
except GPTRouterStreamingError as e:
|
|
286
|
+
raise e
|
|
287
|
+
except Exception:
|
|
288
|
+
continue
|
|
289
|
+
return
|
|
290
|
+
except (httpx.ReadTimeout, ServerError, httpx.ReadError) as err:
|
|
291
|
+
logger.error(str(err))
|
|
292
|
+
if attempt_number == retry_count:
|
|
293
|
+
logger.error("All retry attempts failed")
|
|
294
|
+
raise
|
|
295
|
+
except httpx.HTTPStatusError as err:
|
|
296
|
+
raise Exception(
|
|
297
|
+
f"HTTP Error {err.response.status_code}: {err.response.text}"
|
|
298
|
+
) from err
|
|
299
|
+
except httpx.TimeoutException as err:
|
|
300
|
+
raise TimeoutError("Request timed out") from err
|
|
301
|
+
|
|
302
|
+
def generate(
|
|
303
|
+
self,
|
|
304
|
+
*,
|
|
305
|
+
ordered_generation_requests: List[ModelGenerationRequest],
|
|
306
|
+
is_stream=False,
|
|
307
|
+
model_router_metadata: Optional[GPTRouterMetadata] = None,
|
|
308
|
+
**kwargs,
|
|
309
|
+
) -> Union[GenerationResponse, Generator[ChunkedGenerationResponse]]:
|
|
310
|
+
api_path = "/v1/generate"
|
|
311
|
+
api_method = "POST"
|
|
312
|
+
api_payload = {
|
|
313
|
+
"stream": is_stream,
|
|
314
|
+
"data": [
|
|
315
|
+
request.dict(exclude_none=True, by_alias=True)
|
|
316
|
+
for request in ordered_generation_requests
|
|
317
|
+
],
|
|
318
|
+
}
|
|
319
|
+
api_payload = self.add_metadata_info(api_payload, model_router_metadata)
|
|
320
|
+
if is_stream:
|
|
321
|
+
return self.stream_events(
|
|
322
|
+
path=api_path,
|
|
323
|
+
method=api_method,
|
|
324
|
+
payload=api_payload,
|
|
325
|
+
)
|
|
326
|
+
result = self._api_call(
|
|
327
|
+
path=api_path,
|
|
328
|
+
method=api_method,
|
|
329
|
+
payload=api_payload,
|
|
330
|
+
)
|
|
331
|
+
return parse_obj_as(GenerationResponse, result)
|
|
332
|
+
|
|
333
|
+
async def agenerate(
|
|
334
|
+
self,
|
|
335
|
+
*,
|
|
336
|
+
ordered_generation_requests: List[ModelGenerationRequest],
|
|
337
|
+
is_stream=False,
|
|
338
|
+
model_router_metadata: Optional[GPTRouterMetadata] = None,
|
|
339
|
+
**kwargs,
|
|
340
|
+
) -> GenerationResponse:
|
|
341
|
+
api_path = "/v1/generate"
|
|
342
|
+
api_method = "POST"
|
|
343
|
+
api_payload = {
|
|
344
|
+
"stream": is_stream,
|
|
345
|
+
"data": [
|
|
346
|
+
request.dict(exclude_none=True, by_alias=True)
|
|
347
|
+
for request in ordered_generation_requests
|
|
348
|
+
],
|
|
349
|
+
}
|
|
350
|
+
api_payload = self.add_metadata_info(api_payload, model_router_metadata)
|
|
351
|
+
if is_stream:
|
|
352
|
+
return self.astream_events(
|
|
353
|
+
path=api_path,
|
|
354
|
+
method=api_method,
|
|
355
|
+
payload=api_payload,
|
|
356
|
+
)
|
|
357
|
+
result = await self._async_api_call(
|
|
358
|
+
path=api_path,
|
|
359
|
+
method=api_method,
|
|
360
|
+
payload=api_payload,
|
|
361
|
+
)
|
|
362
|
+
return parse_obj_as(GenerationResponse, result)
|
|
363
|
+
|
|
364
|
+
async def agenerate_images(
|
|
365
|
+
self, *, image_generation_request: ImageGenerationRequest
|
|
366
|
+
) -> List[ImageGenerationResponse]:
|
|
367
|
+
api_path = "/v1/generate/generate-image"
|
|
368
|
+
api_method = "POST"
|
|
369
|
+
api_payload = image_generation_request.dict()
|
|
370
|
+
|
|
371
|
+
api_response = await self._async_api_call(
|
|
372
|
+
path=api_path,
|
|
373
|
+
method=api_method,
|
|
374
|
+
payload=api_payload,
|
|
375
|
+
)
|
|
376
|
+
generated_images = api_response.get("response", [])
|
|
377
|
+
if isinstance(generated_images, dict):
|
|
378
|
+
generated_images = generated_images.get("artifacts", [])
|
|
379
|
+
|
|
380
|
+
return [
|
|
381
|
+
parse_obj_as(ImageGenerationResponse, generated_img)
|
|
382
|
+
for generated_img in generated_images
|
|
383
|
+
]
|
|
@@ -1,282 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
from pydantic import parse_obj_as
|
|
3
|
-
import logging
|
|
4
|
-
import json
|
|
5
|
-
import httpx
|
|
6
|
-
from typing import Generator, List, Optional, Union
|
|
7
|
-
|
|
8
|
-
from gpt_router.models import (
|
|
9
|
-
GPTRouterMetadata,
|
|
10
|
-
ImageGenerationRequest,
|
|
11
|
-
ImageGenerationResponse,
|
|
12
|
-
ModelGenerationRequest,
|
|
13
|
-
GenerationResponse,
|
|
14
|
-
ChunkedGenerationResponse,
|
|
15
|
-
)
|
|
16
|
-
from gpt_router.exceptions import (
|
|
17
|
-
GPTRouterApiTimeoutError,
|
|
18
|
-
GPTRouterBadRequestError,
|
|
19
|
-
GPTRouterStreamingError,
|
|
20
|
-
GPTRouterForbiddenError,
|
|
21
|
-
GPTRouterInternalServerError,
|
|
22
|
-
GPTRouterNotAvailableError,
|
|
23
|
-
GPTRouterTooManyRequestsError,
|
|
24
|
-
GPTRouterUnauthorizedError,
|
|
25
|
-
)
|
|
26
|
-
from gpt_router.constants import DEFAULT_REQUEST_TIMEOUT
|
|
27
|
-
|
|
28
|
-
logger = logging.getLogger(__name__)
|
|
29
|
-
|
|
30
|
-
STATUS_CODE_EXCEPTION_MAPPING = {
|
|
31
|
-
400: GPTRouterBadRequestError,
|
|
32
|
-
406: GPTRouterNotAvailableError,
|
|
33
|
-
401: GPTRouterUnauthorizedError,
|
|
34
|
-
403: GPTRouterForbiddenError,
|
|
35
|
-
429: GPTRouterTooManyRequestsError,
|
|
36
|
-
500: GPTRouterInternalServerError,
|
|
37
|
-
503: GPTRouterNotAvailableError,
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
class GPTRouterClient:
|
|
42
|
-
models = None
|
|
43
|
-
request_timeout = DEFAULT_REQUEST_TIMEOUT
|
|
44
|
-
|
|
45
|
-
def __init__(self, base_url, api_key, request_timeout: int = 60, additional_metadata: Optional[GPTRouterMetadata] = None):
|
|
46
|
-
self.base_url = base_url
|
|
47
|
-
self.api_key = api_key
|
|
48
|
-
self.request_timeout = request_timeout
|
|
49
|
-
self.additional_metadata = additional_metadata
|
|
50
|
-
|
|
51
|
-
def add_metadata_info(
|
|
52
|
-
self,
|
|
53
|
-
payload: dict,
|
|
54
|
-
model_router_metadata: Optional[GPTRouterMetadata] = None,
|
|
55
|
-
):
|
|
56
|
-
metadata = {}
|
|
57
|
-
if self.additional_metadata:
|
|
58
|
-
metadata.update(self.additional_metadata.dict())
|
|
59
|
-
if model_router_metadata:
|
|
60
|
-
metadata.update(model_router_metadata.dict())
|
|
61
|
-
|
|
62
|
-
payload.update(
|
|
63
|
-
{
|
|
64
|
-
"metadata": metadata,
|
|
65
|
-
"tag": metadata.get('tag'),
|
|
66
|
-
"createdByUserId": metadata['created_by_user_id'],
|
|
67
|
-
"historyId": str(metadata['history_id']) if metadata.get('history_id') else None,
|
|
68
|
-
}
|
|
69
|
-
)
|
|
70
|
-
|
|
71
|
-
payload = {k: v for k, v in payload.items() if v is not None}
|
|
72
|
-
return payload
|
|
73
|
-
|
|
74
|
-
async def _async_api_call(self, *, path: str, method: str, payload: dict):
|
|
75
|
-
try:
|
|
76
|
-
async with httpx.AsyncClient() as client:
|
|
77
|
-
response = await client.request(
|
|
78
|
-
method,
|
|
79
|
-
url=self.base_url.rstrip('/') + ('/api' if not self.base_url.endswith('/api') else '') + path,
|
|
80
|
-
headers={
|
|
81
|
-
"content-type": "application/json",
|
|
82
|
-
"ws-secret": self.api_key,
|
|
83
|
-
},
|
|
84
|
-
json=payload,
|
|
85
|
-
timeout=self.request_timeout,
|
|
86
|
-
)
|
|
87
|
-
if response.status_code == 200:
|
|
88
|
-
return response.json()
|
|
89
|
-
elif response.status_code == 202 or response.status_code == 204:
|
|
90
|
-
return None
|
|
91
|
-
else:
|
|
92
|
-
error_class = STATUS_CODE_EXCEPTION_MAPPING.get(
|
|
93
|
-
response.status_code, Exception
|
|
94
|
-
)
|
|
95
|
-
raise error_class(response.json())
|
|
96
|
-
except httpx.TimeoutException as err:
|
|
97
|
-
logger.error(f"Timeout error: {err}")
|
|
98
|
-
raise GPTRouterApiTimeoutError("Api Request timed out")
|
|
99
|
-
|
|
100
|
-
def _api_call(self, *, path: str, method: str, payload: dict):
|
|
101
|
-
try:
|
|
102
|
-
with httpx.Client() as client:
|
|
103
|
-
response = client.request(
|
|
104
|
-
method,
|
|
105
|
-
url=self.base_url.rstrip('/') + ('/api' if not self.base_url.endswith('/api') else '') + path,
|
|
106
|
-
headers={
|
|
107
|
-
"content-type": "application/json",
|
|
108
|
-
"ws-secret": self.api_key,
|
|
109
|
-
},
|
|
110
|
-
json=payload,
|
|
111
|
-
timeout=self.request_timeout,
|
|
112
|
-
)
|
|
113
|
-
if response.status_code == 200:
|
|
114
|
-
return response.json()
|
|
115
|
-
elif response.status_code == 202 or response.status_code == 204:
|
|
116
|
-
return None
|
|
117
|
-
else:
|
|
118
|
-
error_class = STATUS_CODE_EXCEPTION_MAPPING.get(
|
|
119
|
-
response.status_code, Exception
|
|
120
|
-
)
|
|
121
|
-
raise error_class(response.json())
|
|
122
|
-
except httpx.TimeoutException as err:
|
|
123
|
-
logger.error(f"Timeout error: {err}")
|
|
124
|
-
raise GPTRouterApiTimeoutError("Api Request timed out")
|
|
125
|
-
|
|
126
|
-
async def astream_events(self, *, path: str, method: str, payload: dict):
|
|
127
|
-
try:
|
|
128
|
-
async with httpx.AsyncClient() as client:
|
|
129
|
-
async with client.stream(
|
|
130
|
-
method,
|
|
131
|
-
url=self.base_url.rstrip('/') + ('/api' if not self.base_url.endswith('/api') else '') + path,
|
|
132
|
-
data=json.dumps(payload),
|
|
133
|
-
headers={
|
|
134
|
-
"Content-type": "application/json",
|
|
135
|
-
"ws-secret": self.api_key,
|
|
136
|
-
},
|
|
137
|
-
timeout=self.request_timeout,
|
|
138
|
-
) as response:
|
|
139
|
-
async for line in response.aiter_lines():
|
|
140
|
-
try:
|
|
141
|
-
if line.strip() == "":
|
|
142
|
-
continue
|
|
143
|
-
|
|
144
|
-
line_type, line_data = (
|
|
145
|
-
segment.strip() for segment in line.split(":", 1)
|
|
146
|
-
)
|
|
147
|
-
|
|
148
|
-
if line_type != "data":
|
|
149
|
-
continue
|
|
150
|
-
|
|
151
|
-
data: dict = json.loads(line_data.strip())
|
|
152
|
-
if data["event"] == "error":
|
|
153
|
-
raise GPTRouterStreamingError(data)
|
|
154
|
-
yield parse_obj_as(ChunkedGenerationResponse, data)
|
|
155
|
-
except GPTRouterStreamingError as e:
|
|
156
|
-
raise e
|
|
157
|
-
except Exception:
|
|
158
|
-
continue
|
|
159
|
-
except httpx.TimeoutException as err:
|
|
160
|
-
logger.error(f"Timeout error: {err}")
|
|
161
|
-
raise TimeoutError("Request timed out")
|
|
162
|
-
|
|
163
|
-
def stream_events(
|
|
164
|
-
self, *, path: str, method: str, payload: dict
|
|
165
|
-
) -> Generator[ChunkedGenerationResponse]:
|
|
166
|
-
try:
|
|
167
|
-
with httpx.Client() as client:
|
|
168
|
-
with client.stream(
|
|
169
|
-
method=method,
|
|
170
|
-
url=self.base_url.rstrip('/') + ('/api' if not self.base_url.endswith('/api') else '') + path,
|
|
171
|
-
data=json.dumps(payload),
|
|
172
|
-
headers={
|
|
173
|
-
"Content-type": "application/json",
|
|
174
|
-
"ws-secret": self.api_key,
|
|
175
|
-
},
|
|
176
|
-
timeout=self.request_timeout,
|
|
177
|
-
) as response:
|
|
178
|
-
for line in response.iter_lines():
|
|
179
|
-
try:
|
|
180
|
-
if line.strip() == "":
|
|
181
|
-
continue
|
|
182
|
-
|
|
183
|
-
line_type, line_data = (
|
|
184
|
-
segment.strip() for segment in line.split(":", 1)
|
|
185
|
-
)
|
|
186
|
-
if line_type != "data":
|
|
187
|
-
continue
|
|
188
|
-
|
|
189
|
-
data = json.loads(line_data.strip())
|
|
190
|
-
if data["event"].lower() == "error":
|
|
191
|
-
raise GPTRouterStreamingError(data["message"])
|
|
192
|
-
yield parse_obj_as(ChunkedGenerationResponse, data)
|
|
193
|
-
except GPTRouterStreamingError as e:
|
|
194
|
-
raise e
|
|
195
|
-
except Exception:
|
|
196
|
-
continue
|
|
197
|
-
except httpx.TimeoutException as err:
|
|
198
|
-
logger.error(f"Timeout error: {err}")
|
|
199
|
-
raise TimeoutError("Request timed out")
|
|
200
|
-
|
|
201
|
-
def generate(
|
|
202
|
-
self,
|
|
203
|
-
*,
|
|
204
|
-
ordered_generation_requests: List[ModelGenerationRequest],
|
|
205
|
-
is_stream=False,
|
|
206
|
-
model_router_metadata: Optional[GPTRouterMetadata] = None,
|
|
207
|
-
**kwargs,
|
|
208
|
-
) -> Union[GenerationResponse, Generator[ChunkedGenerationResponse]]:
|
|
209
|
-
api_path = "/v1/generate"
|
|
210
|
-
api_method = "POST"
|
|
211
|
-
api_payload = {
|
|
212
|
-
"stream": is_stream,
|
|
213
|
-
"data": [
|
|
214
|
-
request.dict(exclude_none=True, by_alias=True)
|
|
215
|
-
for request in ordered_generation_requests
|
|
216
|
-
],
|
|
217
|
-
}
|
|
218
|
-
api_payload = self.add_metadata_info(api_payload, model_router_metadata)
|
|
219
|
-
if is_stream:
|
|
220
|
-
return self.stream_events(
|
|
221
|
-
path=api_path,
|
|
222
|
-
method=api_method,
|
|
223
|
-
payload=api_payload,
|
|
224
|
-
)
|
|
225
|
-
result = self._api_call(
|
|
226
|
-
path=api_path,
|
|
227
|
-
method=api_method,
|
|
228
|
-
payload=api_payload,
|
|
229
|
-
)
|
|
230
|
-
return parse_obj_as(GenerationResponse, result)
|
|
231
|
-
|
|
232
|
-
async def agenerate(
|
|
233
|
-
self,
|
|
234
|
-
*,
|
|
235
|
-
ordered_generation_requests: List[ModelGenerationRequest],
|
|
236
|
-
is_stream=False,
|
|
237
|
-
model_router_metadata: Optional[GPTRouterMetadata] = None,
|
|
238
|
-
**kwargs,
|
|
239
|
-
) -> GenerationResponse:
|
|
240
|
-
api_path = "/v1/generate"
|
|
241
|
-
api_method = "POST"
|
|
242
|
-
api_payload = {
|
|
243
|
-
"stream": is_stream,
|
|
244
|
-
"data": [
|
|
245
|
-
request.dict(exclude_none=True, by_alias=True)
|
|
246
|
-
for request in ordered_generation_requests
|
|
247
|
-
],
|
|
248
|
-
}
|
|
249
|
-
api_payload = self.add_metadata_info(api_payload, model_router_metadata)
|
|
250
|
-
if is_stream:
|
|
251
|
-
return self.astream_events(
|
|
252
|
-
path=api_path,
|
|
253
|
-
method=api_method,
|
|
254
|
-
payload=api_payload,
|
|
255
|
-
)
|
|
256
|
-
result = await self._async_api_call(
|
|
257
|
-
path=api_path,
|
|
258
|
-
method=api_method,
|
|
259
|
-
payload=api_payload,
|
|
260
|
-
)
|
|
261
|
-
return parse_obj_as(GenerationResponse, result)
|
|
262
|
-
|
|
263
|
-
async def agenerate_images(
|
|
264
|
-
self, *, image_generation_request: ImageGenerationRequest
|
|
265
|
-
) -> List[ImageGenerationResponse]:
|
|
266
|
-
api_path = "/v1/generate/generate-image"
|
|
267
|
-
api_method = "POST"
|
|
268
|
-
api_payload = image_generation_request.dict()
|
|
269
|
-
|
|
270
|
-
api_response = await self._async_api_call(
|
|
271
|
-
path=api_path,
|
|
272
|
-
method=api_method,
|
|
273
|
-
payload=api_payload,
|
|
274
|
-
)
|
|
275
|
-
generated_images = api_response.get("response", [])
|
|
276
|
-
if isinstance(generated_images, dict):
|
|
277
|
-
generated_images = generated_images.get("artifacts", [])
|
|
278
|
-
|
|
279
|
-
return [
|
|
280
|
-
parse_obj_as(ImageGenerationResponse, generated_img)
|
|
281
|
-
for generated_img in generated_images
|
|
282
|
-
]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|