mbxai 0.5.23__tar.gz → 0.5.24__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.
Files changed (25) hide show
  1. {mbxai-0.5.23 → mbxai-0.5.24}/.vscode/PythonImportHelper-v2-Completion.json +46 -4
  2. {mbxai-0.5.23 → mbxai-0.5.24}/PKG-INFO +1 -1
  3. {mbxai-0.5.23 → mbxai-0.5.24}/pyproject.toml +1 -1
  4. {mbxai-0.5.23 → mbxai-0.5.24}/setup.py +1 -1
  5. {mbxai-0.5.23 → mbxai-0.5.24}/src/mbxai/__init__.py +1 -1
  6. {mbxai-0.5.23 → mbxai-0.5.24}/src/mbxai/mcp/server.py +1 -1
  7. {mbxai-0.5.23 → mbxai-0.5.24}/src/mbxai/openrouter/client.py +92 -5
  8. {mbxai-0.5.23 → mbxai-0.5.24}/src/mbxai/openrouter/config.py +13 -1
  9. {mbxai-0.5.23 → mbxai-0.5.24}/uv.lock +7 -7
  10. {mbxai-0.5.23 → mbxai-0.5.24}/LICENSE +0 -0
  11. {mbxai-0.5.23 → mbxai-0.5.24}/README.md +0 -0
  12. {mbxai-0.5.23 → mbxai-0.5.24}/src/mbxai/core.py +0 -0
  13. {mbxai-0.5.23 → mbxai-0.5.24}/src/mbxai/mcp/__init__.py +0 -0
  14. {mbxai-0.5.23 → mbxai-0.5.24}/src/mbxai/mcp/client.py +0 -0
  15. {mbxai-0.5.23 → mbxai-0.5.24}/src/mbxai/mcp/example.py +0 -0
  16. {mbxai-0.5.23 → mbxai-0.5.24}/src/mbxai/openrouter/__init__.py +0 -0
  17. {mbxai-0.5.23 → mbxai-0.5.24}/src/mbxai/openrouter/models.py +0 -0
  18. {mbxai-0.5.23 → mbxai-0.5.24}/src/mbxai/tools/__init__.py +0 -0
  19. {mbxai-0.5.23 → mbxai-0.5.24}/src/mbxai/tools/client.py +0 -0
  20. {mbxai-0.5.23 → mbxai-0.5.24}/src/mbxai/tools/example.py +0 -0
  21. {mbxai-0.5.23 → mbxai-0.5.24}/src/mbxai/tools/types.py +0 -0
  22. {mbxai-0.5.23 → mbxai-0.5.24}/tests/test_core.py +0 -0
  23. {mbxai-0.5.23 → mbxai-0.5.24}/tests/test_mcp.py +0 -0
  24. {mbxai-0.5.23 → mbxai-0.5.24}/tests/test_openrouter.py +0 -0
  25. {mbxai-0.5.23 → mbxai-0.5.24}/tests/test_tools.py +0 -0
@@ -131,6 +131,14 @@
131
131
  "detail": "typing",
132
132
  "documentation": {}
133
133
  },
134
+ {
135
+ "label": "Optional",
136
+ "importPath": "typing",
137
+ "description": "typing",
138
+ "isExtraImport": true,
139
+ "detail": "typing",
140
+ "documentation": {}
141
+ },
134
142
  {
135
143
  "label": "ClassVar",
136
144
  "importPath": "typing",
@@ -286,6 +294,14 @@
286
294
  "detail": "pydantic",
287
295
  "documentation": {}
288
296
  },
297
+ {
298
+ "label": "Field",
299
+ "importPath": "pydantic",
300
+ "description": "pydantic",
301
+ "isExtraImport": true,
302
+ "detail": "pydantic",
303
+ "documentation": {}
304
+ },
289
305
  {
290
306
  "label": "BaseModel",
291
307
  "importPath": "pydantic",
@@ -422,6 +438,23 @@
422
438
  "detail": "openai",
423
439
  "documentation": {}
424
440
  },
441
+ {
442
+ "label": "time",
443
+ "kind": 6,
444
+ "isExtraImport": true,
445
+ "importPath": "time",
446
+ "description": "time",
447
+ "detail": "time",
448
+ "documentation": {}
449
+ },
450
+ {
451
+ "label": "wraps",
452
+ "importPath": "functools",
453
+ "description": "functools",
454
+ "isExtraImport": true,
455
+ "detail": "functools",
456
+ "documentation": {}
457
+ },
425
458
  {
426
459
  "label": "Enum",
427
460
  "importPath": "enum",
@@ -817,7 +850,7 @@
817
850
  "kind": 6,
818
851
  "importPath": "src.mbxai.openrouter.client",
819
852
  "description": "src.mbxai.openrouter.client",
820
- "peekOfCode": "class OpenRouterError(Exception):\n \"\"\"Base exception for OpenRouter client errors.\"\"\"\n pass\nclass OpenRouterConnectionError(OpenRouterError):\n \"\"\"Raised when there is a connection error.\"\"\"\n pass\nclass OpenRouterAPIError(OpenRouterError):\n \"\"\"Raised when the API returns an error.\"\"\"\n pass\nclass OpenRouterClient:",
853
+ "peekOfCode": "class OpenRouterError(Exception):\n \"\"\"Base exception for OpenRouter client errors.\"\"\"\n pass\nclass OpenRouterConnectionError(OpenRouterError):\n \"\"\"Raised when there is a connection error.\"\"\"\n pass\nclass OpenRouterAPIError(OpenRouterError):\n \"\"\"Raised when the API returns an error.\"\"\"\n pass\ndef with_retry(max_retries: int = 3, initial_delay: float = 1.0, max_delay: float = 10.0):",
821
854
  "detail": "src.mbxai.openrouter.client",
822
855
  "documentation": {}
823
856
  },
@@ -826,7 +859,7 @@
826
859
  "kind": 6,
827
860
  "importPath": "src.mbxai.openrouter.client",
828
861
  "description": "src.mbxai.openrouter.client",
829
- "peekOfCode": "class OpenRouterConnectionError(OpenRouterError):\n \"\"\"Raised when there is a connection error.\"\"\"\n pass\nclass OpenRouterAPIError(OpenRouterError):\n \"\"\"Raised when the API returns an error.\"\"\"\n pass\nclass OpenRouterClient:\n \"\"\"Client for interacting with the OpenRouter API.\"\"\"\n def __init__(\n self,",
862
+ "peekOfCode": "class OpenRouterConnectionError(OpenRouterError):\n \"\"\"Raised when there is a connection error.\"\"\"\n pass\nclass OpenRouterAPIError(OpenRouterError):\n \"\"\"Raised when the API returns an error.\"\"\"\n pass\ndef with_retry(max_retries: int = 3, initial_delay: float = 1.0, max_delay: float = 10.0):\n \"\"\"Decorator to add retry logic to a function.\n Args:\n max_retries: Maximum number of retry attempts",
830
863
  "detail": "src.mbxai.openrouter.client",
831
864
  "documentation": {}
832
865
  },
@@ -835,7 +868,7 @@
835
868
  "kind": 6,
836
869
  "importPath": "src.mbxai.openrouter.client",
837
870
  "description": "src.mbxai.openrouter.client",
838
- "peekOfCode": "class OpenRouterAPIError(OpenRouterError):\n \"\"\"Raised when the API returns an error.\"\"\"\n pass\nclass OpenRouterClient:\n \"\"\"Client for interacting with the OpenRouter API.\"\"\"\n def __init__(\n self,\n token: str,\n model: Union[str, OpenRouterModel] = OpenRouterModel.GPT4_TURBO,\n base_url: Optional[str] = None,",
871
+ "peekOfCode": "class OpenRouterAPIError(OpenRouterError):\n \"\"\"Raised when the API returns an error.\"\"\"\n pass\ndef with_retry(max_retries: int = 3, initial_delay: float = 1.0, max_delay: float = 10.0):\n \"\"\"Decorator to add retry logic to a function.\n Args:\n max_retries: Maximum number of retry attempts\n initial_delay: Initial delay between retries in seconds\n max_delay: Maximum delay between retries in seconds\n \"\"\"",
839
872
  "detail": "src.mbxai.openrouter.client",
