mbxai 0.6.26__py3-none-any.whl → 0.7.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 +1 -1
- mbxai/mcp/server.py +1 -1
- mbxai/openrouter/client.py +20 -15
- mbxai/tools/client.py +120 -155
- {mbxai-0.6.26.dist-info → mbxai-0.7.0.dist-info}/METADATA +1 -1
- {mbxai-0.6.26.dist-info → mbxai-0.7.0.dist-info}/RECORD +8 -8
- {mbxai-0.6.26.dist-info → mbxai-0.7.0.dist-info}/WHEEL +0 -0
- {mbxai-0.6.26.dist-info → mbxai-0.7.0.dist-info}/licenses/LICENSE +0 -0
mbxai/__init__.py
CHANGED
mbxai/mcp/server.py
CHANGED
mbxai/openrouter/client.py
CHANGED
@@ -205,7 +205,7 @@ 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.
|
208
|
+
response = self._client.responses.create(
|
209
209
|
messages=messages,
|
210
210
|
model=model or self.model,
|
211
211
|
stream=stream,
|
@@ -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, '
|
218
|
-
logger.info(f"Response
|
219
|
-
|
220
|
-
|
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,17 +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
|
-
|
277
|
-
|
272
|
+
# Use responses.parse for structured output
|
273
|
+
response = self._client.responses.parse(
|
278
274
|
model=model or self.model,
|
279
|
-
|
275
|
+
input=messages,
|
276
|
+
text_format=response_format,
|
280
277
|
**kwargs,
|
281
278
|
)
|
282
279
|
|
280
|
+
if not response:
|
281
|
+
logger.error(f"Full response content: {response}")
|
282
|
+
raise OpenRouterAPIError("Invalid response from OpenRouter: empty response")
|
283
|
+
|
283
284
|
# Log response details
|
284
285
|
logger.info("Received response from OpenRouter")
|
285
|
-
if hasattr(response, '
|
286
|
-
logger.info(f"Response
|
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")
|
287
292
|
|
288
293
|
return response
|
289
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
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
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
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
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
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
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
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
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
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
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
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
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,18 +1,18 @@
|
|
1
|
-
mbxai/__init__.py,sha256=
|
1
|
+
mbxai/__init__.py,sha256=3Df0lwLheU0HxspabtS7logrnJNfK1O9b2ErV4pfYBg,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=
|
6
|
+
mbxai/mcp/server.py,sha256=_qWxAxMNosw7PRiuhuqhN1DW7Z2p4PMT6LweVQ8gins,3462
|
7
7
|
mbxai/openrouter/__init__.py,sha256=Ito9Qp_B6q-RLGAQcYyTJVWwR2YAZvNqE-HIYXxhtD8,298
|
8
|
-
mbxai/openrouter/client.py,sha256=
|
8
|
+
mbxai/openrouter/client.py,sha256=16ToKA0x-CdIXRrVzmv-31u9973Yssv-tUvG_InBlI0,13715
|
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=
|
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.
|
16
|
-
mbxai-0.
|
17
|
-
mbxai-0.
|
18
|
-
mbxai-0.
|
15
|
+
mbxai-0.7.0.dist-info/METADATA,sha256=5-1BkoCgNgY04G-lrmfBqO1pGPprVWX2h9hGQGYNAPE,4147
|
16
|
+
mbxai-0.7.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
17
|
+
mbxai-0.7.0.dist-info/licenses/LICENSE,sha256=hEyhc4FxwYo3NQ40yNgZ7STqwVk-1_XcTXOnAPbGJAw,1069
|
18
|
+
mbxai-0.7.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|