mbxai 0.5.24__py3-none-any.whl → 0.6.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 CHANGED
@@ -2,4 +2,4 @@
2
2
  MBX AI package.
3
3
  """
4
4
 
5
- __version__ = "0.5.24"
5
+ __version__ = "0.6.0"
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.24",
34
+ version="0.6.0",
35
35
  )
36
36
 
37
37
  # Initialize MCP server
@@ -9,6 +9,7 @@ from .models import OpenRouterModel, OpenRouterModelRegistry
9
9
  from .config import OpenRouterConfig
10
10
  import logging
11
11
  import time
12
+ import asyncio
12
13
  from functools import wraps
13
14
 
14
15
  logger = logging.getLogger(__name__)
mbxai/tools/client.py CHANGED
@@ -16,40 +16,90 @@ logger = logging.getLogger(__name__)
16
16
  T = TypeVar("T", bound=BaseModel)
17
17
 
18
18
  class ToolClient:
19
- """Client for handling tool calls with OpenRouter."""
19
+ """Base class for tool clients."""
20
20
 
21
- def __init__(self, openrouter_client: OpenRouterClient) -> None:
22
- """Initialize the ToolClient.
23
-
24
- Args:
25
- openrouter_client: The OpenRouter client to use
26
- """
27
- self._client = openrouter_client
21
+ def __init__(self, openrouter_client: OpenRouterClient):
22
+ """Initialize the tool client."""
23
+ self._openrouter_client = openrouter_client
28
24
  self._tools: dict[str, Tool] = {}
29
25
 
30
- def register_tool(
31
- self,
32
- name: str,
33
- description: str,
34
- function: Callable[..., Any],
35
- schema: dict[str, Any],
36
- ) -> None:
37
- """Register a new tool.
26
+ def register_tool(self, tool: Tool) -> None:
27
+ """Register a tool."""
28
+ self._tools[tool.name] = tool
38
29
 
