mbxai 1.2.2__py3-none-any.whl → 1.4.0__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__ = "1.2.2"
5
+ __version__ = "1.4.0"
@@ -51,6 +51,10 @@ def main():
51
51
  if not hasattr(response, 'choices'):
52
52
  logger.error(f"Invalid response format - no choices attribute: {response}")
53
53
  return
54
+
55
+ if response.choices is None:
56
+ logger.error("Response choices is None")
57
+ return
54
58
 
55
59
  if not response.choices:
56
60
  logger.error("No choices in response")
mbxai/mcp/client.py CHANGED
@@ -86,7 +86,7 @@ class MCPClient(ToolClient):
86
86
 
87
87
  try:
88
88
  result = response.json()
89
- logger.info(f"Tool {tool.name} response parsed successfully")
89
+ logger.debug(f"Tool {tool.name} response parsed successfully")
90
90
  return result
91
91
  except Exception as e:
92
92
  logger.error(f"Failed to parse tool {tool.name} response: {str(e)}")
@@ -105,7 +105,7 @@ class MCPClient(ToolClient):
105
105
 
106
106
  # Extract tools array from response
107
107
  tools_data = response_data.get("tools", [])
108
- logger.info(f"Received {len(tools_data)} tools from server {name}")
108
+ logger.debug(f"Received {len(tools_data)} tools from server {name}")
109
109
 
110
110
  # Register each tool
111
111
  for idx, tool_data in enumerate(tools_data):
@@ -128,7 +128,7 @@ class MCPClient(ToolClient):
128
128
 
129
129
  # Register the tool with ToolClient
130
130
  self._tools[tool.name] = tool
131
- logger.info(f"Successfully registered tool: {tool.name}")
131
+ logger.debug(f"Successfully registered tool: {tool.name}")
132
132
  except Exception as e:
133
133
  logger.error(f"Failed to register tool: {str(e)}")
134
134
  logger.error(f"Tool data that caused the error: {json.dumps(tool_data, indent=2)}")
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="1.2.2",
34
+ version="1.4.0",
35
35
  )
36
36
 
37
37
  # Initialize MCP server
@@ -214,12 +214,12 @@ class OpenRouterClient:
214
214
  """Get a chat completion from OpenRouter."""
215
215
  try:
216
216
  # Log the request details
217
- logger.info(f"Sending chat completion request to OpenRouter with model: {model or self.model}")
218
- logger.info(f"Message count: {len(messages)}")
217
+ logger.debug(f"Sending chat completion request to OpenRouter with model: {model or self.model}")
218
+ logger.debug(f"Message count: {len(messages)}")
219
219
 
220
220
  # Calculate total message size for logging
221
221
  total_size = sum(len(str(msg)) for msg in messages)
222
- logger.info(f"Total message size: {total_size} bytes")
222
+ logger.debug(f"Total message size: {total_size} bytes")
223
223
 
