mbxai 0.6.27__py3-none-any.whl → 0.7.1__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.6.27"
5
+ __version__ = "0.7.1"
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.6.27",
34
+ version="0.7.1",
35
35
  )
36
36
 
37
37
  # Initialize MCP server
@@ -205,8 +205,8 @@ class OpenRouterClient:
205
205
  total_size = sum(len(str(msg)) for msg in messages)
206
206
  logger.info(f"Total message size: {total_size} bytes")
207
207
 
208
- response = self._client.chat.completions.create(
209
- messages=messages,
208
+ response = self._client.responses.create(
209
+ input=messages,
210
210
  model=model or self.model,
211
211
  stream=stream,
212
212
  **kwargs,
@@ -214,14 +214,10 @@ class OpenRouterClient:
214
214
 
215
215
  # Log response details
216
216
  logger.info("Received response from OpenRouter")
217
- if hasattr(response, 'choices') and response.choices:
218
- logger.info(f"Response has {len(response.choices)} choices")
219
- if hasattr(response.choices[0], 'message'):
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)")
217
+ if hasattr(response, 'output'):
218
+ logger.info(f"Response output length: {len(response.output) if response.output else 0}")
219
+ if hasattr(response, 'tool_calls'):
220
+ logger.info(f"Response includes {len(response.tool_calls)} tool calls")
225
221
 
226
222
  return response
227
223
 
@@ -256,7 +252,7 @@ class OpenRouterClient:
256
252
  **kwargs: Additional parameters
257
253
 
258
254
  Returns:
259
- Parsed completion response
255
+ Parsed completion response with output and output_parsed fields
260
256
 
261
257
  Raises:
262
258
  OpenRouterConnectionError: For connection issues
@@ -273,21 +269,26 @@ class OpenRouterClient:
273
269
  total_size = sum(len(str(msg)) for msg in messages)
274
270
  logger.info(f"Total message size: {total_size} bytes")
275
271
 
276
- # Make the parse request
277
- response = self._client.beta.chat.completions.parse(
278
- messages=messages,
272
+ # Use responses.parse for structured output
273
+ response = self._client.responses.parse(
279
274
  model=model or self.model,
280
- response_format=response_format,
275
+ input=messages,
276
+ text_format=response_format,
281
277
  **kwargs,
282
278
  )
283
279
 
284
- if not response or not hasattr(response, 'choices') or not response.choices:
280
+ if not response:
285
281
  logger.error(f"Full response content: {response}")
286
- raise OpenRouterAPIError("Invalid response from OpenRouter: missing choices")
282
+ raise OpenRouterAPIError("Invalid response from OpenRouter: empty response")
287
283
 
288
284
  # Log response details
289
285
  logger.info("Received response from OpenRouter")
290
- logger.info(f"Response has {len(response.choices)} choices")
286
+ if hasattr(response, 'output'):
287
+ logger.info(f"Response output length: {len(response.output) if response.output else 0}")
288
+ if hasattr(response, 'output_parsed'):
289
+ logger.info("Response includes parsed output")
290
+ if hasattr(response, 'tool_calls'):
291
+ logger.info(f"Response includes {len(response.tool_calls)} tool calls")
291
292
 
292
293
  return response
293
294
 
mbxai/tools/client.py CHANGED
@@ -210,7 +210,6 @@ class ToolClient:
210
210
  if tools:
211
211
  logger.info(f"Available tools: {[tool['function']['name'] for tool in tools]}")
212
212
  kwargs["tools"] = tools
213
- kwargs["tool_choice"] = "auto"
214
213
 
215
214
  while True:
216
215
  # Get the model's response
@@ -224,87 +223,71 @@ class ToolClient:
224
223
  if stream:
225
224
  return response
226
225
 
227
- message = response.choices[0].message
228
- # Add the assistant's message with tool calls
229
- assistant_message = {
230
- "role": "assistant",
231
- "content": message.content or None, # Ensure content is None if empty
232
- }
233
- if message.tool_calls:
234
- assistant_message["tool_calls"] = [
235
- {
236
- "id": tool_call.id,
237
- "type": "function",
238
- "function": {
239
- "name": tool_call.function.name,
240
- "arguments": tool_call.function.arguments,
241
- },
242
- }
243
- for tool_call in message.tool_calls
226
+ # Parse the response output
227
+ output = response.output
228
+
229
+ # Get all function calls from the output
230
+ tool_calls = []
231
+ if isinstance(output, list):
232
+ tool_calls = [
233
+ tc for tc in output
234
+ if isinstance(tc, dict) and tc.get("type") == "function_call"
244
235
  ]
245
- messages.append(assistant_message)
246
- logger.info(f"Message count: {len(messages)}, Added assistant message with tool calls: {[tc.function.name for tc in message.tool_calls] if message.tool_calls else None}")
247
-
248
- # If there are no tool calls, we're done
249
- if not message.tool_calls:
250
- return response
251
-
252
- # Process all tool calls
253
- tool_responses = []
254
- for tool_call in message.tool_calls:
255
- tool = self._tools.get(tool_call.function.name)
256
- if not tool:
257
- raise ValueError(f"Unknown tool: {tool_call.function.name}")
258
-
259
- # Parse arguments if they're a string
260
- arguments = tool_call.function.arguments
261
- if isinstance(arguments, str):
236
+
237
+ # Process function calls if any
238
+ if tool_calls:
239
+ logger.info(f"Processing {len(tool_calls)} function calls")
240
+
241
+ # Process each function call
242
+ for tool_call in tool_calls:
243
+ logger.info(f"Processing tool call: {tool_call['name']}")
244
+
245
+ # Get the tool
246
+ tool = self._tools.get(tool_call["name"])
247
+ if not tool:
248
+ raise ValueError(f"Unknown tool: {tool_call['name']}")
249
+
250
+ # Parse arguments
262
251
  try:
263
- arguments = json.loads(arguments)
252
+ arguments = json.loads(tool_call["arguments"])
264
253
  except json.JSONDecodeError as e:
265
254
  logger.error(f"Failed to parse tool arguments: {e}")
266
- raise ValueError(f"Invalid tool arguments format: {arguments}")
255
+ raise ValueError(f"Invalid tool arguments format: {tool_call['arguments']}")
267
256
 
268
- # Call the tool
269
- logger.info(f"Calling tool: {tool.name} with args: {self._truncate_dict(arguments)}")
270
- try:
271
- if inspect.iscoroutinefunction(tool.function):
272
- result = await asyncio.wait_for(tool.function(**arguments), timeout=300.0) # 5 minutes timeout
273
- else:
274
- result = tool.function(**arguments)
275
- logger.info(f"Tool {tool.name} completed successfully")
276
- except asyncio.TimeoutError:
277
- logger.error(f"Tool {tool.name} timed out after 5 minutes")
278
- result = {"error": "Tool execution timed out after 5 minutes"}
279
- except Exception as e:
280
- logger.error(f"Error calling tool {tool.name}: {str(e)}")
281
- result = {"error": f"Tool execution failed: {str(e)}"}
282
-
283
- # Convert result to JSON string if it's not already
284
- if not isinstance(result, str):
285
- result = json.dumps(result)
286
-
287
- # Create the tool response
288
- tool_response = {
289
- "role": "tool",
290
- "tool_call_id": tool_call.id,
291
- "content": result,
292
- }
293
- tool_responses.append(tool_response)
294
- logger.info(f"Created tool response for call ID {tool_call.id}")
295
-
296
- # Add all tool responses to the messages
297
- messages.extend(tool_responses)
298
- logger.info(f"Message count: {len(messages)}, Added {len(tool_responses)} tool responses to messages")
299
-
300
- # Validate the message sequence
301
- self._validate_message_sequence(messages, validate_responses=True)
302
-
303
- # Log the messages we're about to send
304
- self._log_messages(messages, validate_responses=False)
305
-
306
- # Continue the loop to get the next response
307
- continue
257
+ # Call the tool
258
+ logger.info(f"Calling tool: {tool.name} with args: {self._truncate_dict(arguments)}")
259
+ try:
260
+ if inspect.iscoroutinefunction(tool.function):
261
+ result = await asyncio.wait_for(tool.function(**arguments), timeout=300.0)
262
+ else:
263
+ result = tool.function(**arguments)
264
+ logger.info(f"Tool {tool.name} completed successfully")
265
+ except asyncio.TimeoutError:
266
+ logger.error(f"Tool {tool.name} timed out after 5 minutes")
267
+ result = {"error": "Tool execution timed out after 5 minutes"}
268
+ except Exception as e:
269
+ logger.error(f"Error calling tool {tool.name}: {str(e)}")
270
+ result = {"error": f"Tool execution failed: {str(e)}"}
271
+
272
+ # Convert result to string if needed
273
+ if not isinstance(result, str):
274
+ result = json.dumps(result)
275
+
276
+ # Append the function call and result to messages
277
+ messages.append(tool_call) # Append the model's function call
278
+ messages.append({
279
+ "type": "function_call_output",
280
+ "call_id": tool_call["call_id"],
281
+ "output": result
282
+ })
283
+
284
+ logger.info(f"Added function call and output for {tool_call['name']}")
285
+
286
+ # Continue the conversation after processing all calls
287
+ continue
288
+ else:
289
+ logger.info("Added assistant response")
290
+ return response
308
291
 
309
292
  async def parse(
310
293
  self,
@@ -330,7 +313,6 @@ class ToolClient:
330
313
  if tools:
331
314
  logger.info(f"Available tools: {[tool['function']['name'] for tool in tools]}")
332
315
  kwargs["tools"] = tools
333
- kwargs["tool_choice"] = "auto"
334
316
 
335
317
  while True:
336
318
  # Get the model's response
@@ -341,85 +323,68 @@ class ToolClient:
341
323
  **kwargs,
342
324
  )
343
325
 
344
- message = response.choices[0].message
345
- # Add the assistant's message with tool calls
346
- assistant_message = {
347
- "role": "assistant",
348
- "content": message.content or None,
349
- "parsed": message.parsed or None,
350
- }
351
- if message.tool_calls:
352
- assistant_message["tool_calls"] = [
353
- {
354
- "id": tool_call.id,
355
- "type": "function",
356
- "function": {
357
- "name": tool_call.function.name,
358
- "arguments": tool_call.function.arguments,
359
- },
360
- }
361
- for tool_call in message.tool_calls
326
+ # Parse the response output
327
+ output = response.output
328
+
329
+ # Get all function calls from the output
330
+ tool_calls = []
331
+ if isinstance(output, list):
332
+ tool_calls = [
333
+ tc for tc in output
334
+ if isinstance(tc, dict) and tc.get("type") == "function_call"
362
335
  ]
363
- messages.append(assistant_message)
364
- logger.info(f"Message count: {len(messages)}, Added assistant message with tool calls: {[tc.function.name for tc in message.tool_calls] if message.tool_calls else None}")
365
-
366
- # If there are no tool calls, we're done
367
- if not message.tool_calls:
368
- return response
369
-
370
- # Process all tool calls
371
- tool_responses = []
372
- for tool_call in message.tool_calls:
373
- tool = self._tools.get(tool_call.function.name)
374
- if not tool:
375
- raise ValueError(f"Unknown tool: {tool_call.function.name}")
376
-
377
- # Parse arguments if they're a string
378
- arguments = tool_call.function.arguments
379
- if isinstance(arguments, str):
336
+
337
+ # Process function calls if any
338
+ if tool_calls:
339
+ logger.info(f"Processing {len(tool_calls)} function calls")
340
+
341
+ # Process each function call
342
+ for tool_call in tool_calls:
343
+ logger.info(f"Processing tool call: {tool_call['name']}")
344
+
345
+ # Get the tool
346
+ tool = self._tools.get(tool_call["name"])
347
+ if not tool:
348
+ raise ValueError(f"Unknown tool: {tool_call['name']}")
349
+
350
+ # Parse arguments
380
351
  try:
381
- arguments = json.loads(arguments)
352
+ arguments = json.loads(tool_call["arguments"])
382
353
  except json.JSONDecodeError as e:
383
354
  logger.error(f"Failed to parse tool arguments: {e}")
384
- raise ValueError(f"Invalid tool arguments format: {arguments}")
355
+ raise ValueError(f"Invalid tool arguments format: {tool_call['arguments']}")
385
356
 
386
- # Call the tool
387
- logger.info(f"Calling tool: {tool.name} with args: {self._truncate_dict(arguments)}")
388
- try:
389
- if inspect.iscoroutinefunction(tool.function):
390
- result = await asyncio.wait_for(tool.function(**arguments), timeout=300.0) # 5 minutes timeout
391
- else:
392
- result = tool.function(**arguments)
393
- logger.info(f"Tool {tool.name} completed successfully")
394
- except asyncio.TimeoutError:
395
- logger.error(f"Tool {tool.name} timed out after 5 minutes")
396
- result = {"error": "Tool execution timed out after 5 minutes"}
397
- except Exception as e:
398
- logger.error(f"Error calling tool {tool.name}: {str(e)}")
399
- result = {"error": f"Tool execution failed: {str(e)}"}
400
-
401
- # Convert result to JSON string if it's not already
402
- if not isinstance(result, str):
403
- result = json.dumps(result)
404
-
405
- # Create the tool response
406
- tool_response = {
407
- "role": "tool",
408
- "tool_call_id": tool_call.id,
409
- "content": result,
410
- }
411
- tool_responses.append(tool_response)
412
- logger.info(f"Created tool response for call ID {tool_call.id}")
413
-
414
- # Add all tool responses to the messages
415
- messages.extend(tool_responses)
416
- logger.info(f"Message count: {len(messages)}, Added {len(tool_responses)} tool responses to messages")
417
-
418
- # Validate the message sequence
419
- self._validate_message_sequence(messages, validate_responses=True)
420
-
421
- # Log the messages we're about to send
422
- self._log_messages(messages, validate_responses=False)
423
-
424
- # Continue the loop to get the next response
425
- continue
357
+ # Call the tool
358
+ logger.info(f"Calling tool: {tool.name} with args: {self._truncate_dict(arguments)}")
359
+ try:
360
+ if inspect.iscoroutinefunction(tool.function):
361
+ result = await asyncio.wait_for(tool.function(**arguments), timeout=300.0)
362
+ else:
363
+ result = tool.function(**arguments)
364
+ logger.info(f"Tool {tool.name} completed successfully")
365
+ except asyncio.TimeoutError:
366
+ logger.error(f"Tool {tool.name} timed out after 5 minutes")
367
+ result = {"error": "Tool execution timed out after 5 minutes"}
368
+ except Exception as e:
369
+ logger.error(f"Error calling tool {tool.name}: {str(e)}")
370
+ result = {"error": f"Tool execution failed: {str(e)}"}
371
+
372
+ # Convert result to string if needed
373
+ if not isinstance(result, str):
374
+ result = json.dumps(result)
375
+
376
+ # Append the function call and result to messages
377
+ messages.append(tool_call) # Append the model's function call
378
+ messages.append({
379
+ "type": "function_call_output",
380
+ "call_id": tool_call["call_id"],
381
+ "output": result
382
+ })
383
+
384
+ logger.info(f"Added function call and output for {tool_call['name']}")
385
+
386
+ # Continue the conversation after processing all calls
387
+ continue
388
+ else:
389
+ logger.info("Added assistant response with parsed output")
390
+ return response
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mbxai
3
- Version: 0.6.27
3
+ Version: 0.7.1
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=OKyb_lQ2-RORvCDJpCVmYgx4xQW5Wd6FX-Mju2vweGk,48
1
+ mbxai/__init__.py,sha256=b04ZHhDxHhRGITIm9P-7ipelT2KxZfA1ZiICj2ooHHE,47
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=m3FBMqewv6b8ZJ7V9TnHqHxJ3acHDQDHvbuYhweI9-g,5283
5
5
  mbxai/mcp/example.py,sha256=oaol7AvvZnX86JWNz64KvPjab5gg1VjVN3G8eFSzuaE,2350
6
- mbxai/mcp/server.py,sha256=d5l9778YxCnTErgdmBVi3n56FdIyJGUR_nCWQgR9Gtw,3463
6
+ mbxai/mcp/server.py,sha256=dLvuETOxKlKIOISvsFqjZm7Gjui_yKsafJAjsoebrCo,3462
7
7
  mbxai/openrouter/__init__.py,sha256=Ito9Qp_B6q-RLGAQcYyTJVWwR2YAZvNqE-HIYXxhtD8,298
8
- mbxai/openrouter/client.py,sha256=1HmWPJvP5lNVwSguaAX6ZlKth0-DUZT9GiJRi6JOgxo,13759
8
+ mbxai/openrouter/client.py,sha256=w7m3txv1O0KcpzLZM54r8WiH9vkrtDSAP9j5Q2XjMbk,13712
9
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=ogxrHvgJ7OR62Lmd5x9Eh5d2C0jqWyQis7Zy3yKpZ78,218
12
- mbxai/tools/client.py,sha256=qOf8MiQ8_flPUNiMioOyjeEaV8rN1EBWb98T8qTC3D4,17582
12
+ mbxai/tools/client.py,sha256=uubKrzz_T_EAwkCMh3jo8ylWYJ41_KdFhj3-Tyuvc3g,16006
13
13
  mbxai/tools/example.py,sha256=1HgKK39zzUuwFbnp3f0ThyWVfA_8P28PZcTwaUw5K78,2232
14
14
  mbxai/tools/types.py,sha256=PEJ2AxBqywbJCp689QZhG87rDHWNuKGnmB5CCQsAMlw,5251
15
- mbxai-0.6.27.dist-info/METADATA,sha256=072pPTX8EUuxy5tJhiHomexjRlEGtSZEW-ZYnfwOW4U,4148
16
- mbxai-0.6.27.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
17
- mbxai-0.6.27.dist-info/licenses/LICENSE,sha256=hEyhc4FxwYo3NQ40yNgZ7STqwVk-1_XcTXOnAPbGJAw,1069
18
- mbxai-0.6.27.dist-info/RECORD,,
15
+ mbxai-0.7.1.dist-info/METADATA,sha256=_zFLBe8WFVEyU_5JeHNrNzzDaA673EoxXtuRfUTkVJs,4147
16
+ mbxai-0.7.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
17
+ mbxai-0.7.1.dist-info/licenses/LICENSE,sha256=hEyhc4FxwYo3NQ40yNgZ7STqwVk-1_XcTXOnAPbGJAw,1069
18
+ mbxai-0.7.1.dist-info/RECORD,,
File without changes