840
873
  "documentation": {}
841
874
  },
@@ -844,7 +877,16 @@
844
877
  "kind": 6,
845
878
  "importPath": "src.mbxai.openrouter.client",
846
879
  "description": "src.mbxai.openrouter.client",
847
- "peekOfCode": "class OpenRouterClient:\n \"\"\"Client for interacting with the OpenRouter API.\"\"\"\n def __init__(\n self,\n token: str,\n model: Union[str, OpenRouterModel] = OpenRouterModel.GPT4_TURBO,\n base_url: Optional[str] = None,\n default_headers: Optional[dict[str, str]] = None,\n ) -> None:\n \"\"\"Initialize the OpenRouter client.",
880
+ "peekOfCode": "class OpenRouterClient:\n \"\"\"Client for interacting with the OpenRouter API.\"\"\"\n def __init__(\n self,\n token: str,\n model: Union[str, OpenRouterModel] = OpenRouterModel.GPT4_TURBO,\n base_url: Optional[str] = None,\n default_headers: Optional[dict[str, str]] = None,\n max_retries: int = 3,\n retry_initial_delay: float = 1.0,",
881
+ "detail": "src.mbxai.openrouter.client",
882
+ "documentation": {}
883
+ },
884
+ {
885
+ "label": "with_retry",
886
+ "kind": 2,
887
+ "importPath": "src.mbxai.openrouter.client",
888
+ "description": "src.mbxai.openrouter.client",
889
+ "peekOfCode": "def with_retry(max_retries: int = 3, initial_delay: float = 1.0, max_delay: float = 10.0):\n \"\"\"Decorator to add retry logic to a function.\n Args:\n max_retries: Maximum number of retry attempts\n initial_delay: Initial delay between retries in seconds\n max_delay: Maximum delay between retries in seconds\n \"\"\"\n def decorator(func):\n @wraps(func)\n async def async_wrapper(*args, **kwargs):",
848
890
  "detail": "src.mbxai.openrouter.client",
849
891
  "documentation": {}
850
892
  },
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mbxai
3
- Version: 0.5.23
3
+ Version: 0.5.24
4
4
  Summary: MBX AI SDK
5
5
  Project-URL: Homepage, https://www.mibexx.de
6
6
  Project-URL: Documentation, https://www.mibexx.de
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "mbxai"
7
- version = "0.5.23"
7
+ version = "0.5.24"
8
8
  authors = [
9
9
  { name = "MBX AI" }
10
10
  ]
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name="mbxai",
5
- version="0.5.23",
5
+ version="0.5.24",
6
6
  author="MBX AI",
7
7
  description="MBX AI SDK",
8
8
  long_description=open("README.md").read(),
@@ -2,4 +2,4 @@
2
2
  MBX AI package.
3
3
  """
4
4
 
5
- __version__ = "0.5.23"
5
+ __version__ = "0.5.24"
@@ -31,7 +31,7 @@ class MCPServer:
31
31
  self.app = FastAPI(
32
32
  title=self.name,
33
33
  description=self.description,
34
- version="0.5.23",
34
+ version="0.5.24",
35
35
  )
36
36
 
37
37
  # Initialize MCP server
@@ -4,10 +4,12 @@ OpenRouter client implementation.
4
4
 
5
5
  from typing import Any, Optional, Union
6
6
  from openai import OpenAI, OpenAIError
7
- from pydantic import BaseModel, TypeAdapter
7
+ from pydantic import BaseModel, TypeAdapter, Field
8
8
  from .models import OpenRouterModel, OpenRouterModelRegistry
9
9
  from .config import OpenRouterConfig
10
10
  import logging
11
+ import time
12
+ from functools import wraps
11
13
 
12
14
  logger = logging.getLogger(__name__)
13
15
 
@@ -27,6 +29,65 @@ class OpenRouterAPIError(OpenRouterError):
27
29
  pass
28
30
 
29
31
 
32
+ def with_retry(max_retries: int = 3, initial_delay: float = 1.0, max_delay: float = 10.0):
33
+ """Decorator to add retry logic to a function.
34
+
35
+ Args:
36
+ max_retries: Maximum number of retry attempts
37
+ initial_delay: Initial delay between retries in seconds
38
+ max_delay: Maximum delay between retries in seconds
39
+ """
40
+ def decorator(func):
41
+ @wraps(func)
42
+ async def async_wrapper(*args, **kwargs):
43
+ last_error = None
44
+ delay = initial_delay
45
+
46
+ for attempt in range(max_retries + 1):
47
+ try:
48
+ return await func(*args, **kwargs)
49
+ except (OpenRouterConnectionError, OpenRouterAPIError) as e:
50
+ last_error = e
51
+ if attempt < max_retries:
52
+ logger.warning(f"Attempt {attempt + 1} failed: {str(e)}. Retrying in {delay:.1f}s...")
53
+ await asyncio.sleep(delay)
54
+ delay = min(delay * 2, max_delay)
55
+ else:
56
+ logger.error(f"All {max_retries + 1} attempts failed")
57
+ raise last_error
58
+ except Exception as e:
59
+ # Don't retry other types of errors
60
+ raise e
61
+
62
+ raise last_error
63
+
64
+ @wraps(func)
65
+ def sync_wrapper(*args, **kwargs):
66
+ last_error = None
67
+ delay = initial_delay
68
+
69
+ for attempt in range(max_retries + 1):
70
+ try:
71
+ return func(*args, **kwargs)
72
+ except (OpenRouterConnectionError, OpenRouterAPIError) as e:
73
+ last_error = e
74
+ if attempt < max_retries:
75
+ logger.warning(f"Attempt {attempt + 1} failed: {str(e)}. Retrying in {delay:.1f}s...")
76
+ time.sleep(delay)
77
+ delay = min(delay * 2, max_delay)
78
+ else:
79
+ logger.error(f"All {max_retries + 1} attempts failed")
80
+ raise last_error
81
+ except Exception as e:
82
+ # Don't retry other types of errors
83
+ raise e
84
+
85
+ raise last_error
86
+
87
+ return async_wrapper if asyncio.iscoroutinefunction(func) else sync_wrapper
88
+ return decorator
89
+
90
+
30
91
  class OpenRouterClient:
31
92
  """Client for interacting with the OpenRouter API."""
32
93
 
@@ -36,6 +97,9 @@ class OpenRouterClient:
36
97
  model: Union[str, OpenRouterModel] = OpenRouterModel.GPT4_TURBO,
37
98
  base_url: Optional[str] = None,
38
99
  default_headers: Optional[dict[str, str]] = None,
100
+ max_retries: int = 3,
101
+ retry_initial_delay: float = 1.0,
102
+ retry_max_delay: float = 10.0,
39
103
  ) -> None:
40
104
  """Initialize the OpenRouter client.
41
105
 
@@ -44,6 +108,9 @@ class OpenRouterClient:
44
108
  model: The model to use (default: GPT4_TURBO)
45
109
  base_url: Optional custom base URL for the API
46
110
  default_headers: Optional default headers for API requests
111
+ max_retries: Maximum number of retry attempts (default: 3)
112
+ retry_initial_delay: Initial delay between retries in seconds (default: 1.0)
113
+ retry_max_delay: Maximum delay between retries in seconds (default: 10.0)
47
114
 
48
115
  Raises:
49
116
  OpenRouterError: If initialization fails
@@ -56,7 +123,10 @@ class OpenRouterClient:
56
123
  default_headers=default_headers or {
57
124
  "HTTP-Referer": "https://github.com/mibexx/mbxai",
58
125
  "X-Title": "MBX AI",
59
- }
126
+ },
127
+ max_retries=max_retries,
128
+ retry_initial_delay=retry_initial_delay,
129
+ retry_max_delay=retry_max_delay,
60
130
  )
61
131
 
