mbxai 0.5.18__tar.gz → 0.5.20__tar.gz

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.
Files changed (25) hide show
  1. {mbxai-0.5.18 → mbxai-0.5.20}/PKG-INFO +1 -1
  2. {mbxai-0.5.18 → mbxai-0.5.20}/pyproject.toml +1 -1
  3. {mbxai-0.5.18 → mbxai-0.5.20}/setup.py +1 -1
  4. {mbxai-0.5.18 → mbxai-0.5.20}/src/mbxai/__init__.py +1 -1
  5. {mbxai-0.5.18 → mbxai-0.5.20}/src/mbxai/mcp/server.py +1 -1
  6. {mbxai-0.5.18 → mbxai-0.5.20}/src/mbxai/tools/client.py +72 -54
  7. {mbxai-0.5.18 → mbxai-0.5.20}/uv.lock +7 -7
  8. {mbxai-0.5.18 → mbxai-0.5.20}/.vscode/PythonImportHelper-v2-Completion.json +0 -0
  9. {mbxai-0.5.18 → mbxai-0.5.20}/LICENSE +0 -0
  10. {mbxai-0.5.18 → mbxai-0.5.20}/README.md +0 -0
  11. {mbxai-0.5.18 → mbxai-0.5.20}/src/mbxai/core.py +0 -0
  12. {mbxai-0.5.18 → mbxai-0.5.20}/src/mbxai/mcp/__init__.py +0 -0
  13. {mbxai-0.5.18 → mbxai-0.5.20}/src/mbxai/mcp/client.py +0 -0
  14. {mbxai-0.5.18 → mbxai-0.5.20}/src/mbxai/mcp/example.py +0 -0
  15. {mbxai-0.5.18 → mbxai-0.5.20}/src/mbxai/openrouter/__init__.py +0 -0
  16. {mbxai-0.5.18 → mbxai-0.5.20}/src/mbxai/openrouter/client.py +0 -0
  17. {mbxai-0.5.18 → mbxai-0.5.20}/src/mbxai/openrouter/config.py +0 -0
  18. {mbxai-0.5.18 → mbxai-0.5.20}/src/mbxai/openrouter/models.py +0 -0
  19. {mbxai-0.5.18 → mbxai-0.5.20}/src/mbxai/tools/__init__.py +0 -0
  20. {mbxai-0.5.18 → mbxai-0.5.20}/src/mbxai/tools/example.py +0 -0
  21. {mbxai-0.5.18 → mbxai-0.5.20}/src/mbxai/tools/types.py +0 -0
  22. {mbxai-0.5.18 → mbxai-0.5.20}/tests/test_core.py +0 -0
  23. {mbxai-0.5.18 → mbxai-0.5.20}/tests/test_mcp.py +0 -0
  24. {mbxai-0.5.18 → mbxai-0.5.20}/tests/test_openrouter.py +0 -0
  25. {mbxai-0.5.18 → mbxai-0.5.20}/tests/test_tools.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mbxai
3
- Version: 0.5.18
3
+ Version: 0.5.20
4
4
  Summary: MBX AI SDK
5
5
  Project-URL: Homepage, https://www.mibexx.de
6
6
  Project-URL: Documentation, https://www.mibexx.de
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "mbxai"
7
- version = "0.5.18"
7
+ version = "0.5.20"
8
8
  authors = [
9
9
  { name = "MBX AI" }
10
10
  ]
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name="mbxai",
5
- version="0.5.18",
5
+ version="0.5.20",
6
6
  author="MBX AI",
7
7
  description="MBX AI SDK",
8
8
  long_description=open("README.md").read(),
@@ -2,4 +2,4 @@
2
2
  MBX AI package.
