mbxai 0.5.23__py3-none-any.whl → 0.5.24__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.24"
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.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:
@@ -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
@@ -1,18 +1,18 @@
1
- mbxai/__init__.py,sha256=XGjnT5mfT2741bWJurwuPoX71kI5-dNSGNDJyUad-vo,48
1
+ mbxai/__init__.py,sha256=uOU108fCrriuZbpIuLG4acnQ0ZDvDwD6AnuvvE9mA4A,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=-oTWHpKJBF2p6Y9s8FhSFY4mytwNzCCx4S2lwR5Xr_E,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=YOjcYkD8VLMVzBd6z_rB7kLGEzNwIVSUEOyMZlcIhkw,13647
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.24.dist-info/METADATA,sha256=yA2MFVTn6yxrLmoC_XgS8frW9MalTT2XEv2Re5S7Org,4108
16
+ mbxai-0.5.24.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
17
+ mbxai-0.5.24.dist-info/licenses/LICENSE,sha256=hEyhc4FxwYo3NQ40yNgZ7STqwVk-1_XcTXOnAPbGJAw,1069
18
+ mbxai-0.5.24.dist-info/RECORD,,
File without changes