224
224
  request = {
225
225
  "model": model or self.model,
@@ -234,6 +234,15 @@ class OpenRouterClient:
234
234
  logger.error("Received None response from OpenRouter API")
235
235
  raise OpenRouterAPIError("Received None response from OpenRouter API")
236
236
 
237
+ # Validate response structure
238
+ if not hasattr(response, 'choices'):
239
+ logger.error(f"Response missing 'choices' attribute. Available attributes: {dir(response)}")
240
+ raise OpenRouterAPIError("Invalid response format: missing 'choices' attribute")
241
+
242
+ if response.choices is None:
243
+ logger.error("Response choices is None")
244
+ raise OpenRouterAPIError("Invalid response format: choices is None")
245
+
237
246
  logger.debug(f"Response type: {type(response)}")
238
247
  logger.debug(f"Response attributes: {dir(response)}")
239
248
  logger.debug(f"Received response from OpenRouter: {len(response.choices)} choices")
@@ -281,13 +290,13 @@ class OpenRouterClient:
281
290
 
282
291
  try:
283
292
  # Log the request details
284
- logger.info(f"Sending parse request to OpenRouter with model: {model or self.model}")
285
- logger.info(f"Message count: {len(messages)}")
286
- logger.info(f"Response format: {response_format}")
293
+ logger.debug(f"Sending parse request to OpenRouter with model: {model or self.model}")
294
+ logger.debug(f"Message count: {len(messages)}")
295
+ logger.debug(f"Response format: {response_format}")
287
296
 
288
297
  # Calculate total message size for logging
289
298
  total_size = sum(len(str(msg)) for msg in messages)
290
- logger.info(f"Total message size: {total_size} bytes")
299
+ logger.debug(f"Total message size: {total_size} bytes")
291
300
 
292
301
  try:
293
302
  response = self._client.beta.chat.completions.parse(**request)
@@ -320,48 +329,7 @@ class OpenRouterClient:
320
329
  logger.error("Received None response from OpenRouter API")
321
330
  raise OpenRouterAPIError("Received None response from OpenRouter API")
322
331
 
323
- # Try to get the raw response content if available
324
- if hasattr(response, '_response'):
325
- try:
326
- raw_content = response._response.text
327
- logger.debug(f"Raw response content: {raw_content[:1000]}...")
328
- except Exception as e:
329
- logger.debug(f"Could not get raw response content: {e}")
330
-
331
- # Validate response structure
332
- if not hasattr(response, 'choices'):
333
- logger.error(f"Response missing 'choices' attribute. Available attributes: {dir(response)}")
334
- raise OpenRouterAPIError("Invalid response format: missing 'choices' attribute")
335
-
336
- if not response.choices:
337
- logger.error("Response has empty choices list")
338
- raise OpenRouterAPIError("Invalid response format: empty choices list")
339
-
340
- if not hasattr(response.choices[0], 'message'):
341
- logger.error(f"First choice missing 'message' attribute. Available attributes: {dir(response.choices[0])}")
342
- raise OpenRouterAPIError("Invalid response format: missing 'message' attribute in first choice")
343
-
344
- # Check if the message has a parsed attribute or content
345
- if not hasattr(response.choices[0].message, 'parsed') and not hasattr(response.choices[0].message, 'content'):
346
- logger.error(f"Message missing both 'parsed' and 'content' attributes. Available attributes: {dir(response.choices[0].message)}")
347
- raise OpenRouterAPIError("Invalid response format: message must have either 'parsed' or 'content' attribute")
348
-
349
- # If there's no parsed attribute but there is content, try to parse it
350
- if not hasattr(response.choices[0].message, 'parsed') and hasattr(response.choices[0].message, 'content'):
351
- try:
352
- content = response.choices[0].message.content
353
- if isinstance(content, str):
354
- parsed = json.loads(content)
355
- response.choices[0].message.parsed = parsed
356
- else:
357
- response.choices[0].message.parsed = content
358
- except Exception as e:
359
- stack_trace = traceback.format_exc()
360
- logger.error(f"Failed to parse message content: {str(e)}")
361
- logger.error(f"Stack trace:\n{stack_trace}")
362
- raise OpenRouterAPIError(f"Failed to parse message content: {str(e)}\nStack trace:\n{stack_trace}")
363
-
364
- logger.info(f"Received response from OpenRouter: {len(response.choices)} choices")
332
+ logger.debug(f"Received response from OpenRouter: {len(response.choices)} choices")
365
333
 
366
334
  return response
367
335
 
@@ -409,13 +377,13 @@ class OpenRouterClient:
409
377
  response_format_param = type_to_response_format_param(response_format)
410
378
 
411
379
  # Log the request details
412
- logger.info(f"Sending parsed chat completion request to OpenRouter with model: {model or self.model}")
413
- logger.info(f"Message count: {len(messages)}")
380
+ logger.debug(f"Sending parsed chat completion request to OpenRouter with model: {model or self.model}")
381
+ logger.debug(f"Message count: {len(messages)}")
414
382
  logger.debug(f"Response format: {json.dumps(response_format_param, indent=2)}")
415
383
 
416
384
  # Calculate total message size for logging
417
385
  total_size = sum(len(str(msg)) for msg in messages)
418
- logger.info(f"Total message size: {total_size} bytes")
386
+ logger.debug(f"Total message size: {total_size} bytes")
419
387
 
420
388
  request = {
421
389
  "model": model or self.model,
@@ -431,6 +399,15 @@ class OpenRouterClient:
431
399
  logger.error("Received None response from OpenRouter API")
432
400
  raise OpenRouterAPIError("Received None response from OpenRouter API")
433
401
 
402
+ # Validate response structure
403
+ if not hasattr(response, 'choices'):
404
+ logger.error(f"Response missing 'choices' attribute. Available attributes: {dir(response)}")
405
+ raise OpenRouterAPIError("Invalid response format: missing 'choices' attribute")
406
+
407
+ if response.choices is None:
408
+ logger.error("Response choices is None")
409
+ raise OpenRouterAPIError("Invalid response format: choices is None")
410
+
434
411
  logger.debug(f"Response type: {type(response)}")
435
412
  logger.debug(f"Response attributes: {dir(response)}")
436
413
  logger.debug(f"Received response from OpenRouter: {len(response.choices)} choices")
mbxai/tools/client.py CHANGED
@@ -48,7 +48,7 @@ class ToolClient:
48
48
  schema=schema,
49
49
  )
50
50
  self._tools[name] = tool
51
- logger.info(f"Registered tool: {name}")
51
+ logger.debug(f"Registered tool: {name}")
52
52
 
53
53
  def _truncate_content(self, content: str | None, max_length: int = 100) -> str:
54
54
  """Truncate content for logging."""
@@ -88,11 +88,11 @@ class ToolClient:
88
88
  # Track tool calls
89
89
  for tc in msg["tool_calls"]:
90
90
  tool_call_ids.add(tc["id"])
91
- logger.info(f"Found tool call {tc['id']} for {tc['function']['name']} in message {i}")
91
+ logger.debug(f"Found tool call {tc['id']} for {tc['function']['name']} in message {i}")
92
92
  elif role == "tool":
93
93
  # Track tool responses
94
94
  tool_response_ids.add(msg["tool_call_id"])
95
- logger.info(f"Found tool response for call ID {msg['tool_call_id']} in message {i}")
95
+ logger.debug(f"Found tool response for call ID {msg['tool_call_id']} in message {i}")
96
96
 
97
97
  # Only validate responses if requested
98
98
  if validate_responses:
@@ -118,7 +118,7 @@ class ToolClient:
118
118
  messages: The messages to log
119
119
  validate_responses: Whether to validate that all tool calls have responses
120
120
  """
121
- logger.info("Sending messages to OpenRouter:")
121
+ logger.debug("Sending messages to OpenRouter:")
122
122
  for i, msg in enumerate(messages):
123
123
  role = msg.get("role", "unknown")
124
124
  content = self._truncate_content(msg.get("content"))
@@ -130,11 +130,11 @@ class ToolClient:
130
130
  f"{tc['function']['name']}(id={tc['id']})"
131
131
  for tc in tool_calls
132
132
  ]
133
- logger.info(f" Message {i} - {role}: content='{content}', tool_calls={tool_call_info}")
133
+ logger.debug(f" Message {i} - {role}: content='{content}', tool_calls={tool_call_info}")
134
134
  elif tool_call_id:
135
- logger.info(f" Message {i} - {role}: content='{content}', tool_call_id={tool_call_id}")
135
+ logger.debug(f" Message {i} - {role}: content='{content}', tool_call_id={tool_call_id}")
136
136
  else:
137
- logger.info(f" Message {i} - {role}: content='{content}'")
137
+ logger.debug(f" Message {i} - {role}: content='{content}'")
138
138
 
139
139
  # Validate message sequence
140
140
  self._validate_message_sequence(messages, validate_responses)
@@ -166,7 +166,7 @@ class ToolClient:
166
166
  raise ValueError(f"Invalid tool arguments format: {arguments}")
167
167
 
168
168
  # Call the tool
169
- logger.info(f"Calling tool: {tool.name} with args: {self._truncate_dict(arguments)}")
169
+ logger.debug(f"Calling tool: {tool.name} with args: {self._truncate_dict(arguments)}")
170
170
  if inspect.iscoroutinefunction(tool.function):
171
171
  result = await tool.function(**arguments)
172
172
  else:
@@ -183,11 +183,11 @@ class ToolClient:
183
183
  "content": result,
184
184
  }
185
185
  tool_responses.append(tool_response)
186
- logger.info(f"Created tool response for call ID {tool_call.id}")
186
+ logger.debug(f"Created tool response for call ID {tool_call.id}")
187
187
 
188
188
  # Add all tool responses to the messages
189
189
  messages.extend(tool_responses)
190
- logger.info(f"Message count: {len(messages)}, Added {len(tool_responses)} tool responses to messages")
190
+ logger.debug(f"Message count: {len(messages)}, Added {len(tool_responses)} tool responses to messages")
191
191
 
192
192
  # Validate the message sequence
193
193
  self._validate_message_sequence(messages, validate_responses=True)
@@ -207,7 +207,7 @@ class ToolClient:
207
207
  tools = [tool.to_openai_function() for tool in self._tools.values()]
208
208
 
209
209
  if tools:
210
- logger.info(f"Available tools: {[tool['function']['name'] for tool in tools]}")
210
+ logger.debug(f"Available tools: {[tool['function']['name'] for tool in tools]}")
211
211
  kwargs["tools"] = tools
212
212
 
213
213
  while True:
@@ -234,11 +234,11 @@ class ToolClient:
234
234
  tool_calls = response.choices[0].message.tool_calls
235
235
  # Process function calls if any
236
236
  if tool_calls:
237
- logger.info(f"Tool calls: {len(tool_calls)} - Tools: {[tc.function.name for tc in tool_calls]}")
237
+ logger.debug(f"Tool calls: {len(tool_calls)} - Tools: {[tc.function.name for tc in tool_calls]}")
238
238
 
239
239
  # Process each function call
240
240
  for tool_call in tool_calls:
241
- logger.info(f"Processing tool call: {tool_call.function.name}")
241
+ logger.debug(f"Processing tool call: {tool_call.function.name}")
242
242
 
243
243
  # Get the tool
244
244
  tool = self._tools.get(tool_call.function.name)
@@ -253,10 +253,10 @@ class ToolClient:
253
253
  raise ValueError(f"Invalid tool arguments format: {tool_call.function.arguments}")
254
254
 
255
255
  # Call the tool
256
- logger.info(f"Calling tool: {tool.name} with args: {self._truncate_dict(arguments)}")
256
+ logger.debug(f"Calling tool: {tool.name} with args: {self._truncate_dict(arguments)}")
257
257
  try:
258
258
  result = tool.function(**arguments)
259
- logger.info(f"Tool {tool.name} completed successfully")
259
+ logger.debug(f"Tool {tool.name} completed successfully")
260
260
  except Exception as e:
261
261
  logger.error(f"Error calling tool {tool.name}: {str(e)}")
262
262
  result = {"error": f"Tool execution failed: {str(e)}"}
@@ -273,12 +273,12 @@ class ToolClient:
273
273
  "content": result,
274
274
  })
275
275
 
276
- logger.info(f"Added function call and output for {tool_call.function.name}")
276
+ logger.debug(f"Added function call and output for {tool_call.function.name}")
277
277
 
278
278
  # Continue the conversation after processing all calls
279
279
  continue
280
280
  else:
281
- logger.info("Final response")
281
+ logger.debug("Final response")
282
282
  return response
283
283
 
284
284
  def parse(
@@ -293,7 +293,7 @@ class ToolClient:
293
293
  tools = [tool.to_openai_function() for tool in self._tools.values()]
294
294
 
295
295
  if tools:
296
- logger.info(f"Available tools: {[tool['function']['name'] for tool in tools]}")
296
+ logger.debug(f"Available tools: {[tool['function']['name'] for tool in tools]}")
297
297
  kwargs["tools"] = tools
298
298
 
299
299
  while True:
@@ -317,11 +317,11 @@ class ToolClient:
317
317
  tool_calls = response.choices[0].message.tool_calls
318
318
  # Process function calls if any
319
319
  if tool_calls:
320
- logger.info(f"Tool calls: {len(tool_calls)} - Tools: {[tc.function.name for tc in tool_calls]}")
320
+ logger.debug(f"Tool calls: {len(tool_calls)} - Tools: {[tc.function.name for tc in tool_calls]}")
321
321
 
322
322
  # Process each function call
323
323
  for tool_call in tool_calls:
324
- logger.info(f"Processing tool call: {tool_call.function.name}")
324
+ logger.debug(f"Processing tool call: {tool_call.function.name}")
325
325
 
326
326
  # Get the tool
327
327
  tool = self._tools.get(tool_call.function.name)
@@ -336,10 +336,10 @@ class ToolClient:
336
336
  raise ValueError(f"Invalid tool arguments format: {tool_call.function.arguments}")
337
337
 
338
338
  # Call the tool
339
- logger.info(f"Calling tool: {tool.name} with args: {self._truncate_dict(arguments)}")
339
+ logger.debug(f"Calling tool: {tool.name} with args: {self._truncate_dict(arguments)}")
340
340
  try:
341
341
  result = tool.function(**arguments)
342
- logger.info(f"Tool {tool.name} completed successfully")
342
+ logger.debug(f"Tool {tool.name} completed successfully")
343
343
  except Exception as e:
344
344
  logger.error(f"Error calling tool {tool.name}: {str(e)}")
345
345
  result = {"error": f"Tool execution failed: {str(e)}"}
@@ -356,10 +356,10 @@ class ToolClient:
356
356
  "content": result,
357
357
  })
358
358
 
359
- logger.info(f"Added function call and output for {tool_call.function.name}")
359
+ logger.debug(f"Added function call and output for {tool_call.function.name}")
360
360
 
361
361
  # Continue the conversation after processing all calls
362
362
  continue
363
363
  else:
364
- logger.info("Final response")
364
+ logger.debug("Final response")
365
365
  return response
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mbxai
3
- Version: 1.2.2
3
+ Version: 1.4.0
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,4 +1,4 @@
1
- mbxai/__init__.py,sha256=CaI4CMV8Eswv3HMCO8KNKv5hrNhl0wFIHDHcX6sagwA,47
1
+ mbxai/__init__.py,sha256=E7p4_3-qHNUH7NcRh8unQZwHeS5pRX-O8zL-n0jaR2M,47
2
2
  mbxai/core.py,sha256=WMvmU9TTa7M_m-qWsUew4xH8Ul6xseCZ2iBCXJTW-Bs,196
3
3
  mbxai/examples/openrouter_example.py,sha256=-grXHKMmFLoh-yUIEMc31n8Gg1S7uSazBWCIOWxgbyQ,1317
4
4
  mbxai/examples/parse_example.py,sha256=eCKMJoOl6qwo8sDP6Trc6ncgjPlgTqi5tPE2kB5_P0k,3821
@@ -7,22 +7,22 @@ mbxai/examples/request.json,sha256=fjVMses305wVUXgcmjESCvPgP81Js8Kk6zHjZ8EDyEg,5
7
7
  mbxai/examples/response.json,sha256=4SGJJyQjWWeN__Mrxm6ZtHIo1NUtLEheldd5KaA2mHw,856
8
8
  mbxai/examples/send_request.py,sha256=O5gCHUHy7RvkEFo9IQATgnSOfOdu8OqKHfjAlLDwWPg,6023
9
9
  mbxai/examples/tool_client_example.py,sha256=9DNaejXLA85dPbExMiv5y76qlFhzOJF9E5EnMOsy_Dc,3993
10
- mbxai/examples/mcp/mcp_client_example.py,sha256=R4H-OU5FvGL41cCkTdLa3bocsmVJYQYOcOHRf61nbZc,2822
10
+ mbxai/examples/mcp/mcp_client_example.py,sha256=d5-TRHNDdp3nT_NGt0tKpT3VUAJVvqAHSyqkzk9Dd2s,2972
11
11
  mbxai/examples/mcp/mcp_server_example.py,sha256=nFfg22Jnc6HMW_ezLO3So1xwDdx2_rItj5CR-y_Nevs,3966
12
12
  mbxai/mcp/__init__.py,sha256=_ek9iYdYqW5saKetj4qDci11jxesQDiHPJRpHMKkxgU,175
13
- mbxai/mcp/client.py,sha256=rrUq2ObvUZOcz6fRyZ7ukB2rzUXIPqnQtBFwyt3VrJY,5216
13
+ mbxai/mcp/client.py,sha256=QRzId6o4_WRWVv3rtm8cfZZGaoY_UlaOO-oqNjY-tmw,5219
14
14
  mbxai/mcp/example.py,sha256=oaol7AvvZnX86JWNz64KvPjab5gg1VjVN3G8eFSzuaE,2350
15
- mbxai/mcp/server.py,sha256=779lPFAXYbOc3dBO-tK_PBCtICStyDc1zQaNdo1xfxU,3454
15
+ mbxai/mcp/server.py,sha256=V-o8BugXj9Jj_yUIGepH0fCw8lrxYSpVRpI5CFfAvnk,3454
16
16
  mbxai/openrouter/__init__.py,sha256=Ito9Qp_B6q-RLGAQcYyTJVWwR2YAZvNqE-HIYXxhtD8,298
17
- mbxai/openrouter/client.py,sha256=au2VDSoIU6zKd1LIlQ6ObG_YpUK2AaG9bW-fBV2iVgk,19882
17
+ mbxai/openrouter/client.py,sha256=3LD6WDJ8wjo_nefH5d1NJCsrWPvBc_KBf2NsItUoSt8,18302
18
18
  mbxai/openrouter/config.py,sha256=Ia93s-auim9Sq71eunVDbn9ET5xX2zusXpV4JBdHAzs,3251
19
19
  mbxai/openrouter/models.py,sha256=b3IjjtZAjeGOf2rLsdnCD1HacjTnS8jmv_ZXorc-KJQ,2604
20
20
  mbxai/openrouter/schema.py,sha256=H_77ZrA9zmbX155bWpCJj1jehUyJPS0QybEW1IVAoe0,540
21
21
  mbxai/tools/__init__.py,sha256=ogxrHvgJ7OR62Lmd5x9Eh5d2C0jqWyQis7Zy3yKpZ78,218
22
- mbxai/tools/client.py,sha256=YbQMWMw8c5XPwarT1u6TRgbsqYBJaTsO8j3EyCBoz9g,14906
22
+ mbxai/tools/client.py,sha256=j6yB2hYxvWbaQ5SqN1Fs_YFdPtwettdcMoXcdeV-520,14930
23
23
  mbxai/tools/example.py,sha256=1HgKK39zzUuwFbnp3f0ThyWVfA_8P28PZcTwaUw5K78,2232
24
24
  mbxai/tools/types.py,sha256=7gNIJBjzr9i4DT50OGLMjn3-6yBXqlK-kIz_RWcqywo,5875
25
- mbxai-1.2.2.dist-info/METADATA,sha256=9lTianvqao9yH8Ix7X_KrYOhzuBL3yny0XxdexDPQTw,4147
26
- mbxai-1.2.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
27
- mbxai-1.2.2.dist-info/licenses/LICENSE,sha256=hEyhc4FxwYo3NQ40yNgZ7STqwVk-1_XcTXOnAPbGJAw,1069
28
- mbxai-1.2.2.dist-info/RECORD,,
25
+ mbxai-1.4.0.dist-info/METADATA,sha256=EWpGN5OMQTKiNodh1zxL-tU1Ud3U3-Q20Sz-Ep7YUuA,4147
26
+ mbxai-1.4.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
27
+ mbxai-1.4.0.dist-info/licenses/LICENSE,sha256=hEyhc4FxwYo3NQ40yNgZ7STqwVk-1_XcTXOnAPbGJAw,1069
28
+ mbxai-1.4.0.dist-info/RECORD,,
File without changes