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 +1 -1
- mbxai/examples/mcp/mcp_client_example.py +4 -0
- mbxai/mcp/client.py +3 -3
- mbxai/mcp/server.py +1 -1
- mbxai/openrouter/client.py +29 -52
- mbxai/tools/client.py +24 -24
- {mbxai-1.2.2.dist-info → mbxai-1.4.0.dist-info}/METADATA +1 -1
- {mbxai-1.2.2.dist-info → mbxai-1.4.0.dist-info}/RECORD +10 -10
- {mbxai-1.2.2.dist-info → mbxai-1.4.0.dist-info}/WHEEL +0 -0
- {mbxai-1.2.2.dist-info → mbxai-1.4.0.dist-info}/licenses/LICENSE +0 -0
mbxai/__init__.py
CHANGED
@@ -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.
|
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.
|
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.
|
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
mbxai/openrouter/client.py
CHANGED
@@ -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.
|
218
|
-
logger.
|
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.
|
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.
|
285
|
-
logger.
|
286
|
-
logger.
|
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.
|
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
|
-
|
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.
|
413
|
-
logger.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
133
|
+
logger.debug(f" Message {i} - {role}: content='{content}', tool_calls={tool_call_info}")
|
134
134
|
elif tool_call_id:
|
135
|
-
logger.
|
135
|
+
logger.debug(f" Message {i} - {role}: content='{content}', tool_call_id={tool_call_id}")
|
136
136
|
else:
|
137
|
-
logger.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
364
|
+
logger.debug("Final response")
|
365
365
|
return response
|
@@ -1,4 +1,4 @@
|
|
1
|
-
mbxai/__init__.py,sha256=
|
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=
|
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=
|
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=
|
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=
|
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=
|
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.
|
26
|
-
mbxai-1.
|
27
|
-
mbxai-1.
|
28
|
-
mbxai-1.
|
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
|
File without changes
|