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 CHANGED
@@ -2,4 +2,4 @@
2
2
  MBX AI package.
3
3
  """
4
4
 
5
- __version__ = "0.5.12"
5
+ __version__ = "0.5.14"
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.5.12",
34
+ version="0.5.14",
35
35
  )
36
36
 
37
37
  # Initialize MCP server
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.debug(f"Registered tool: {name}")
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.debug(f"Using tools: {tools}")
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.debug(f"Added assistant message: {assistant_message}")
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.debug(f"Calling tool {tool.name} with arguments: {arguments}")
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.debug(f"Created tool response: {tool_response}")
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.debug(f"Added all tool responses to messages: {tool_responses}")
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.debug(f"Added assistant message: {assistant_message}")
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.debug(f"Using tools: {tools}")
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.debug(f"Added assistant message: {assistant_message}")
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.debug(f"Calling tool {tool.name} with arguments: {arguments}")
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.debug(f"Created tool response: {tool_response}")
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.debug(f"Added assistant message: {assistant_message}")
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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mbxai
3
- Version: 0.5.12
3
+ Version: 0.5.14
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=IZikFZwxYq2ZzCj7jfjGct1is4gCcMAc590whjAThy4,48
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=MXGh1jn4tKLY49fAs_5rTaVMh1GT-azvwyXGHIDoSRg,3463
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=8aTrRSljnjrKIaBzLspSc8OpqRar6LZn__SVNOBpgHM,13190
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.12.dist-info/METADATA,sha256=mga6ghbaA3DUBmlVAAGAyHiACU--F6dgiR038ausnec,4108
16
- mbxai-0.5.12.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
17
- mbxai-0.5.12.dist-info/licenses/LICENSE,sha256=hEyhc4FxwYo3NQ40yNgZ7STqwVk-1_XcTXOnAPbGJAw,1069
18
- mbxai-0.5.12.dist-info/RECORD,,
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