mbxai 0.5.12__py3-none-any.whl → 0.5.14__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/tools/client.py +76 -33
- {mbxai-0.5.12.dist-info → mbxai-0.5.14.dist-info}/METADATA +1 -1
- {mbxai-0.5.12.dist-info → mbxai-0.5.14.dist-info}/RECORD +7 -7
- {mbxai-0.5.12.dist-info → mbxai-0.5.14.dist-info}/WHEEL +0 -0
- {mbxai-0.5.12.dist-info → mbxai-0.5.14.dist-info}/licenses/LICENSE +0 -0
mbxai/__init__.py
CHANGED
mbxai/mcp/server.py
CHANGED
mbxai/tools/client.py
CHANGED
@@ -48,7 +48,57 @@ class ToolClient:
|
|
48
48
|
schema=schema,
|
49
49
|
)
|
50
50
|
self._tools[name] = tool
|
51
|
-
logger.
|
51
|
+
logger.info(f"Registered tool: {name}")
|
52
|
+
|
53
|
+
def _truncate_content(self, content: str | None, max_length: int = 100) -> str:
|
54
|
+
"""Truncate content for logging."""
|
55
|
+
if not content:
|
56
|
+
return "None"
|
57
|
+
if len(content) <= max_length:
|
58
|
+
return content
|
59
|
+
return content[:max_length] + "..."
|
60
|
+
|
61
|
+
def _log_messages(self, messages: list[dict[str, Any]]) -> None:
|
62
|
+
"""Log the messages being sent to OpenRouter."""
|
63
|
+
logger.info("Sending messages to OpenRouter:")
|
64
|
+
for msg in messages:
|
65
|
+
role = msg.get("role", "unknown")
|
66
|
+
content = self._truncate_content(msg.get("content"))
|
67
|
+
tool_calls = msg.get("tool_calls", [])
|
68
|
+
tool_call_id = msg.get("tool_call_id")
|
69
|
+
|
70
|
+
if tool_calls:
|
71
|
+
tool_call_info = [
|
72
|
+
f"{tc['function']['name']}(id={tc['id']})"
|
73
|
+
for tc in tool_calls
|
74
|
+
]
|
75
|
+
logger.info(f" {role}: content='{content}', tool_calls={tool_call_info}")
|
76
|
+
elif tool_call_id:
|
77
|
+
logger.info(f" {role}: content='{content}', tool_call_id={tool_call_id}")
|
78
|
+
else:
|
79
|
+
logger.info(f" {role}: content='{content}'")
|
80
|
+
|
81
|
+
# Validate tool call responses
|
82
|
+
tool_call_ids = set()
|
83
|
+
tool_response_ids = set()
|
84
|
+
|
85
|
+
for msg in messages:
|
86
|
+
if msg.get("role") == "assistant" and "tool_calls" in msg:
|
87
|
+
for tc in msg["tool_calls"]:
|
88
|
+
tool_call_ids.add(tc["id"])
|
89
|
+
elif msg.get("role") == "tool":
|
90
|
+
tool_response_ids.add(msg["tool_call_id"])
|
91
|
+
|
92
|
+
missing_responses = tool_call_ids - tool_response_ids
|
93
|
+
if missing_responses:
|
94
|
+
logger.error(f"Missing tool responses for call IDs: {missing_responses}")
|
95
|
+
logger.error("Message sequence:")
|
96
|
+
for msg in messages:
|
97
|
+
role = msg.get("role", "unknown")
|
98
|
+
if role == "assistant" and "tool_calls" in msg:
|
99
|
+
logger.error(f" Assistant message with tool calls: {[tc['id'] for tc in msg['tool_calls']]}")
|
100
|
+
elif role == "tool":
|
101
|
+
logger.error(f" Tool response for call ID: {msg['tool_call_id']}")
|
52
102
|
|
53
103
|
async def chat(
|
54
104
|
self,
|
@@ -72,11 +122,14 @@ class ToolClient:
|
|
72
122
|
tools = [tool.to_openai_function() for tool in self._tools.values()]
|
73
123
|
|
74
124
|
if tools:
|
75
|
-
logger.
|
125
|
+
logger.info(f"Available tools: {[tool['function']['name'] for tool in tools]}")
|
76
126
|
kwargs["tools"] = tools
|
77
127
|
kwargs["tool_choice"] = "auto"
|
78
128
|
|
79
129
|
while True:
|
130
|
+
# Log messages before sending to OpenRouter
|
131
|
+
self._log_messages(messages)
|
132
|
+
|
80
133
|
# Get the model's response
|
81
134
|
response = self._client.chat_completion(
|
82
135
|
messages=messages,
|
@@ -107,7 +160,7 @@ class ToolClient:
|
|
107
160
|
for tool_call in message.tool_calls
|
108
161
|
]
|
109
162
|
messages.append(assistant_message)
|
110
|
-
logger.
|
163
|
+
logger.info(f"Assistant message: content='{self._truncate_content(message.content)}', tool_calls={[tc.function.name for tc in message.tool_calls] if message.tool_calls else None}")
|
111
164
|
|
112
165
|
# If there are no tool calls, we're done
|
113
166
|
if not message.tool_calls:
|
@@ -116,35 +169,25 @@ class ToolClient:
|
|
116
169
|
# Handle all tool calls before getting the next model response
|
117
170
|
tool_responses = []
|
118
171
|
for tool_call in message.tool_calls:
|
119
|
-
logger.debug(f"Processing tool call: {tool_call}")
|
120
|
-
logger.debug(f"Tool call ID: {tool_call.id}")
|
121
|
-
logger.debug(f"Tool call function: {tool_call.function}")
|
122
|
-
logger.debug(f"Tool call arguments: {tool_call.function.arguments}")
|
123
|
-
|
124
172
|
tool = self._tools.get(tool_call.function.name)
|
125
173
|
if not tool:
|
126
174
|
raise ValueError(f"Unknown tool: {tool_call.function.name}")
|
127
175
|
|
128
176
|
# Parse arguments if they're a string
|
129
177
|
arguments = tool_call.function.arguments
|
130
|
-
logger.debug(f"Raw arguments type: {type(arguments)}")
|
131
|
-
logger.debug(f"Raw arguments: {arguments}")
|
132
|
-
|
133
178
|
if isinstance(arguments, str):
|
134
179
|
try:
|
135
180
|
arguments = json.loads(arguments)
|
136
|
-
logger.debug(f"Parsed arguments: {arguments}")
|
137
181
|
except json.JSONDecodeError as e:
|
138
182
|
logger.error(f"Failed to parse tool arguments: {e}")
|
139
183
|
raise ValueError(f"Invalid tool arguments format: {arguments}")
|
140
184
|
|
141
185
|
# Call the tool
|
142
|
-
logger.
|
186
|
+
logger.info(f"Calling tool: {tool.name} with args: {self._truncate_content(json.dumps(arguments))}")
|
143
187
|
if inspect.iscoroutinefunction(tool.function):
|
144
188
|
result = await tool.function(**arguments)
|
145
189
|
else:
|
146
190
|
result = tool.function(**arguments)
|
147
|
-
logger.debug(f"Tool result: {result}")
|
148
191
|
|
149
192
|
# Convert result to JSON string if it's not already
|
150
193
|
if not isinstance(result, str):
|
@@ -157,11 +200,19 @@ class ToolClient:
|
|
157
200
|
"content": result,
|
158
201
|
}
|
159
202
|
tool_responses.append(tool_response)
|
160
|
-
logger.
|
203
|
+
logger.info(f"Tool response for call ID {tool_call.id}: {self._truncate_content(result)}")
|
161
204
|
|
162
205
|
# Add all tool responses to the messages
|
163
206
|
messages.extend(tool_responses)
|
164
|
-
logger.
|
207
|
+
logger.info(f"Added {len(tool_responses)} tool responses to messages")
|
208
|
+
|
209
|
+
# Validate that all tool calls have responses
|
210
|
+
tool_call_ids = {tc.id for tc in message.tool_calls}
|
211
|
+
tool_response_ids = {tr["tool_call_id"] for tr in tool_responses}
|
212
|
+
missing_responses = tool_call_ids - tool_response_ids
|
213
|
+
if missing_responses:
|
214
|
+
logger.error(f"Missing tool responses for call IDs: {missing_responses}")
|
215
|
+
raise ValueError(f"Missing tool responses for call IDs: {missing_responses}")
|
165
216
|
|
166
217
|
# Get a new response from the model with all tool results
|
167
218
|
response = self._client.chat_completion(
|
@@ -193,7 +244,7 @@ class ToolClient:
|
|
193
244
|
for tool_call in message.tool_calls
|
194
245
|
]
|
195
246
|
messages.append(assistant_message)
|
196
|
-
logger.
|
247
|
+
logger.info(f"Assistant message: content='{self._truncate_content(message.content)}', tool_calls={[tc.function.name for tc in message.tool_calls] if message.tool_calls else None}")
|
197
248
|
|
198
249
|
# If there are no more tool calls, we're done
|
199
250
|
if not message.tool_calls:
|
@@ -223,11 +274,14 @@ class ToolClient:
|
|
223
274
|
tools = [tool.to_openai_function() for tool in self._tools.values()]
|
224
275
|
|
225
276
|
if tools:
|
226
|
-
logger.
|
277
|
+
logger.info(f"Available tools: {[tool['function']['name'] for tool in tools]}")
|
227
278
|
kwargs["tools"] = tools
|
228
279
|
kwargs["tool_choice"] = "auto"
|
229
280
|
|
230
281
|
while True:
|
282
|
+
# Log messages before sending to OpenRouter
|
283
|
+
self._log_messages(messages)
|
284
|
+
|
231
285
|
# Get the model's response
|
232
286
|
response = self._client.chat_completion_parse(
|
233
287
|
messages=messages,
|
@@ -259,7 +313,7 @@ class ToolClient:
|
|
259
313
|
for tool_call in message.tool_calls
|
260
314
|
]
|
261
315
|
messages.append(assistant_message)
|
262
|
-
logger.
|
316
|
+
logger.info(f"Assistant message: content='{self._truncate_content(message.content)}', tool_calls={[tc.function.name for tc in message.tool_calls] if message.tool_calls else None}")
|
263
317
|
|
264
318
|
# If there are no tool calls, we're done
|
265
319
|
if not message.tool_calls:
|
@@ -268,35 +322,25 @@ class ToolClient:
|
|
268
322
|
# Handle all tool calls before getting the next model response
|
269
323
|
tool_responses = []
|
270
324
|
for tool_call in message.tool_calls:
|
271
|
-
logger.debug(f"Processing tool call: {tool_call}")
|
272
|
-
logger.debug(f"Tool call ID: {tool_call.id}")
|
273
|
-
logger.debug(f"Tool call function: {tool_call.function}")
|
274
|
-
logger.debug(f"Tool call arguments: {tool_call.function.arguments}")
|
275
|
-
|
276
325
|
tool = self._tools.get(tool_call.function.name)
|
277
326
|
if not tool:
|
278
327
|
raise ValueError(f"Unknown tool: {tool_call.function.name}")
|
279
328
|
|
280
329
|
# Parse arguments if they're a string
|
281
330
|
arguments = tool_call.function.arguments
|
282
|
-
logger.debug(f"Raw arguments type: {type(arguments)}")
|
283
|
-
logger.debug(f"Raw arguments: {arguments}")
|
284
|
-
|
285
331
|
if isinstance(arguments, str):
|
286
332
|
try:
|
287
333
|
arguments = json.loads(arguments)
|
288
|
-
logger.debug(f"Parsed arguments: {arguments}")
|
289
334
|
except json.JSONDecodeError as e:
|
290
335
|
logger.error(f"Failed to parse tool arguments: {e}")
|
291
336
|
raise ValueError(f"Invalid tool arguments format: {arguments}")
|
292
337
|
|
293
338
|
# Call the tool
|
294
|
-
logger.
|
339
|
+
logger.info(f"Calling tool: {tool.name} with args: {self._truncate_content(json.dumps(arguments))}")
|
295
340
|
if inspect.iscoroutinefunction(tool.function):
|
296
341
|
result = await tool.function(**arguments)
|
297
342
|
else:
|
298
343
|
result = tool.function(**arguments)
|
299
|
-
logger.debug(f"Tool result: {result}")
|
300
344
|
|
301
345
|
# Convert result to JSON string if it's not already
|
302
346
|
if not isinstance(result, str):
|
@@ -309,11 +353,10 @@ class ToolClient:
|
|
309
353
|
"content": result,
|
310
354
|
}
|
311
355
|
tool_responses.append(tool_response)
|
312
|
-
logger.
|
356
|
+
logger.info(f"Tool response: {self._truncate_content(result)}")
|
313
357
|
|
314
358
|
# Add all tool responses to the messages
|
315
359
|
messages.extend(tool_responses)
|
316
|
-
logger.debug(f"Added all tool responses to messages: {tool_responses}")
|
317
360
|
|
318
361
|
# Get a new response from the model with all tool results
|
319
362
|
response = self._client.chat_completion_parse(
|
@@ -346,7 +389,7 @@ class ToolClient:
|
|
346
389
|
for tool_call in message.tool_calls
|
347
390
|
]
|
348
391
|
messages.append(assistant_message)
|
349
|
-
logger.
|
392
|
+
logger.info(f"Assistant message: content='{self._truncate_content(message.content)}', tool_calls={[tc.function.name for tc in message.tool_calls] if message.tool_calls else None}")
|
350
393
|
|
351
394
|
# If there are no more tool calls, we're done
|
352
395
|
if not message.tool_calls:
|
@@ -1,18 +1,18 @@
|
|
1
|
-
mbxai/__init__.py,sha256=
|
1
|
+
mbxai/__init__.py,sha256=dffyFNDcTA_0NDroPZIJc7T7242wliyUpX1i47_bBmQ,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=hEAVWIrIq758C1zm9aWGf-FiITB3LxtuxZEZ0CcjJ4s,5343
|
5
5
|
mbxai/mcp/example.py,sha256=oaol7AvvZnX86JWNz64KvPjab5gg1VjVN3G8eFSzuaE,2350
|
6
|
-
mbxai/mcp/server.py,sha256=
|
6
|
+
mbxai/mcp/server.py,sha256=1kmpDlay-9PQQ4-AXtBpCdyVJKWooYhTBJI-QGbWUXc,3463
|
7
7
|
mbxai/openrouter/__init__.py,sha256=Ito9Qp_B6q-RLGAQcYyTJVWwR2YAZvNqE-HIYXxhtD8,298
|
8
8
|
mbxai/openrouter/client.py,sha256=XLRMRNRJH96Jl6_af0KkzRDdLJnixh8I3RvEEcFuXyg,10840
|
9
9
|
mbxai/openrouter/config.py,sha256=MTX_YHsFrM7JYqovJSkEF6JzVyIdajeI5Dja2CALH58,2874
|
10
10
|
mbxai/openrouter/models.py,sha256=b3IjjtZAjeGOf2rLsdnCD1HacjTnS8jmv_ZXorc-KJQ,2604
|
11
11
|
mbxai/tools/__init__.py,sha256=QUFaXhDm-UKcuAtT1rbKzhBkvyRBVokcQIOf9cxIuwc,160
|
12
|
-
mbxai/tools/client.py,sha256=
|
12
|
+
mbxai/tools/client.py,sha256=ag6gTDPfY_M1Mrf1iUaBX4HLCcsFKYHDiGaukj940Jk,15613
|
13
13
|
mbxai/tools/example.py,sha256=1HgKK39zzUuwFbnp3f0ThyWVfA_8P28PZcTwaUw5K78,2232
|
14
14
|
mbxai/tools/types.py,sha256=fo5t9UbsHGynhA88vD_ecgDqL8iLvt2E1h1ym43Rrgk,745
|
15
|
-
mbxai-0.5.
|
16
|
-
mbxai-0.5.
|
17
|
-
mbxai-0.5.
|
18
|
-
mbxai-0.5.
|
15
|
+
mbxai-0.5.14.dist-info/METADATA,sha256=VkZHG5kjh8_dy_P2s1JmZnuwzlQgZCoMVVcx5y40zXM,4108
|
16
|
+
mbxai-0.5.14.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
17
|
+
mbxai-0.5.14.dist-info/licenses/LICENSE,sha256=hEyhc4FxwYo3NQ40yNgZ7STqwVk-1_XcTXOnAPbGJAw,1069
|
18
|
+
mbxai-0.5.14.dist-info/RECORD,,
|
File without changes
|
File without changes
|