mbxai 0.5.4__tar.gz → 0.5.6__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.
@@ -187,22 +187,6 @@
187
187
  "detail": "typing",
188
188
  "documentation": {}
189
189
  },
190
- {
191
- "label": "TypedDict",
192
- "importPath": "typing",
193
- "description": "typing",
194
- "isExtraImport": true,
195
- "detail": "typing",
196
- "documentation": {}
197
- },
198
- {
199
- "label": "NotRequired",
200
- "importPath": "typing",
201
- "description": "typing",
202
- "isExtraImport": true,
203
- "detail": "typing",
204
- "documentation": {}
205
- },
206
190
  {
207
191
  "label": "Any",
208
192
  "importPath": "typing",
@@ -592,14 +576,6 @@
592
576
  "detail": "mbxai.openrouter",
593
577
  "documentation": {}
594
578
  },
595
- {
596
- "label": "OpenRouterModel",
597
- "importPath": "mbxai.openrouter",
598
- "description": "mbxai.openrouter",
599
- "isExtraImport": true,
600
- "detail": "mbxai.openrouter",
601
- "documentation": {}
602
- },
603
579
  {
604
580
  "label": "load_dotenv",
605
581
  "importPath": "dotenv",
@@ -632,6 +608,14 @@
632
608
  "detail": "mbxai.tools",
633
609
  "documentation": {}
634
610
  },
611
+ {
612
+ "label": "ToolCall",
613
+ "importPath": "mbxai.tools",
614
+ "description": "mbxai.tools",
615
+ "isExtraImport": true,
616
+ "detail": "mbxai.tools",
617
+ "documentation": {}
618
+ },
635
619
  {
636
620
  "label": "setup",
637
621
  "importPath": "setuptools",
@@ -900,6 +884,15 @@
900
884
  "detail": "src.mbxai.tools.client",
901
885
  "documentation": {}
902
886
  },
887
+ {
888
+ "label": "logger",
889
+ "kind": 5,
890
+ "importPath": "src.mbxai.tools.client",
891
+ "description": "src.mbxai.tools.client",
892
+ "peekOfCode": "logger = logging.getLogger(__name__)\nT = TypeVar(\"T\", bound=BaseModel)\nclass ToolClient:\n \"\"\"Client for handling tool calls with OpenRouter.\"\"\"\n def __init__(self, openrouter_client: OpenRouterClient) -> None:\n \"\"\"Initialize the ToolClient.\n Args:\n openrouter_client: The OpenRouter client to use\n \"\"\"\n self._client = openrouter_client",
893
+ "detail": "src.mbxai.tools.client",
894
+ "documentation": {}
895
+ },
903
896
  {
904
897
  "label": "T",
905
898
  "kind": 5,
@@ -936,15 +929,6 @@
936
929
  "detail": "src.mbxai.tools.example",
937
930
  "documentation": {}
938
931
  },
939
- {
940
- "label": "ToolFunction",
941
- "kind": 6,
942
- "importPath": "src.mbxai.tools.types",
943
- "description": "src.mbxai.tools.types",
944
- "peekOfCode": "class ToolFunction(TypedDict):\n \"\"\"OpenAI function definition for a tool.\"\"\"\n name: str\n description: str\n parameters: dict[str, Any]\nclass ToolCall(BaseModel):\n \"\"\"A tool call from the model.\"\"\"\n id: str\n name: str\n arguments: dict[str, Any]",
945
- "detail": "src.mbxai.tools.types",
946
- "documentation": {}
947
- },
948
932
  {
949
933
  "label": "ToolCall",
950
934
  "kind": 6,
@@ -959,7 +943,7 @@
959
943
  "kind": 6,
960
944
  "importPath": "src.mbxai.tools.types",
961
945
  "description": "src.mbxai.tools.types",
962
- "peekOfCode": "class Tool(BaseModel):\n \"\"\"A tool that can be used by the model.\"\"\"\n name: str\n description: str\n function: Callable[..., Any]\n schema: dict[str, Any]\n def to_openai_function(self) -> ToolFunction:\n \"\"\"Convert the tool to an OpenAI function definition.\"\"\"\n return {\n \"name\": self.name,",
946
+ "peekOfCode": "class Tool(BaseModel):\n \"\"\"A tool that can be used by the model.\"\"\"\n name: str\n description: str\n function: Callable[..., Any]\n schema: dict[str, Any]\n def to_openai_function(self) -> dict[str, Any]:\n \"\"\"Convert the tool to an OpenAI function definition.\"\"\"\n return {\n \"type\": \"function\",",
963
947
  "detail": "src.mbxai.tools.types",
964
948
  "documentation": {}
965
949
  },
@@ -1332,6 +1316,15 @@
1332
1316
  "detail": "tests.test_tools",
1333
1317
  "documentation": {}
1334
1318
  },
1319
+ {
1320
+ "label": "test_tool_to_openai_function",
1321
+ "kind": 2,
1322
+ "importPath": "tests.test_tools",
1323
+ "description": "tests.test_tools",
1324
+ "peekOfCode": "def test_tool_to_openai_function():\n \"\"\"Test converting a tool to OpenAI function format.\"\"\"\n # Create a test tool\n def test_function(arg1: str) -> str:\n return f\"Test: {arg1}\"\n tool = Tool(\n name=\"test_tool\",\n description=\"A test tool\",\n function=test_function,\n schema={",
1325
+ "detail": "tests.test_tools",
1326
+ "documentation": {}
1327
+ },
1335
1328
  {
1336
1329
  "label": "test_chat_without_tools",
1337
1330
  "kind": 2,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mbxai
3
- Version: 0.5.4
3
+ Version: 0.5.6
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.4"
7
+ version = "0.5.6"
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.4",
5
+ version="0.5.6",
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.4"
5
+ __version__ = "0.5.6"
@@ -24,10 +24,12 @@ class MCPTool(Tool):
24
24
  def to_openai_function(self) -> dict[str, Any]:
25
25
  """Convert the tool to an OpenAI function definition."""
26
26
  return {
27
- "name": self.name,
28
- "description": self.description,
29
- "parameters": self._convert_to_openai_schema(self.input_schema),
30
- "type": "function"
27
+ "type": "function",
28
+ "function": {
29
+ "name": self.name,
30
+ "description": self.description,
31
+ "parameters": self._convert_to_openai_schema(self.input_schema)
32
+ }
31
33
  }
32
34
 
33
35
  def _convert_to_openai_schema(self, mcp_schema: dict[str, Any]) -> dict[str, Any]:
@@ -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.4",
34
+ version="0.5.6",
35
35
  )
36
36
 
37
37
  # Initialize MCP server
@@ -3,10 +3,13 @@ ToolClient implementation for MBX AI.
3
3
  """
4
4
 
5
5
  from typing import Any, Callable, TypeVar, cast
6
+ import logging
6
7
  from pydantic import BaseModel
7
8
  from ..openrouter import OpenRouterClient
8
9
  from .types import Tool, ToolCall
9
10
 
11
+ logger = logging.getLogger(__name__)
12
+
10
13
  T = TypeVar("T", bound=BaseModel)
11
14
 
12
15
  class ToolClient:
@@ -43,6 +46,7 @@ class ToolClient:
43
46
  schema=schema,
44
47
  )
45
48
  self._tools[name] = tool
49
+ logger.debug(f"Registered tool: {name}")
46
50
 
47
51
  def chat(
48
52
  self,
@@ -65,12 +69,12 @@ class ToolClient:
65
69
  """
66
70
  tools = [tool.to_openai_function() for tool in self._tools.values()]
67
71
 
72
+ if tools:
73
+ logger.debug(f"Using tools: {tools}")
74
+ kwargs["tools"] = tools
75
+ kwargs["tool_choice"] = "auto"
76
+
68
77
  while True:
69
- # Add tools to the request if we have any
70
- if tools:
71
- kwargs["tools"] = tools
72
- kwargs["tool_choice"] = "auto"
73
-
74
78
  # Get the model's response
75
79
  response = self._client.chat_completion(
76
80
  messages=messages,
@@ -91,12 +95,33 @@ class ToolClient:
91
95
 
92
96
  # Handle each tool call
93
97
  for tool_call in message.tool_calls:
98
+ logger.debug(f"Processing tool call: {tool_call}")
99
+ logger.debug(f"Tool call ID: {tool_call.id}")
100
+ logger.debug(f"Tool call function: {tool_call.function}")
101
+ logger.debug(f"Tool call arguments: {tool_call.function.arguments}")
102
+
94
103
  tool = self._tools.get(tool_call.function.name)
95
104
  if not tool:
96
105
  raise ValueError(f"Unknown tool: {tool_call.function.name}")
97
106
 
107
+ # Parse arguments if they're a string
108
+ arguments = tool_call.function.arguments
109
+ logger.debug(f"Raw arguments type: {type(arguments)}")
110
+ logger.debug(f"Raw arguments: {arguments}")
111
+
112
+ if isinstance(arguments, str):
113
+ import json
114
+ try:
115
+ arguments = json.loads(arguments)
116
+ logger.debug(f"Parsed arguments: {arguments}")
117
+ except json.JSONDecodeError as e:
118
+ logger.error(f"Failed to parse tool arguments: {e}")
119
+ raise ValueError(f"Invalid tool arguments format: {arguments}")
120
+
98
121
  # Call the tool
99
- result = tool.function(**tool_call.function.arguments)
122
+ logger.debug(f"Calling tool {tool.name} with arguments: {arguments}")
123
+ result = tool.function(**arguments)
124
+ logger.debug(f"Tool result: {result}")
100
125
 
101
126
  # Add the tool response to the messages
102
127
  messages.append({
@@ -129,12 +154,12 @@ class ToolClient:
129
154
  """
130
155
  tools = [tool.to_openai_function() for tool in self._tools.values()]
131
156
 
157
+ if tools:
158
+ logger.debug(f"Using tools: {tools}")
159
+ kwargs["tools"] = tools
160
+ kwargs["tool_choice"] = "auto"
161
+
132
162
  while True:
133
- # Add tools to the request if we have any
134
- if tools:
135
- kwargs["tools"] = tools
136
- kwargs["tool_choice"] = "auto"
137
-
138
163
  # Get the model's response
139
164
  response = self._client.chat_completion_parse(
140
165
  messages=messages,
@@ -156,12 +181,33 @@ class ToolClient:
156
181
 
157
182
  # Handle each tool call
158
183
  for tool_call in message.tool_calls:
184
+ logger.debug(f"Processing tool call: {tool_call}")
185
+ logger.debug(f"Tool call ID: {tool_call.id}")
186
+ logger.debug(f"Tool call function: {tool_call.function}")
187
+ logger.debug(f"Tool call arguments: {tool_call.function.arguments}")
188
+
159
189
  tool = self._tools.get(tool_call.function.name)
160
190
  if not tool:
161
191
  raise ValueError(f"Unknown tool: {tool_call.function.name}")
162
192
 
193
+ # Parse arguments if they're a string
194
+ arguments = tool_call.function.arguments
195
+ logger.debug(f"Raw arguments type: {type(arguments)}")
196
+ logger.debug(f"Raw arguments: {arguments}")
197
+
198
+ if isinstance(arguments, str):
199
+ import json
200
+ try:
201
+ arguments = json.loads(arguments)
202
+ logger.debug(f"Parsed arguments: {arguments}")
203
+ except json.JSONDecodeError as e:
204
+ logger.error(f"Failed to parse tool arguments: {e}")
205
+ raise ValueError(f"Invalid tool arguments format: {arguments}")
206
+
163
207
  # Call the tool
164
- result = tool.function(**tool_call.function.arguments)
208
+ logger.debug(f"Calling tool {tool.name} with arguments: {arguments}")
209
+ result = tool.function(**arguments)
210
+ logger.debug(f"Tool result: {result}")
165
211
 
166
212
  # Add the tool response to the messages
167
213
  messages.append({
@@ -2,15 +2,9 @@
2
2
  Type definitions for the tools package.
3
3
  """
4
4
 
5
- from typing import Any, Callable, TypedDict, NotRequired
5
+ from typing import Any, Callable
6
6
  from pydantic import BaseModel
7
7
 
8
- class ToolFunction(TypedDict):
9
- """OpenAI function definition for a tool."""
10
- name: str
11
- description: str
12
- parameters: dict[str, Any]
13
-
14
8
  class ToolCall(BaseModel):
15
9
  """A tool call from the model."""
16
10
  id: str
@@ -24,10 +18,13 @@ class Tool(BaseModel):
24
18
  function: Callable[..., Any]
25
19
  schema: dict[str, Any]
26
20
 
27
- def to_openai_function(self) -> ToolFunction:
21
+ def to_openai_function(self) -> dict[str, Any]:
28
22
  """Convert the tool to an OpenAI function definition."""
29
23
  return {
30
- "name": self.name,
31
- "description": self.description,
32
- "parameters": self.schema,
24
+ "type": "function",
25
+ "function": {
26
+ "name": self.name,
27
+ "description": self.description,
28
+ "parameters": self.schema
29
+ }
33
30
  }
@@ -1,12 +1,11 @@
1
- """
2
- Tests for the ToolClient.
3
- """
1
+ """Tests for the tools module."""
4
2
 
5
3
  import pytest
6
4
  from unittest.mock import Mock, patch
7
5
  from pydantic import BaseModel
8
- from mbxai.tools import ToolClient, Tool
9
- from mbxai.openrouter import OpenRouterClient, OpenRouterModel
6
+
7
+ from mbxai.openrouter import OpenRouterClient
8
+ from mbxai.tools import ToolClient, Tool, ToolCall
10
9
 
11
10
  class TestOutput(BaseModel):
12
11
  """Test output model."""
@@ -43,11 +42,45 @@ def test_tool_registration():
43
42
  # Verify tool was registered
44
43
  assert "test_tool" in tool_client._tools
45
44
  tool = tool_client._tools["test_tool"]
45
+ assert isinstance(tool, Tool)
46
46
  assert tool.name == "test_tool"
47
47
  assert tool.description == "A test tool"
48
48
  assert tool.function == test_function
49
49
  assert tool.schema["type"] == "object"
50
50
 
51
+ def test_tool_to_openai_function():
52
+ """Test converting a tool to OpenAI function format."""
53
+ # Create a test tool
54
+ def test_function(arg1: str) -> str:
55
+ return f"Test: {arg1}"
56
+
57
+ tool = Tool(
58
+ name="test_tool",
59
+ description="A test tool",
60
+ function=test_function,
61
+ schema={
62
+ "type": "object",
63
+ "properties": {
64
+ "arg1": {
65
+ "type": "string",
66
+ "description": "Test argument"
67
+ }
68
+ },
69
+ "required": ["arg1"]
70
+ }
71
+ )
72
+
73
+ # Convert to OpenAI function format
74
+ function_def = tool.to_openai_function()
75
+
76
+ # Verify the format
77
+ assert function_def["type"] == "function"
78
+ assert "function" in function_def
79
+ assert function_def["function"]["name"] == "test_tool"
80
+ assert function_def["function"]["description"] == "A test tool"
81
+ assert function_def["function"]["parameters"]["type"] == "object"
82
+ assert "arg1" in function_def["function"]["parameters"]["properties"]
83
+
51
84
  def test_chat_without_tools():
52
85
  """Test chat without any tools."""
53
86
  # Setup
@@ -292,11 +292,11 @@ wheels = [
292
292
 
293
293
  [[package]]
294
294
  name = "httpx-sse"
295
- version = "0.5.4"
295
+ version = "0.5.6"
296
296
  source = { registry = "https://pypi.org/simple" }
297
- sdist = { url = "https://files.pythonhosted.org/packages/4c/60/8f4281fa9bbf3c8034fd54c0e7412e66edbab6bc74c4996bd616f8d0406e/httpx-sse-0.5.4.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721", size = 12624 }
297
+ sdist = { url = "https://files.pythonhosted.org/packages/4c/60/8f4281fa9bbf3c8034fd54c0e7412e66edbab6bc74c4996bd616f8d0406e/httpx-sse-0.5.6.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721", size = 12624 }
298
298
  wheels = [
299
- { url = "https://files.pythonhosted.org/packages/e1/9b/a181f281f65d776426002f330c31849b86b31fc9d848db62e16f03ff739f/httpx_sse-0.5.4-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f", size = 7819 },
299
+ { url = "https://files.pythonhosted.org/packages/e1/9b/a181f281f65d776426002f330c31849b86b31fc9d848db62e16f03ff739f/httpx_sse-0.5.6-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.4"
449
+ version = "0.5.6"
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.4"
983
+ version = "0.5.6"
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.4.tar.gz", hash = "sha256:9765c87de36671694a67904bf2c96e395be9c6439bb6c87b5142569dcdd65122", size = 76222 }
988
+ sdist = { url = "https://files.pythonhosted.org/packages/82/5c/e6082df02e215b846b4b8c0b887a64d7d08ffaba30605502639d44c06b82/typing_inspection-0.5.6.tar.gz", hash = "sha256:9765c87de36671694a67904bf2c96e395be9c6439bb6c87b5142569dcdd65122", size = 76222 }
989
989
  wheels = [
990
- { url = "https://files.pythonhosted.org/packages/31/08/aa4fdfb71f7de5176385bd9e90852eaf6b5d622735020ad600f2bab54385/typing_inspection-0.5.4-py3-none-any.whl", hash = "sha256:50e72559fcd2a6367a19f7a7e610e6afcb9fac940c650290eed893d61386832f", size = 14125 },
990
+ { url = "https://files.pythonhosted.org/packages/31/08/aa4fdfb71f7de5176385bd9e90852eaf6b5d622735020ad600f2bab54385/typing_inspection-0.5.6-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