39
- Args:
40
- name: The name of the tool
41
- description: A description of what the tool does
42
- function: The function to call when the tool is used
43
- schema: The JSON schema for the tool's parameters
44
- """
45
- tool = Tool(
46
- name=name,
47
- description=description,
48
- function=function,
49
- schema=schema,
30
+ async def invoke_tool(self, tool_name: str, **kwargs: Any) -> Any:
31
+ """Invoke a tool by name."""
32
+ tool = self._tools.get(tool_name)
33
+ if not tool:
34
+ raise ValueError(f"Tool {tool_name} not found")
35
+
36
+ if not tool.function:
37
+ raise ValueError(f"Tool {tool_name} has no function implementation")
38
+
39
+ return await tool.function(**kwargs)
40
+
41
+ async def chat(self, messages: list[dict[str, str]], model: str) -> Any:
42
+ """Process a chat request with tools."""
43
+ # Convert tools to OpenAI function format
44
+ functions = [tool.to_openai_function() for tool in self._tools.values()]
45
+
46
+ # Make the chat request
47
+ response = await self._openrouter_client.chat(
48
+ messages=messages,
49
+ model=model,
50
+ functions=functions,
50
51
  )
51
- self._tools[name] = tool
52
- logger.info(f"Registered tool: {name}")
52
+
53
+ # Validate response
54
+ if not response:
55
+ raise ValueError("No response received from OpenRouter")
56
+
57
+ if not response.choices:
58
+ raise ValueError("Response missing choices")
59
+
60
+ choice = response.choices[0]
61
+ if not choice:
62
+ raise ValueError("Empty choice in response")
63
+
64
+ message = choice.message
65
+ if not message:
66
+ raise ValueError("Choice missing message")
67
+
68
+ # If message has function call, execute it
69
+ if message.function_call:
70
+ tool_name = message.function_call.name
71
+ tool_args = json.loads(message.function_call.arguments)
72
+
73
+ # Invoke the tool
74
+ tool_response = await self.invoke_tool(tool_name, **tool_args)
75
+
76
+ # Add tool response to messages
77
+ messages.append({
78
+ "role": "assistant",
79
+ "content": None,
80
+ "function_call": {
81
+ "name": tool_name,
82
+ "arguments": message.function_call.arguments,
83
+ },
84
+ })
85
+ messages.append({
86
+ "role": "function",
87
+ "name": tool_name,
88
+ "content": json.dumps(tool_response),
89
+ })
90
+
91
+ # Get final response
92
+ final_response = await self._openrouter_client.chat(
93
+ messages=messages,
94
+ model=model,
95
+ )
96
+
97
+ if not final_response or not final_response.choices:
98
+ raise ValueError("No response received after tool execution")
99
+
100
+ return final_response
101
+
102
+ return response
53
103
 
54
104
  def _truncate_content(self, content: str | None, max_length: int = 100) -> str:
55
105
  """Truncate content for logging."""
@@ -196,116 +246,6 @@ class ToolClient:
196
246
  # Log the messages we're about to send
197
247
  self._log_messages(messages, validate_responses=False)
198
248
 
199
- async def chat(
200
- self,
201
- messages: list[dict[str, Any]],
202
- *,
203
- model: str | None = None,
204
- stream: bool = False,
205
- **kwargs: Any,
206
- ) -> Any:
207
- """Chat with the model, handling tool calls."""
208
- tools = [tool.to_openai_function() for tool in self._tools.values()]
209
-
210
- if tools:
211
- logger.info(f"Available tools: {[tool['function']['name'] for tool in tools]}")
212
- kwargs["tools"] = tools
213
- kwargs["tool_choice"] = "auto"
214
-
215
- while True:
216
- # Get the model's response
217
- response = self._client.chat_completion(
218
- messages=messages,
219
- model=model,
220
- stream=stream,
221
- **kwargs,
222
- )
223
-
224
- if stream:
225
- return response
226
-
227
- message = response.choices[0].message
228
- # Add the assistant's message with tool calls
229
- assistant_message = {
230
- "role": "assistant",
231
- "content": message.content or None, # Ensure content is None if empty
232
- }
233
- if message.tool_calls:
234
- assistant_message["tool_calls"] = [
235
- {
236
- "id": tool_call.id,
237
- "type": "function",
238
- "function": {
239
- "name": tool_call.function.name,
240
- "arguments": tool_call.function.arguments,
241
- },
242
- }
243
- for tool_call in message.tool_calls
244
- ]
245
- messages.append(assistant_message)
246
- 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}")
247
-
248
- # If there are no tool calls, we're done
249
- if not message.tool_calls:
250
- return response
251
-
252
- # Process all tool calls
253
- tool_responses = []
254
- for tool_call in message.tool_calls:
255
- tool = self._tools.get(tool_call.function.name)
256
- if not tool:
257
- raise ValueError(f"Unknown tool: {tool_call.function.name}")
258
-
259
- # Parse arguments if they're a string
260
- arguments = tool_call.function.arguments
261
- if isinstance(arguments, str):
262
- try:
263
- arguments = json.loads(arguments)
264
- except json.JSONDecodeError as e:
265
- logger.error(f"Failed to parse tool arguments: {e}")
266
- raise ValueError(f"Invalid tool arguments format: {arguments}")
267
-
268
- # Call the tool
269
- logger.info(f"Calling tool: {tool.name} with args: {self._truncate_dict(arguments)}")
270
- try:
271
- if inspect.iscoroutinefunction(tool.function):
272
- result = await asyncio.wait_for(tool.function(**arguments), timeout=300.0) # 5 minutes timeout
273
- else:
274
- result = tool.function(**arguments)
275
- logger.info(f"Tool {tool.name} completed successfully")
276
- except asyncio.TimeoutError:
277
- logger.error(f"Tool {tool.name} timed out after 5 minutes")
278
- result = {"error": "Tool execution timed out after 5 minutes"}
279
- except Exception as e:
280
- logger.error(f"Error calling tool {tool.name}: {str(e)}")
281
- result = {"error": f"Tool execution failed: {str(e)}"}
282
-
283
- # Convert result to JSON string if it's not already
284
- if not isinstance(result, str):
285
- result = json.dumps(result)
286
-
287
- # Create the tool response
288
- tool_response = {
289
- "role": "tool",
290
- "tool_call_id": tool_call.id,
291
- "content": result,
292
- }
293
- tool_responses.append(tool_response)
294
- logger.info(f"Created tool response for call ID {tool_call.id}")
295
-
296
- # Add all tool responses to the messages
297
- messages.extend(tool_responses)
298
- logger.info(f"Message count: {len(messages)}, Added {len(tool_responses)} tool responses to messages")
299
-
300
- # Validate the message sequence
301
- self._validate_message_sequence(messages, validate_responses=True)
302
-
303
- # Log the messages we're about to send
304
- self._log_messages(messages, validate_responses=False)
305
-
306
- # Continue the loop to get the next response
307
- continue
308
-
309
249
  async def parse(
310
250
  self,
311
251
  messages: list[dict[str, Any]],
@@ -339,7 +279,7 @@ class ToolClient:
339
279
  self._log_messages(messages)
340
280
 
341
281
  # Get the model's response
342
- response = self._client.chat_completion_parse(
282
+ response = self._openrouter_client.chat_completion_parse(
343
283
  messages=messages,
344
284
  response_format=response_format,
345
285
  model=model,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mbxai
3
- Version: 0.5.24
3
+ Version: 0.6.0
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=uOU108fCrriuZbpIuLG4acnQ0ZDvDwD6AnuvvE9mA4A,48
1
+ mbxai/__init__.py,sha256=Jcym8UkXy8bN6bXrlc6D6eL9sjPD8EC0xMMW7BJ3Zt0,47
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=B8ZpH-uecmTCgoDw65LwwVxsFWVoX-08t5ff0hOEPXk,6011
5
5
  mbxai/mcp/example.py,sha256=oaol7AvvZnX86JWNz64KvPjab5gg1VjVN3G8eFSzuaE,2350
6
- mbxai/mcp/server.py,sha256=-oTWHpKJBF2p6Y9s8FhSFY4mytwNzCCx4S2lwR5Xr_E,3463
6
+ mbxai/mcp/server.py,sha256=WUBsnIuSc9kKpZRm_JVnML0PYQOjRqon3RIv1qlJuHw,3462
7
7
  mbxai/openrouter/__init__.py,sha256=Ito9Qp_B6q-RLGAQcYyTJVWwR2YAZvNqE-HIYXxhtD8,298
8
- mbxai/openrouter/client.py,sha256=YOjcYkD8VLMVzBd6z_rB7kLGEzNwIVSUEOyMZlcIhkw,13647
8
+ mbxai/openrouter/client.py,sha256=RO5tbF42vkcjxjvC-QFB8DGA0gQLljH3KPBn3HgZV8I,13662
9
9
  mbxai/openrouter/config.py,sha256=Ia93s-auim9Sq71eunVDbn9ET5xX2zusXpV4JBdHAzs,3251
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=t7rdITqgCbDXQPFOZhGj6VDDPAwqdilJMKPfCOcJaFo,17279
12
+ mbxai/tools/client.py,sha256=ozIuWTQV4jePnKJrBHc8ZxMPUWfTbCqc1pOoImWoHrk,14577
13
13
  mbxai/tools/example.py,sha256=1HgKK39zzUuwFbnp3f0ThyWVfA_8P28PZcTwaUw5K78,2232
14
14
  mbxai/tools/types.py,sha256=fo5t9UbsHGynhA88vD_ecgDqL8iLvt2E1h1ym43Rrgk,745
15
- mbxai-0.5.24.dist-info/METADATA,sha256=yA2MFVTn6yxrLmoC_XgS8frW9MalTT2XEv2Re5S7Org,4108
16
- mbxai-0.5.24.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
17
- mbxai-0.5.24.dist-info/licenses/LICENSE,sha256=hEyhc4FxwYo3NQ40yNgZ7STqwVk-1_XcTXOnAPbGJAw,1069
18
- mbxai-0.5.24.dist-info/RECORD,,
15
+ mbxai-0.6.0.dist-info/METADATA,sha256=maISXH6b_8zmxBmzj-olxxDaM7vc9TbRmVlXdqVhbys,4107
16
+ mbxai-0.6.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
17
+ mbxai-0.6.0.dist-info/licenses/LICENSE,sha256=hEyhc4FxwYo3NQ40yNgZ7STqwVk-1_XcTXOnAPbGJAw,1069
18
+ mbxai-0.6.0.dist-info/RECORD,,
File without changes