GPTRouter 1.0.4__tar.gz → 1.0.6__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.
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: GPTRouter
3
- Version: 1.0.4
3
+ Version: 1.0.6
4
4
  Summary: A Python package for working with GPTRouter APIs
5
5
  Author-email: sirjanpreet@writesonic.com
6
6
  Classifier: Development Status :: 3 - Alpha
@@ -10,6 +10,16 @@ Classifier: Programming Language :: Python :: 3.10
10
10
  Classifier: Programming Language :: Python :: 3.11
11
11
  Description-Content-Type: text/markdown
12
12
  License-File: LICENSE
13
+ Requires-Dist: httpx>=0.23.3
14
+ Requires-Dist: pydantic>=2.0
15
+ Requires-Dist: pyhumps>=3.8.0
16
+ Dynamic: author-email
17
+ Dynamic: classifier
18
+ Dynamic: description
19
+ Dynamic: description-content-type
20
+ Dynamic: license-file
21
+ Dynamic: requires-dist
22
+ Dynamic: summary
13
23
 
14
24
  # GPTRouter Python Client
15
25
 
@@ -37,14 +47,14 @@ messages = [
37
47
  {"role": "user", "content": "Write me a short poem"},
38
48
  ]
39
49
  prompt_params = GenerationParams(messages=messages)
