mbxai 0.5.23__py3-none-any.whl → 0.5.25__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.
mbxai/__init__.py CHANGED
@@ -2,4 +2,4 @@
2
2
  MBX AI package.
3
3
  """
4
4
 
5
- __version__ = "0.5.23"
5
+ __version__ = "0.5.25"
mbxai/mcp/server.py CHANGED
@@ -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.25",
35
35
  )
36
36
 
37
37
  # Initialize MCP server
@@ -4,10 +4,13 @@ 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
+ import asyncio
13
+ from functools import wraps
11
14
 
12
15
  logger = logging.getLogger(__name__)
13
16
 
@@ -27,6 +30,65 @@ class OpenRouterAPIError(OpenRouterError):
27
30
  pass
28
31
 
29
32
 
33
+ def with_retry(max_retries: int = 3, initial_delay: float = 1.0, max_delay: float = 10.0):
34
+ """Decorator to add retry logic to a function.
35
+
36
+ Args:
37
+ max_retries: Maximum number of retry attempts
38
+ initial_delay: Initial delay between retries in seconds
39
+ max_delay: Maximum delay between retries in seconds
40
+ """
41
+ def decorator(func):
42
+ @wraps(func)
43
+ async def async_wrapper(*args, **kwargs):
44
+ last_error = None
45
+ delay = initial_delay
46
+
47
+ for attempt in range(max_retries + 1):
48
+ try:
49
+ return await func(*args, **kwargs)
50
+ except (OpenRouterConnectionError, OpenRouterAPIError) as e:
51
+ last_error = e
52
+ if attempt < max_retries:
53
+ logger.warning(f"Attempt {attempt + 1} failed: {str(e)}. Retrying in {delay:.1f}s...")
54
+ await asyncio.sleep(delay)
55
+ delay = min(delay * 2, max_delay)
56
+ else:
57
+ logger.error(f"All {max_retries + 1} attempts failed")
58
+ raise last_error
59
+ except Exception as e:
60
+ # Don't retry other types of errors
61
+ raise e
62
+
63
+ raise last_error
64
+
65
+ @wraps(func)
66
+ def sync_wrapper(*args, **kwargs):
67
+ last_error = None
68
+ delay = initial_delay
69
+
70
+ for attempt in range(max_retries + 1):
71
+ try:
72
+ return func(*args, **kwargs)
73
+ except (OpenRouterConnectionError, OpenRouterAPIError) as e:
74
+ last_error = e
75
+ if attempt < max_retries:
76
+ logger.warning(f"Attempt {attempt + 1} failed: {str(e)}. Retrying in {delay:.1f}s...")
77
+ time.sleep(delay)
78
+ delay = min(delay * 2, max_delay)
79
+ else:
80
+ logger.error(f"All {max_retries + 1} attempts failed")
81
+ raise last_error
82
+ except Exception as e:
83
+ # Don't retry other types of errors
84
+ raise e
85
+
86
+ raise last_error
87
+
88
+ return async_wrapper if asyncio.iscoroutinefunction(func) else sync_wrapper
89
+ return decorator
90
+
91
+
30
92
  class OpenRouterClient:
31
93
  """Client for interacting with the OpenRouter API."""
32
94
 
@@ -36,6 +98,9 @@ class OpenRouterClient:
36
98
  model: Union[str, OpenRouterModel] = OpenRouterModel.GPT4_TURBO,
37
99
  base_url: Optional[str] = None,
38
100
  default_headers: Optional[dict[str, str]] = None,
101
+ max_retries: int = 3,
102
+ retry_initial_delay: float = 1.0,
103
+ retry_max_delay: float = 10.0,
39
104
  ) -> None:
40
105
  """Initialize the OpenRouter client.
41
106
 
@@ -44,6 +109,9 @@ class OpenRouterClient:
44
109
  model: The model to use (default: GPT4_TURBO)
45
110
  base_url: Optional custom base URL for the API
46
111
  default_headers: Optional default headers for API requests
112
+ max_retries: Maximum number of retry attempts (default: 3)
113
+ retry_initial_delay: Initial delay between retries in seconds (default: 1.0)
114
+ retry_max_delay: Maximum delay between retries in seconds (default: 10.0)
47
115
 