62
132
  self._client = OpenAI(
@@ -86,6 +156,10 @@ class OpenRouterClient:
86
156
  raise OpenRouterAPIError(f"API error during {operation}: {error_msg}")
87
157
  elif "Connection" in error_msg:
88
158
  raise OpenRouterConnectionError(f"Connection error during {operation}: {error_msg}")
159
+ elif "Expecting value" in error_msg and "line" in error_msg:
160
+ # This is a JSON parsing error, likely due to a truncated or malformed response
161
+ logger.error("JSON parsing error detected. This might be due to a large response or network issues.")
162
+ raise OpenRouterAPIError(f"Response parsing error during {operation}. The response might be too large or malformed.")
89
163
  else:
90
164
  raise OpenRouterError(f"Error during {operation}: {error_msg}")
91
165
 
@@ -111,6 +185,7 @@ class OpenRouterClient:
111
185
  """
112
186
  self.model = value
113
187
 
188
+ @with_retry()
114
189
  def chat_completion(
115
190
  self,
116
191
  messages: list[dict[str, Any]],
@@ -122,9 +197,13 @@ class OpenRouterClient:
122
197
  """Get a chat completion from OpenRouter."""
123
198
  try:
124
199
  # Log the request details
125
- logger.info(f"Sending chat completion request to OpenRouter with model: {model}")
200
+ logger.info(f"Sending chat completion request to OpenRouter with model: {model or self.model}")
126
201
  logger.info(f"Message count: {len(messages)}")
127
202
 
203
+ # Calculate total message size for logging
204
+ total_size = sum(len(str(msg)) for msg in messages)
205
+ logger.info(f"Total message size: {total_size} bytes")
206
+
128
207
  response = self._client.chat.completions.create(
129
208
  messages=messages,
130
209
  model=model or self.model,
@@ -137,7 +216,11 @@ class OpenRouterClient:
137
216
  if hasattr(response, 'choices') and response.choices:
138
217
  logger.info(f"Response has {len(response.choices)} choices")
139
218
  if hasattr(response.choices[0], 'message'):
140
- logger.info(f"First choice has message with content length: {len(response.choices[0].message.content) if response.choices[0].message.content else 0}")
219
+ content = response.choices[0].message.content
220
+ content_length = len(content) if content else 0
221
+ logger.info(f"First choice has message with content length: {content_length}")
222
+ if content_length > 1000000: # Log warning for very large responses
223
+ logger.warning(f"Response content is very large ({content_length} bytes)")
141
224
 
142
225
  return response
143
226
 
@@ -147,11 +230,14 @@ class OpenRouterClient:
147
230
  logger.error(f"Response status: {e.response.status_code}")
148
231
  logger.error(f"Response headers: {e.response.headers}")
149
232
  try:
150
- logger.error(f"Response content: {e.response.text[:1000]}...")
233
+ content = e.response.text
234
+ logger.error(f"Response content length: {len(content)} bytes")
235
+ logger.error(f"Response content preview: {content[:1000]}...")
151
236
  except:
152
237
  logger.error("Could not read response content")
153
238
  self._handle_api_error("chat completion", e)
154
239
 
240
+ @with_retry()
155
241
  def chat_completion_parse(
156
242
  self,
157
243
  messages: list[dict[str, Any]],
@@ -218,6 +304,7 @@ class OpenRouterClient:
218
304
  except Exception as e:
219
305
  self._handle_api_error("chat completion parse", e)
220
306
 
307
+ @with_retry()
221
308
  def embeddings(
222
309
  self,
223
310
  input: Union[str, list[str]],
@@ -1,5 +1,5 @@
1
1
  from pydantic import BaseModel, Field, field_validator
2
- from typing import Union
2
+ from typing import Union, Optional
3
3
  from .models import OpenRouterModel, OpenRouterModelRegistry
4
4
 
5
5
  class OpenRouterConfig(BaseModel):
@@ -21,6 +21,18 @@ class OpenRouterConfig(BaseModel):
21
21
  },
22
22
  description="Default headers to include in all requests"
23
23
  )
24
+ max_retries: int = Field(
25
+ default=3,
26
+ description="Maximum number of retry attempts"
27
+ )
28
+ retry_initial_delay: float = Field(
29
+ default=1.0,
30
+ description="Initial delay between retries in seconds"
31
+ )
32
+ retry_max_delay: float = Field(
33
+ default=10.0,
34
+ description="Maximum delay between retries in seconds"
35
+ )
24
36
 
25
37
  @field_validator("token")
26
38
  def validate_token(cls, v: str) -> str:
@@ -292,11 +292,11 @@ wheels = [
292
292
 
293
293
  [[package]]
294
294
  name = "httpx-sse"
295
- version = "0.5.23"
295
+ version = "0.5.24"
296
296
  source = { registry = "https://pypi.org/simple" }
297
- sdist = { url = "https://files.pythonhosted.org/packages/4c/60/8f4281fa9bbf3c8034fd54c0e7412e66edbab6bc74c4996bd616f8d0406e/httpx-sse-0.5.23.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721", size = 12624 }
297
+ sdist = { url = "https://files.pythonhosted.org/packages/4c/60/8f4281fa9bbf3c8034fd54c0e7412e66edbab6bc74c4996bd616f8d0406e/httpx-sse-0.5.24.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721", size = 12624 }
298
298
  wheels = [
299
- { url = "https://files.pythonhosted.org/packages/e1/9b/a181f281f65d776426002f330c31849b86b31fc9d848db62e16f03ff739f/httpx_sse-0.5.23-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f", size = 7819 },
299
+ { url = "https://files.pythonhosted.org/packages/e1/9b/a181f281f65d776426002f330c31849b86b31fc9d848db62e16f03ff739f/httpx_sse-0.5.24-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f", size = 7819 },
300
300
  ]
301
301
 
302
302
  [[package]]
@@ -446,7 +446,7 @@ wheels = [
446
446
 
447
447
  [[package]]
448
448
  name = "mbxai"
449
- version = "0.5.23"
449
+ version = "0.5.24"
450
450
  source = { editable = "." }
451
451
  dependencies = [
452
452
  { name = "fastapi" },
@@ -980,14 +980,14 @@ wheels = [
980
980
 
981
981
  [[package]]
982
982
  name = "typing-inspection"
983
- version = "0.5.23"
983
+ version = "0.5.24"
984
984
  source = { registry = "https://pypi.org/simple" }
985
985
  dependencies = [
986
986
  { name = "typing-extensions" },
987
987
  ]
988
- sdist = { url = "https://files.pythonhosted.org/packages/82/5c/e6082df02e215b846b4b8c0b887a64d7d08ffaba30605502639d44c06b82/typing_inspection-0.5.23.tar.gz", hash = "sha256:9765c87de36671694a67904bf2c96e395be9c6439bb6c87b5142569dcdd65122", size = 76222 }
988
+ sdist = { url = "https://files.pythonhosted.org/packages/82/5c/e6082df02e215b846b4b8c0b887a64d7d08ffaba30605502639d44c06b82/typing_inspection-0.5.24.tar.gz", hash = "sha256:9765c87de36671694a67904bf2c96e395be9c6439bb6c87b5142569dcdd65122", size = 76222 }
989
989
  wheels = [
990
- { url = "https://files.pythonhosted.org/packages/31/08/aa4fdfb71f7de5176385bd9e90852eaf6b5d622735020ad600f2bab54385/typing_inspection-0.5.23-py3-none-any.whl", hash = "sha256:50e72559fcd2a6367a19f7a7e610e6afcb9fac940c650290eed893d61386832f", size = 14125 },
990
+ { url = "https://files.pythonhosted.org/packages/31/08/aa4fdfb71f7de5176385bd9e90852eaf6b5d622735020ad600f2bab54385/typing_inspection-0.5.24-py3-none-any.whl", hash = "sha256:50e72559fcd2a6367a19f7a7e610e6afcb9fac940c650290eed893d61386832f", size = 14125 },
991
991
  ]
992
992
 
993
993
  [[package]]
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