40
- claude2_request = ModelGenerationRequest(
41
- model_name=ModelsEnum.CLAUDE_INSTANT_12,
42
- provider_name=ProvidersEnum.ANTHROPIC.value,
50
+ claude_request = ModelGenerationRequest(
51
+ model_name=ModelsEnum.CLAUDE_4_5_HAIKU.value,
52
+ provider_name=ProvidersEnum.CHAT_ANTHROPIC.value,
43
53
  order=1,
44
54
  prompt_params=prompt_params,
45
55
  )
46
56
 
47
- response = client.generate(ordered_generation_requests=[claude2_request])
57
+ response = client.generate(ordered_generation_requests=[claude_request])
48
58
  print(response.choices[0].text)
49
59
  ```
50
60
 
@@ -1,3 +1,3 @@
1
1
  httpx>=0.23.3
2
- pydantic>=1.10.13
2
+ pydantic>=2.0
3
3
  pyhumps>=3.8.0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: GPTRouter
3
- Version: 1.0.4
3
+ Version: 1.0.6
4
4
  Summary: A Python package for working with GPTRouter APIs
5
5
  Author-email: sirjanpreet@writesonic.com
6
6
  Classifier: Development Status :: 3 - Alpha
@@ -10,6 +10,16 @@ Classifier: Programming Language :: Python :: 3.10
10
10
  Classifier: Programming Language :: Python :: 3.11
11
11
  Description-Content-Type: text/markdown
12
12
  License-File: LICENSE
13
+ Requires-Dist: httpx>=0.23.3
14
+ Requires-Dist: pydantic>=2.0
15
+ Requires-Dist: pyhumps>=3.8.0
16
+ Dynamic: author-email
17
+ Dynamic: classifier
18
+ Dynamic: description
19
+ Dynamic: description-content-type
20
+ Dynamic: license-file
21
+ Dynamic: requires-dist
22
+ Dynamic: summary
13
23
 
14
24
  # GPTRouter Python Client
15
25
 
@@ -37,14 +47,14 @@ messages = [
37
47
  {"role": "user", "content": "Write me a short poem"},
38
48
  ]
39
49
  prompt_params = GenerationParams(messages=messages)
40
- claude2_request = ModelGenerationRequest(
41
- model_name=ModelsEnum.CLAUDE_INSTANT_12,
42
- provider_name=ProvidersEnum.ANTHROPIC.value,
50
+ claude_request = ModelGenerationRequest(
51
+ model_name=ModelsEnum.CLAUDE_4_5_HAIKU.value,
52
+ provider_name=ProvidersEnum.CHAT_ANTHROPIC.value,
43
53
  order=1,
44
54
  prompt_params=prompt_params,
45
55
  )
46
56
 
47
- response = client.generate(ordered_generation_requests=[claude2_request])
57
+ response = client.generate(ordered_generation_requests=[claude_request])
48
58
  print(response.choices[0].text)
49
59
  ```
50
60
 
@@ -24,14 +24,14 @@ messages = [
24
24
  {"role": "user", "content": "Write me a short poem"},
25
25
  ]
26
26
  prompt_params = GenerationParams(messages=messages)
27
- claude2_request = ModelGenerationRequest(
28
- model_name=ModelsEnum.CLAUDE_INSTANT_12,
29
- provider_name=ProvidersEnum.ANTHROPIC.value,
27
+ claude_request = ModelGenerationRequest(
28
+ model_name=ModelsEnum.CLAUDE_4_5_HAIKU.value,
29
+ provider_name=ProvidersEnum.CHAT_ANTHROPIC.value,
30
30
  order=1,
31
31
  prompt_params=prompt_params,
32
32
  )
33
33
 
34
- response = client.generate(ordered_generation_requests=[claude2_request])
34
+ response = client.generate(ordered_generation_requests=[claude_request])
35
35
  print(response.choices[0].text)
36
36
  ```
37
37
 
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
  import asyncio
3
- from pydantic import parse_obj_as
3
+ from pydantic import TypeAdapter
4
4
  import logging
5
5
  import json
6
6
  import httpx
@@ -86,7 +86,7 @@ class GPTRouterClient:
86
86
  api_payload = {
87
87
  "data": [
88
88
  {
89
- **request.dict(),
89
+ **request.model_dump(),
90
90
  "order": index + 1,
91
91
  }
92
92
  for index, request in enumerate(ordered_generation_requests)
@@ -98,7 +98,30 @@ class GPTRouterClient:
98
98
  method="POST",
99
99
  payload=api_payload,
100
100
  )
101
- return ModelRouterEmbeddingsGenerationResponse.parse_obj(response)
101
+ return ModelRouterEmbeddingsGenerationResponse.model_validate(response)
102
+
103
+ async def agenerate_embeddings_v2(
104
+ self,
105
+ *,
106
+ input: List[str],
107
+ model_name: str,
108
+ model_router_metadata: Optional[GPTRouterMetadata] = None,
109
+ ) -> ModelRouterEmbeddingsGenerationResponse:
110
+ """
111
+ Generate embeddings using the v2 endpoint which uses round-robin load balancing
112
+ across multiple providers for improved availability and performance.
113
+ """
114
+ api_payload = {
115
+ "input": input,
116
+ "modelName": model_name,
117
+ }
118
+ api_payload = self.add_metadata_info(api_payload, model_router_metadata)
119
+ response = await self._async_api_call(
120
+ path="/v1/generate/embeddings-v2",
121
+ method="POST",
122
+ payload=api_payload,
123
+ )
124
+ return ModelRouterEmbeddingsGenerationResponse.model_validate(response)
102
125
 
103
126
  def add_metadata_info(
104
127
  self,
@@ -107,9 +130,9 @@ class GPTRouterClient:
107
130
  ):
108
131
  metadata = {}
109
132
  if self.additional_metadata:
110
- metadata.update(self.additional_metadata.dict())
133
+ metadata.update(self.additional_metadata.model_dump())
111
134
  if model_router_metadata:
112
- metadata.update(model_router_metadata.dict())
135
+ metadata.update(model_router_metadata.model_dump())
113
136
 
114
137
  payload.update(
115
138
  {
@@ -244,7 +267,7 @@ class GPTRouterClient:
244
267
  data: dict = json.loads(line_data.strip())
245
268
  if data["event"] == "error":
246
269
  raise GPTRouterStreamingError(data)
247
- yield parse_obj_as(ChunkedGenerationResponse, data)
270
+ yield TypeAdapter(ChunkedGenerationResponse).validate_python(data)
248
271
  except GPTRouterStreamingError as e:
249
272
  raise e
250
273
  except Exception:
@@ -331,7 +354,7 @@ class GPTRouterClient:
331
354
  data = json.loads(line_data.strip())
332
355
  if data["event"].lower() == "error":
333
356
  raise GPTRouterStreamingError(data["message"])
334
- yield parse_obj_as(ChunkedGenerationResponse, data)
357
+ yield TypeAdapter(ChunkedGenerationResponse).validate_python(data)
335
358
  except GPTRouterStreamingError as e:
336
359
  raise e
337
360
  except Exception:
@@ -390,7 +413,7 @@ class GPTRouterClient:
390
413
  api_payload = {
391
414
  "stream": is_stream,
392
415
  "data": [
393
- request.dict(exclude_none=True, by_alias=True)
416
+ request.model_dump(exclude_none=True, by_alias=True)
394
417
  for request in ordered_generation_requests
395
418
  ],
396
419
  }
@@ -408,7 +431,7 @@ class GPTRouterClient:
408
431
  payload=api_payload,
409
432
  **kwargs,
410
433
  )
411
- return parse_obj_as(GenerationResponse, result)
434
+ return TypeAdapter(GenerationResponse).validate_python(result)
412
435
 
413
436
  async def agenerate(
414
437
  self,
@@ -424,7 +447,7 @@ class GPTRouterClient:
424
447
  api_payload = {
425
448
  "stream": is_stream,
426
449
  "data": [
427
- request.dict(exclude_none=True, by_alias=True)
450
+ request.model_dump(exclude_none=True, by_alias=True)
428
451
  for request in ordered_generation_requests
429
452
  ],
430
453
  }
@@ -443,14 +466,14 @@ class GPTRouterClient:
443
466
  payload=api_payload,
444
467
  **kwargs,
445
468
  )
446
- return parse_obj_as(GenerationResponse, result)
469
+ return TypeAdapter(GenerationResponse).validate_python(result)
447
470
 
448
471
  async def agenerate_images(
449
472
  self, *, image_generation_request: ImageGenerationRequest
450
473
  ) -> List[ImageGenerationResponse]:
451
474
  api_path = "/v1/generate/generate-image"
452
475
  api_method = "POST"
453
- api_payload = image_generation_request.dict()
476
+ api_payload = image_generation_request.model_dump()
454
477
 
455
478
  api_response = await self._async_api_call(
456
479
  path=api_path,
@@ -462,7 +485,7 @@ class GPTRouterClient:
462
485
  generated_images = self._extract_images_from_dict(generated_images)
463
486
 
464
487
  return [
465
- parse_obj_as(ImageGenerationResponse, generated_img)
488
+ ImageGenerationResponse.model_validate(generated_img)
466
489
  for generated_img in generated_images
467
490
  ]
468
491
 
@@ -496,7 +519,7 @@ class GPTRouterClient:
496
519
  """
497
520
  api_path = "/v1/generate/edit-image"
498
521
  api_method = "POST"
499
- api_payload = image_edit_request.dict()
522
+ api_payload = image_edit_request.model_dump()
500
523
 
501
524
  api_response = await self._async_api_call(
502
525
  path=api_path,
@@ -507,7 +530,7 @@ class GPTRouterClient:
507
530
  generated_images = api_response.get("response", {}).get("data", [])
508
531
 
509
532
  return [
510
- parse_obj_as(ImageGenerationResponse, {
533
+ ImageGenerationResponse.model_validate({
511
534
  "url": img.get("url", None),
512
535
  "base64": img.get("b64_json", None),
513
536
  "finish_reason": "SUCCESS"
@@ -0,0 +1,2 @@
1
+ DEFAULT_API_BASE_URL = "https://prod-model-router.writesonic.com/api"
2
+ DEFAULT_REQUEST_TIMEOUT = 60
@@ -44,6 +44,7 @@ class ModelsEnum(Enum):
44
44
  OPENAI_GPT_35_TURBO_INSTRUCT = "gpt-3.5-turbo-instruct"
45
45
 
46
46
  CLAUDE_INSTANT_12 = "claude-instant-1.2"
47
+ CLAUDE_4_5_HAIKU = "claude-haiku-4-5-20251001"
47
48
 
48
49
  DALLE_3 = "dall-e-3"
49
50
 
@@ -197,4 +198,8 @@ class GPTRouterEngines(Enum):
197
198
 
198
199
  CLAUDE_3_7_SONNET_2025_02_19 = ModelDetails(
199
200
  ProvidersEnum.CHAT_ANTHROPIC.value, "claude-3-7-sonnet-2025-02-19"
201
+ )
202
+
203
+ CLAUDE_4_5_HAIKU = ModelDetails(
204
+ ProvidersEnum.CHAT_ANTHROPIC.value, "claude-haiku-4-5-20251001"
200
205
  )
@@ -1,5 +1,4 @@
1
- from uuid import UUID
2
- from pydantic import BaseModel, Field, root_validator, validator
1
+ from pydantic import ConfigDict, BaseModel, Field
3
2
  from enum import Enum
4
3
  from typing import Dict, List, Literal, Optional, Any, Union
5
4
  from humps import camelize
@@ -83,11 +82,7 @@ class ModelGenerationRequest(BaseModel):
83
82
  provider_name: str
84
83
  order: int = Field(int)
85
84
  prompt_params: Optional[GenerationParams] = Field(default={})
86
-
87
- class Config:
88
- alias_generator = to_camel
89
- allow_population_by_field_name = True
90
- protected_namespaces = ()
85
+ model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True, protected_namespaces=())
91
86
 
92
87
 
93
88
  class Usage(BaseModel):
@@ -111,13 +106,9 @@ class GenerationResponse(BaseModel):
111
106
  model: str
112
107
  provider_id: Optional[str] = Field(None, alias="providerId")
113
108
  model_id: Optional[str] = Field(None, alias="modelId")
114
- meta: Optional[Usage]
109
+ meta: Optional[Usage] = None
115
110
  citations: Optional[List[str]] = None
116
-
117
- class Config:
118
- alias_generator = to_camel
119
- allow_population_by_field_name = True
120
- protected_namespaces = ()
111
+ model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True, protected_namespaces=())
121
112
 
122
113
 
123
114
  class ChunkedGenerationResponse(BaseModel):
@@ -138,17 +129,16 @@ class ImageGenerationRequest(BaseModel):
138
129
  user_id: Optional[str] = Field(None, alias="createdByUserId")
139
130
  history_id: Optional[str] = Field(None, alias="historyId")
140
131
  feature: Optional[str] = Field(None, alias="feature")
141
-
142
- class Config:
143
- allow_population_by_field_name = True
144
- protected_namespaces = ()
132
+ model_config = ConfigDict(populate_by_name=True, protected_namespaces=())
145
133
 
146
134
  @property
147
135
  def size(self) -> Optional[str]:
148
136
  return f"{self.width}x{self.height}" if self.width and self.height else None
149
137
 
150
- def dict(self, *args, **kwargs):
151
- base_dict = super().dict(*args, **kwargs, exclude_none=True, by_alias=True)
138
+ def model_dump(self, *args, **kwargs):
139
+ kwargs.setdefault('exclude_none', True)
140
+ kwargs.setdefault('by_alias', True)
141
+ base_dict = super().model_dump(*args, **kwargs)
152
142
  if self.size is not None:
153
143
  base_dict["size"] = self.size
154
144
  return base_dict
@@ -170,13 +160,12 @@ class ImageEditRequest(BaseModel):
170
160
  response_format: Optional[str] = Field(alias="responseFormat", default=None)
171
161
  size: Optional[str] = Field(default="1024x1024")
172
162
  user: Optional[str] = Field(default=None)
163
+ model_config = ConfigDict(populate_by_name=True, protected_namespaces=())
173
164
 
174
- class Config:
175
- allow_population_by_field_name = True
176
- protected_namespaces = ()
177
-
178
- def dict(self, *args, **kwargs):
179
- base_dict = super().dict(*args, **kwargs, exclude_none=True, by_alias=True)
165
+ def model_dump(self, *args, **kwargs):
166
+ kwargs.setdefault('exclude_none', True)
167
+ kwargs.setdefault('by_alias', True)
168
+ base_dict = super().model_dump(*args, **kwargs)
180
169
  return base_dict
181
170
 
182
171
 
@@ -187,13 +176,13 @@ class ModelRouterGenerationRequest(BaseModel):
187
176
  should_validate_token_count: Optional[bool] = Field(
188
177
  default=False, alias="shouldValidateTokenCount"
189
178
  )
179
+ model_config = ConfigDict(populate_by_name=True, protected_namespaces=())
190
180
 
191
- class Config:
192
- allow_population_by_field_name = True
193
-
194
- def dict(self, *args, **kwargs):
181
+ def model_dump(self, *args, **kwargs):
195
182
  # Generate the standard dict first.
196
- result = super().dict(*args, **kwargs, exclude_none=True, by_alias=True)
183
+ kwargs.setdefault('exclude_none', True)
184
+ kwargs.setdefault('by_alias', True)
185
+ result = super().model_dump(*args, **kwargs)
197
186
 
198
187
  result["providerName"] = self.model_provider
199
188
  result["modelName"] = self.model_name
@@ -215,7 +204,8 @@ class ModelRouterGenerationResponse(BaseModel):
215
204
  model: str
216
205
  provider_id: Optional[str] = Field(None, alias="providerId")
217
206
  model_id: Optional[str] = Field(None, alias="modelId")
218
- meta: Optional[Usage]
207
+ meta: Optional[Usage] = None
208
+ model_config = ConfigDict(protected_namespaces=())
219
209
 
220
210
 
221
211
  class EmbeddingsChoice(BaseModel):
@@ -7,13 +7,18 @@ readme = "README.md"
7
7
 
8
8
  [tool.poetry.dependencies]
9
9
  python = "^3.10"
10
- httpx = "^0.25.2"
11
- pydantic = "^1.10.14"
10
+ httpx = "^0.28.1"
11
+ pydantic = "^2.12"
12
12
  pyhumps = "^3.8.0"
13
- python-dotenv = "^1.0.0"
14
- tenacity = "^9.0.0"
13
+ python-dotenv = "^1.2.1"
14
+ tenacity = "^9.1.2"
15
15
 
16
16
 
17
17
  [build-system]
18
18
  requires = ["poetry-core"]
19
19
  build-backend = "poetry.core.masonry.api"
20
+
21
+ [dependency-groups]
22
+ dev = [
23
+ "bump-pydantic (>=0.8.0,<0.9.0)"
24
+ ]
@@ -5,13 +5,13 @@ with open("README.md", "r") as fh:
5
5
 
6
6
  setup(
7
7
  name="GPTRouter",
8
- version="1.0.4",
8
+ version="1.0.6",
9
9
  description="A Python package for working with GPTRouter APIs",
10
10
  long_description=long_description,
11
11
  long_description_content_type="text/markdown",
12
12
  author_email="sirjanpreet@writesonic.com",
13
13
  packages=["gpt_router"],
14
- install_requires=["httpx>=0.23.3", "pydantic>=1.10.13", "pyhumps>=3.8.0"],
14
+ install_requires=["httpx>=0.23.3", "pydantic>=2.0", "pyhumps>=3.8.0"],
15
15
  classifiers=[
16
16
  "Development Status :: 3 - Alpha",
17
17
  "Intended Audience :: Developers",
@@ -1,2 +0,0 @@
1
- DEFAULT_API_BASE_URL = 'https://gpt-router-preview.writesonic.com'
2
- DEFAULT_REQUEST_TIMEOUT = 60
File without changes
File without changes