48
116
  Raises:
49
117
  OpenRouterError: If initialization fails
@@ -56,7 +124,10 @@ class OpenRouterClient:
56
124
  default_headers=default_headers or {
57
125
  "HTTP-Referer": "https://github.com/mibexx/mbxai",
58
126
  "X-Title": "MBX AI",
59
- }
127
+ },
128
+ max_retries=max_retries,
129
+ retry_initial_delay=retry_initial_delay,
130
+ retry_max_delay=retry_max_delay,
60
131
  )
61
132
 
62
133
  self._client = OpenAI(
@@ -86,6 +157,10 @@ class OpenRouterClient:
86
157
  raise OpenRouterAPIError(f"API error during {operation}: {error_msg}")
87
158
  elif "Connection" in error_msg:
88
159
  raise OpenRouterConnectionError(f"Connection error during {operation}: {error_msg}")
160
+ elif "Expecting value" in error_msg and "line" in error_msg:
161
+ # This is a JSON parsing error, likely due to a truncated or malformed response
162
+ logger.error("JSON parsing error detected. This might be due to a large response or network issues.")
163
+ raise OpenRouterAPIError(f"Response parsing error during {operation}. The response might be too large or malformed.")
89
164
  else:
90
165
  raise OpenRouterError(f"Error during {operation}: {error_msg}")
91
166
 
@@ -111,6 +186,7 @@ class OpenRouterClient:
111
186
  """
112
187
  self.model = value
113
188
 
189
+ @with_retry()
114
190
  def chat_completion(
115
191
  self,
116
192
  messages: list[dict[str, Any]],
@@ -122,9 +198,13 @@ class OpenRouterClient:
122
198
  """Get a chat completion from OpenRouter."""
123
199
  try:
124
200
  # Log the request details
125
- logger.info(f"Sending chat completion request to OpenRouter with model: {model}")
201
+ logger.info(f"Sending chat completion request to OpenRouter with model: {model or self.model}")
126
202
  logger.info(f"Message count: {len(messages)}")
127
203
 
204
+ # Calculate total message size for logging
205
+ total_size = sum(len(str(msg)) for msg in messages)
206
+ logger.info(f"Total message size: {total_size} bytes")
207
+
128
208
  response = self._client.chat.completions.create(
129
209
  messages=messages,
130
210
  model=model or self.model,
@@ -137,7 +217,11 @@ class OpenRouterClient:
137
217
  if hasattr(response, 'choices') and response.choices:
138
218
  logger.info(f"Response has {len(response.choices)} choices")
139
219
  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}")
220
+ content = response.choices[0].message.content
221
+ content_length = len(content) if content else 0
222
+ logger.info(f"First choice has message with content length: {content_length}")
223
+ if content_length > 1000000: # Log warning for very large responses
224
+ logger.warning(f"Response content is very large ({content_length} bytes)")
141
225
 
142
226
  return response
143
227
 
@@ -147,11 +231,14 @@ class OpenRouterClient:
147
231
  logger.error(f"Response status: {e.response.status_code}")
148
232
  logger.error(f"Response headers: {e.response.headers}")
149
233
  try:
150
- logger.error(f"Response content: {e.response.text[:1000]}...")
234
+ content = e.response.text
235
+ logger.error(f"Response content length: {len(content)} bytes")
236
+ logger.error(f"Response content preview: {content[:1000]}...")
151
237
  except:
152
238
  logger.error("Could not read response content")
153
239
  self._handle_api_error("chat completion", e)
154
240
 
241
+ @with_retry()
155
242
  def chat_completion_parse(
156
243
  self,
157
244
  messages: list[dict[str, Any]],
@@ -218,6 +305,7 @@ class OpenRouterClient:
218
305
  except Exception as e:
219
306
  self._handle_api_error("chat completion parse", e)
220
307
 
308
+ @with_retry()
221
309
  def embeddings(
222
310
  self,
223
311
  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:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mbxai
3
- Version: 0.5.23
3
+ Version: 0.5.25
4
4
  Summary: MBX AI SDK
5
5
  Project-URL: Homepage, https://www.mibexx.de
6
6
  Project-URL: Documentation, https://www.mibexx.de
@@ -1,18 +1,18 @@
1
- mbxai/__init__.py,sha256=XGjnT5mfT2741bWJurwuPoX71kI5-dNSGNDJyUad-vo,48
1
+ mbxai/__init__.py,sha256=TPQPmobkBuhgo14NoMCbpjcR4Lgi3T-hc27EY0cDggk,48
2
2
  mbxai/core.py,sha256=WMvmU9TTa7M_m-qWsUew4xH8Ul6xseCZ2iBCXJTW-Bs,196
3
3
  mbxai/mcp/__init__.py,sha256=_ek9iYdYqW5saKetj4qDci11jxesQDiHPJRpHMKkxgU,175
4
4
  mbxai/mcp/client.py,sha256=B8ZpH-uecmTCgoDw65LwwVxsFWVoX-08t5ff0hOEPXk,6011
5
5
  mbxai/mcp/example.py,sha256=oaol7AvvZnX86JWNz64KvPjab5gg1VjVN3G8eFSzuaE,2350
6
- mbxai/mcp/server.py,sha256=fg0IE9ANDjb4JcgbY7Ox73VOoSjCGVQJFwb8REzRQ3s,3463
6
+ mbxai/mcp/server.py,sha256=T0-Y7FeHRFqSTp2ERU96fOQlQJKjMFxg8oqC4dzBmBA,3463
7
7
  mbxai/openrouter/__init__.py,sha256=Ito9Qp_B6q-RLGAQcYyTJVWwR2YAZvNqE-HIYXxhtD8,298
8
- mbxai/openrouter/client.py,sha256=S0FzTwiyqezhCzp2eKJHoCrAmmX1H8dKZajLu2ly18k,9661
9
- mbxai/openrouter/config.py,sha256=MTX_YHsFrM7JYqovJSkEF6JzVyIdajeI5Dja2CALH58,2874
8
+ mbxai/openrouter/client.py,sha256=RO5tbF42vkcjxjvC-QFB8DGA0gQLljH3KPBn3HgZV8I,13662
9
+ mbxai/openrouter/config.py,sha256=Ia93s-auim9Sq71eunVDbn9ET5xX2zusXpV4JBdHAzs,3251
10
10
  mbxai/openrouter/models.py,sha256=b3IjjtZAjeGOf2rLsdnCD1HacjTnS8jmv_ZXorc-KJQ,2604
11
11
  mbxai/tools/__init__.py,sha256=QUFaXhDm-UKcuAtT1rbKzhBkvyRBVokcQIOf9cxIuwc,160
12
12
  mbxai/tools/client.py,sha256=t7rdITqgCbDXQPFOZhGj6VDDPAwqdilJMKPfCOcJaFo,17279
13
13
  mbxai/tools/example.py,sha256=1HgKK39zzUuwFbnp3f0ThyWVfA_8P28PZcTwaUw5K78,2232
14
14
  mbxai/tools/types.py,sha256=fo5t9UbsHGynhA88vD_ecgDqL8iLvt2E1h1ym43Rrgk,745
15
- mbxai-0.5.23.dist-info/METADATA,sha256=9Wxoifgnpdi99EM7ERyGzybUI1__7dluzbjMLDvgfE4,4108
16
- mbxai-0.5.23.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
17
- mbxai-0.5.23.dist-info/licenses/LICENSE,sha256=hEyhc4FxwYo3NQ40yNgZ7STqwVk-1_XcTXOnAPbGJAw,1069
18
- mbxai-0.5.23.dist-info/RECORD,,
15
+ mbxai-0.5.25.dist-info/METADATA,sha256=Mwegs4jsfD2lik8nockINT3KfNvUEyg1zD8RPBps9O0,4108
16
+ mbxai-0.5.25.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
17
+ mbxai-0.5.25.dist-info/licenses/LICENSE,sha256=hEyhc4FxwYo3NQ40yNgZ7STqwVk-1_XcTXOnAPbGJAw,1069
18
+ mbxai-0.5.25.dist-info/RECORD,,
File without changes