3
3
  """
4
4
 
5
- __version__ = "0.5.18"
5
+ __version__ = "0.5.20"
@@ -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.18",
34
+ version="0.5.20",
35
35
  )
36
36
 
37
37
  # Initialize MCP server
@@ -139,6 +139,62 @@ class ToolClient:
139
139
  # Validate message sequence
140
140
  self._validate_message_sequence(messages, validate_responses)
141
141
 
142
+ async def _process_tool_calls(self, message: Any, messages: list[dict[str, Any]]) -> None:
143
+ """Process all tool calls in a message.
144
+
145
+ Args:
146
+ message: The message containing tool calls
147
+ messages: The list of messages to add responses to
148
+ """
149
+ if not message.tool_calls:
150
+ return
151
+
152
+ # Process all tool calls first
153
+ tool_responses = []
154
+ for tool_call in message.tool_calls:
155
+ tool = self._tools.get(tool_call.function.name)
156
+ if not tool:
157
+ raise ValueError(f"Unknown tool: {tool_call.function.name}")
158
+
159
+ # Parse arguments if they're a string
160
+ arguments = tool_call.function.arguments
161
+ if isinstance(arguments, str):
162
+ try:
163
+ arguments = json.loads(arguments)
164
+ except json.JSONDecodeError as e:
165
+ logger.error(f"Failed to parse tool arguments: {e}")
166
+ raise ValueError(f"Invalid tool arguments format: {arguments}")
167
+
168
+ # Call the tool
169
+ logger.info(f"Calling tool: {tool.name} with args: {self._truncate_dict(arguments)}")
170
+ if inspect.iscoroutinefunction(tool.function):
171
+ result = await tool.function(**arguments)
172
+ else:
173
+ result = tool.function(**arguments)
174
+
175
+ # Convert result to JSON string if it's not already
176
+ if not isinstance(result, str):
177
+ result = json.dumps(result)
178
+
179
+ # Create the tool response
180
+ tool_response = {
181
+ "role": "tool",
182
+ "tool_call_id": tool_call.id,
183
+ "content": result,
184
+ }
185
+ tool_responses.append(tool_response)
186
+ logger.info(f"Created tool response for call ID {tool_call.id}")
187
+
188
+ # Add all tool responses to the messages
189
+ messages.extend(tool_responses)
190
+ logger.info(f"Message count: {len(messages)}, Added {len(tool_responses)} tool responses to messages")
191
+
192
+ # Validate the message sequence
193
+ self._validate_message_sequence(messages, validate_responses=True)
194
+
195
+ # Log the messages we're about to send
196
+ self._log_messages(messages, validate_responses=False)
197
+
142
198
  async def chat(
143
199
  self,
144
200
  messages: list[dict[str, Any]],
@@ -147,17 +203,7 @@ class ToolClient:
147
203
  stream: bool = False,
148
204
  **kwargs: Any,
149
205
  ) -> Any:
150
- """Chat with the model, handling tool calls.
151
-
152
- Args:
153
- messages: The conversation messages
154
- model: Optional model override
155
- stream: Whether to stream the response
156
- **kwargs: Additional parameters for the chat completion
157
-
158
- Returns:
159
- The final response from the model
160
- """
206
+ """Chat with the model, handling tool calls."""
161
207
  tools = [tool.to_openai_function() for tool in self._tools.values()]
162
208
 
163
209
  if tools:
@@ -166,9 +212,6 @@ class ToolClient:
166
212
  kwargs["tool_choice"] = "auto"
167
213
 
168
214
  while True:
169
- # Log messages before sending to OpenRouter, but don't validate responses yet
170
- self._log_messages(messages, validate_responses=False)
171
-
172
215
  # Get the model's response
173
216
  response = self._client.chat_completion(
174
217
  messages=messages,
@@ -199,13 +242,14 @@ class ToolClient:
199
242
  for tool_call in message.tool_calls
200
243
  ]
201
244
  messages.append(assistant_message)
202
- logger.info(f"Added assistant message with tool calls: {[tc.function.name for tc in message.tool_calls] if message.tool_calls else None}")
245
+ 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}")
203
246
 
204
247
  # If there are no tool calls, we're done
205
248
  if not message.tool_calls:
206
249
  return response
207
250
 
208
- # Handle all tool calls before getting the next model response
251
+ # Process all tool calls
252
+ tool_responses = []
209
253
  for tool_call in message.tool_calls:
210
254
  tool = self._tools.get(tool_call.function.name)
211
255
  if not tool:
@@ -231,53 +275,27 @@ class ToolClient:
231
275
  if not isinstance(result, str):
232
276
  result = json.dumps(result)
233
277
 
234
- # Create and add the tool response immediately
278
+ # Create the tool response
235
279
  tool_response = {
236
280
  "role": "tool",
237
281
  "tool_call_id": tool_call.id,
238
282
  "content": result,
239
283
  }
240
- messages.append(tool_response)
241
- logger.info(f"Added tool response for call ID {tool_call.id}")
242
-
243
- # Now validate the message sequence after all tool calls have been processed
244
- self._validate_message_sequence(messages, validate_responses=True)
284
+ tool_responses.append(tool_response)
285
+ logger.info(f"Created tool response for call ID {tool_call.id}")
245
286
 
246
- # Get a new response from the model with all tool results
247
- response = self._client.chat_completion(
248
- messages=messages,
249
- model=model,
250
- stream=stream,
251
- **kwargs,
252
- )
287
+ # Add all tool responses to the messages
288
+ messages.extend(tool_responses)
289
+ logger.info(f"Message count: {len(messages)}, Added {len(tool_responses)} tool responses to messages")
253
290
 
254
- if stream:
255
- return response
291
+ # Validate the message sequence
292
+ self._validate_message_sequence(messages, validate_responses=True)
256
293
 
257
- message = response.choices[0].message
258
- # Add the assistant's message with tool calls
259
- assistant_message = {
260
- "role": "assistant",
261
- "content": message.content or None, # Ensure content is None if empty
262
- }
263
- if message.tool_calls:
264
- assistant_message["tool_calls"] = [
265
- {
266
- "id": tool_call.id,
267
- "type": "function",
268
- "function": {
269
- "name": tool_call.function.name,
270
- "arguments": tool_call.function.arguments,
271
- },
272
- }
273
- for tool_call in message.tool_calls
274
- ]
275
- messages.append(assistant_message)
276
- logger.info(f"Added assistant message with tool calls: {[tc.function.name for tc in message.tool_calls] if message.tool_calls else None}")
294
+ # Log the messages we're about to send
295
+ self._log_messages(messages, validate_responses=False)
277
296
 
278
- # If there are no more tool calls, we're done
279
- if not message.tool_calls:
280
- return response
297
+ # Continue the loop to get the next response
298
+ continue
281
299
 
282
300
  async def parse(
283
301
  self,
@@ -292,11 +292,11 @@ wheels = [
292
292
 
293
293
  [[package]]
294
294
  name = "httpx-sse"
295
- version = "0.5.18"
295
+ version = "0.5.20"
296
296
  source = { registry = "https://pypi.org/simple" }
297
- sdist = { url = "https://files.pythonhosted.org/packages/4c/60/8f4281fa9bbf3c8034fd54c0e7412e66edbab6bc74c4996bd616f8d0406e/httpx-sse-0.5.18.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721", size = 12624 }
297
+ sdist = { url = "https://files.pythonhosted.org/packages/4c/60/8f4281fa9bbf3c8034fd54c0e7412e66edbab6bc74c4996bd616f8d0406e/httpx-sse-0.5.20.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721", size = 12624 }
298
298
  wheels = [
299
- { url = "https://files.pythonhosted.org/packages/e1/9b/a181f281f65d776426002f330c31849b86b31fc9d848db62e16f03ff739f/httpx_sse-0.5.18-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f", size = 7819 },
299
+ { url = "https://files.pythonhosted.org/packages/e1/9b/a181f281f65d776426002f330c31849b86b31fc9d848db62e16f03ff739f/httpx_sse-0.5.20-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f", size = 7819 },
300
300
  ]
301
301
 
302
302
  [[package]]
@@ -446,7 +446,7 @@ wheels = [
446
446
 
447
447
  [[package]]
448
448
  name = "mbxai"
449
- version = "0.5.18"
449
+ version = "0.5.20"
450
450
  source = { editable = "." }
451
451
  dependencies = [
452
452
  { name = "fastapi" },
@@ -980,14 +980,14 @@ wheels = [
980
980
 
981
981
  [[package]]
982
982
  name = "typing-inspection"
983
- version = "0.5.18"
983
+ version = "0.5.20"
984
984
  source = { registry = "https://pypi.org/simple" }
985
985
  dependencies = [
986
986
  { name = "typing-extensions" },
987
987
  ]
988
- sdist = { url = "https://files.pythonhosted.org/packages/82/5c/e6082df02e215b846b4b8c0b887a64d7d08ffaba30605502639d44c06b82/typing_inspection-0.5.18.tar.gz", hash = "sha256:9765c87de36671694a67904bf2c96e395be9c6439bb6c87b5142569dcdd65122", size = 76222 }
988
+ sdist = { url = "https://files.pythonhosted.org/packages/82/5c/e6082df02e215b846b4b8c0b887a64d7d08ffaba30605502639d44c06b82/typing_inspection-0.5.20.tar.gz", hash = "sha256:9765c87de36671694a67904bf2c96e395be9c6439bb6c87b5142569dcdd65122", size = 76222 }
989
989
  wheels = [
990
- { url = "https://files.pythonhosted.org/packages/31/08/aa4fdfb71f7de5176385bd9e90852eaf6b5d622735020ad600f2bab54385/typing_inspection-0.5.18-py3-none-any.whl", hash = "sha256:50e72559fcd2a6367a19f7a7e610e6afcb9fac940c650290eed893d61386832f", size = 14125 },
990
+ { url = "https://files.pythonhosted.org/packages/31/08/aa4fdfb71f7de5176385bd9e90852eaf6b5d622735020ad600f2bab54385/typing_inspection-0.5.20-py3-none-any.whl", hash = "sha256:50e72559fcd2a6367a19f7a7e610e6afcb9fac940c650290eed893d61386832f", size = 14125 },
991
991
  ]
992
992
 
993
993
  [[package]]
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes