camel-ai 0.2.75a6__py3-none-any.whl → 0.2.76a0__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.

Potentially problematic release.


This version of camel-ai might be problematic. Click here for more details.

Files changed (38) hide show
  1. camel/__init__.py +1 -1
  2. camel/agents/chat_agent.py +151 -37
  3. camel/configs/__init__.py +3 -0
  4. camel/configs/amd_config.py +70 -0
  5. camel/interpreters/__init__.py +2 -0
  6. camel/interpreters/microsandbox_interpreter.py +395 -0
  7. camel/models/__init__.py +2 -0
  8. camel/models/amd_model.py +101 -0
  9. camel/models/model_factory.py +2 -0
  10. camel/models/openai_model.py +0 -6
  11. camel/runtimes/daytona_runtime.py +11 -12
  12. camel/toolkits/__init__.py +5 -3
  13. camel/toolkits/code_execution.py +28 -1
  14. camel/toolkits/function_tool.py +6 -1
  15. camel/toolkits/hybrid_browser_toolkit/config_loader.py +8 -0
  16. camel/toolkits/hybrid_browser_toolkit/hybrid_browser_toolkit.py +12 -0
  17. camel/toolkits/hybrid_browser_toolkit/hybrid_browser_toolkit_ts.py +33 -14
  18. camel/toolkits/hybrid_browser_toolkit/ts/src/browser-session.ts +135 -40
  19. camel/toolkits/hybrid_browser_toolkit/ts/src/config-loader.ts +2 -0
  20. camel/toolkits/hybrid_browser_toolkit/ts/src/hybrid-browser-toolkit.ts +43 -207
  21. camel/toolkits/hybrid_browser_toolkit/ts/src/parent-child-filter.ts +226 -0
  22. camel/toolkits/hybrid_browser_toolkit/ts/src/snapshot-parser.ts +231 -0
  23. camel/toolkits/hybrid_browser_toolkit/ts/src/som-screenshot-injected.ts +543 -0
  24. camel/toolkits/hybrid_browser_toolkit/ts/websocket-server.js +39 -6
  25. camel/toolkits/hybrid_browser_toolkit/ws_wrapper.py +241 -56
  26. camel/toolkits/hybrid_browser_toolkit_py/hybrid_browser_toolkit.py +5 -1
  27. camel/toolkits/{openai_image_toolkit.py → image_generation_toolkit.py} +98 -31
  28. camel/toolkits/mcp_toolkit.py +39 -14
  29. camel/toolkits/minimax_mcp_toolkit.py +195 -0
  30. camel/toolkits/terminal_toolkit.py +12 -2
  31. camel/toolkits/video_analysis_toolkit.py +16 -10
  32. camel/types/enums.py +11 -0
  33. camel/utils/commons.py +2 -0
  34. camel/utils/mcp.py +136 -2
  35. {camel_ai-0.2.75a6.dist-info → camel_ai-0.2.76a0.dist-info}/METADATA +5 -3
  36. {camel_ai-0.2.75a6.dist-info → camel_ai-0.2.76a0.dist-info}/RECORD +38 -31
  37. {camel_ai-0.2.75a6.dist-info → camel_ai-0.2.76a0.dist-info}/WHEEL +0 -0
  38. {camel_ai-0.2.75a6.dist-info → camel_ai-0.2.76a0.dist-info}/licenses/LICENSE +0 -0
@@ -220,26 +220,34 @@ class MCPToolkit(BaseToolkit):
220
220
  self._exit_stack = AsyncExitStack()
221
221
 
222
222
  try:
223
- # Connect to all clients using AsyncExitStack
224
- for i, client in enumerate(self.clients):
225
- try:
226
- # Use MCPClient directly as async context manager
227
- await self._exit_stack.enter_async_context(client)
228
- msg = f"Connected to client {i+1}/{len(self.clients)}"
229
- logger.debug(msg)
230
- except Exception as e:
231
- logger.error(f"Failed to connect to client {i+1}: {e}")
232
- # AsyncExitStack will handle cleanup of already connected
233
- await self._exit_stack.aclose()
234
- self._exit_stack = None
235
- error_msg = f"Failed to connect to client {i+1}: {e}"
236
- raise MCPConnectionError(error_msg) from e
223
+ # Apply timeout to the entire connection process
224
+ import asyncio
225
+
226
+ timeout_seconds = self.timeout or 30.0
227
+ await asyncio.wait_for(
228
+ self._connect_all_clients(), timeout=timeout_seconds
229
+ )
237
230
 
238
231
  self._is_connected = True
239
232
  msg = f"Successfully connected to {len(self.clients)} MCP servers"
240
233
  logger.info(msg)
241
234
  return self
242
235
 
236
+ except (asyncio.TimeoutError, asyncio.CancelledError):
237
+ self._is_connected = False
238
+ if self._exit_stack:
239
+ await self._exit_stack.aclose()
240
+ self._exit_stack = None
241
+
242
+ timeout_seconds = self.timeout or 30.0
243
+ error_msg = (
244
+ f"Connection timeout after {timeout_seconds}s. "
245
+ f"One or more MCP servers are not responding. "
246
+ f"Please check if the servers are running and accessible."
247
+ )
248
+ logger.error(error_msg)
249
+ raise MCPConnectionError(error_msg)
250
+
243
251
  except Exception:
244
252
  self._is_connected = False
245
253
  if self._exit_stack:
@@ -247,6 +255,23 @@ class MCPToolkit(BaseToolkit):
247
255
  self._exit_stack = None
248
256
  raise
249
257
 
258
+ async def _connect_all_clients(self):
259
+ r"""Connect to all clients sequentially."""
260
+ # Connect to all clients using AsyncExitStack
261
+ for i, client in enumerate(self.clients):
262
+ try:
263
+ # Use MCPClient directly as async context manager
264
+ await self._exit_stack.enter_async_context(client)
265
+ msg = f"Connected to client {i+1}/{len(self.clients)}"
266
+ logger.debug(msg)
267
+ except Exception as e:
268
+ logger.error(f"Failed to connect to client {i+1}: {e}")
269
+ # AsyncExitStack will cleanup already connected clients
270
+ await self._exit_stack.aclose()
271
+ self._exit_stack = None
272
+ error_msg = f"Failed to connect to client {i+1}: {e}"
273
+ raise MCPConnectionError(error_msg) from e
274
+
250
275
  async def disconnect(self):
251
276
  r"""Disconnect from all MCP servers."""
252
277
  if not self._is_connected:
@@ -0,0 +1,195 @@
1
+ # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
2
+ # Licensed under the Apache License, Version 2.0 (the "License");
3
+ # you may not use this file except in compliance with the License.
4
+ # You may obtain a copy of the License at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # Unless required by applicable law or agreed to in writing, software
9
+ # distributed under the License is distributed on an "AS IS" BASIS,
10
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ # See the License for the specific language governing permissions and
12
+ # limitations under the License.
13
+ # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
14
+
15
+ import os
16
+ from typing import Any, Dict, List, Optional
17
+
18
+ from camel.toolkits import BaseToolkit, FunctionTool
19
+
20
+ from .mcp_toolkit import MCPToolkit
21
+
22
+
23
+ class MinimaxMCPToolkit(BaseToolkit):
24
+ r"""MinimaxMCPToolkit provides an interface for interacting with
25
+ MiniMax AI services using the MiniMax MCP server.
26
+
27
+ This toolkit enables access to MiniMax's multimedia generation
28
+ capabilities including text-to-audio, voice cloning, video generation,
29
+ image generation, music generation, and voice design.
30
+
31
+ This toolkit can be used as an async context manager for automatic
32
+ connection management:
33
+
34
+ # Using explicit API key
35
+ async with MinimaxMCPToolkit(api_key="your-key") as toolkit:
36
+ tools = toolkit.get_tools()
37
+ # Toolkit is automatically disconnected when exiting
38
+
39
+ # Using environment variables (recommended for security)
40
+ # Set MINIMAX_API_KEY=your-key in environment
41
+ async with MinimaxMCPToolkit() as toolkit:
42
+ tools = toolkit.get_tools()
43
+
44
+ Environment Variables:
45
+ MINIMAX_API_KEY: MiniMax API key for authentication
46
+ MINIMAX_API_HOST: API host URL (default: https://api.minimax.io)
47
+ MINIMAX_MCP_BASE_PATH: Base path for output files
48
+
49
+ Attributes:
50
+ timeout (Optional[float]): Connection timeout in seconds.
51
+ (default: :obj:`None`)
52
+ """
53
+
54
+ def __init__(
55
+ self,
56
+ api_key: Optional[str] = None,
57
+ api_host: str = "https://api.minimax.io",
58
+ base_path: Optional[str] = None,
59
+ timeout: Optional[float] = None,
60
+ ) -> None:
61
+ r"""Initializes the MinimaxMCPToolkit.
62
+
63
+ Args:
64
+ api_key (Optional[str]): MiniMax API key for authentication.
65
+ If None, will attempt to read from MINIMAX_API_KEY
66
+ environment variable. (default: :obj:`None`)
67
+ api_host (str): MiniMax API host URL. Can be either
68
+ "https://api.minimax.io" (global) or
69
+ "https://api.minimaxi.com" (mainland China).
70
+ Can also be read from MINIMAX_API_HOST environment variable.
71
+ (default: :obj:`"https://api.minimax.io"`)
72
+ base_path (Optional[str]): Base path for output files.
73
+ If None, uses current working directory. Can also be read
74
+ from MINIMAX_MCP_BASE_PATH environment variable.
75
+ (default: :obj:`None`)
76
+ timeout (Optional[float]): Connection timeout in seconds.
77
+ (default: :obj:`None`)
78
+ """
79
+ super().__init__(timeout=timeout)
80
+
81
+ # Read API key from parameter or environment variable
82
+ if api_key is None:
83
+ api_key = os.getenv("MINIMAX_API_KEY")
84
+
85
+ if not api_key:
86
+ raise ValueError(
87
+ "api_key must be provided either as a parameter or through "
88
+ "the MINIMAX_API_KEY environment variable"
89
+ )
90
+
91
+ # Read API host from environment variable if not overridden
92
+ env_api_host = os.getenv("MINIMAX_API_HOST")
93
+ if env_api_host:
94
+ api_host = env_api_host
95
+
96
+ # Read base path from environment variable if not provided
97
+ if base_path is None:
98
+ base_path = os.getenv("MINIMAX_MCP_BASE_PATH")
99
+
100
+ # Set up environment variables for the MCP server
101
+ env = {
102
+ "MINIMAX_API_KEY": api_key,
103
+ "MINIMAX_API_HOST": api_host,
104
+ }
105
+
106
+ if base_path:
107
+ env["MINIMAX_MCP_BASE_PATH"] = base_path
108
+
109
+ self._mcp_toolkit = MCPToolkit(
110
+ config_dict={
111
+ "mcpServers": {
112
+ "minimax": {
113
+ "command": "uvx",
114
+ "args": ["minimax-mcp", "-y"],
115
+ "env": env,
116
+ }
117
+ }
118
+ },
119
+ timeout=timeout,
120
+ )
121
+
122
+ async def connect(self):
123
+ r"""Explicitly connect to the MiniMax MCP server."""
124
+ await self._mcp_toolkit.connect()
125
+
126
+ async def disconnect(self):
127
+ r"""Explicitly disconnect from the MiniMax MCP server."""
128
+ await self._mcp_toolkit.disconnect()
129
+
130
+ @property
131
+ def is_connected(self) -> bool:
132
+ r"""Check if the toolkit is connected to the MCP server.
133
+
134
+ Returns:
135
+ bool: True if connected, False otherwise.
136
+ """
137
+ return self._mcp_toolkit.is_connected
138
+
139
+ async def __aenter__(self) -> "MinimaxMCPToolkit":
140
+ r"""Async context manager entry point.
141
+
142
+ Returns:
143
+ MinimaxMCPToolkit: The connected toolkit instance.
144
+
145
+ Example:
146
+ async with MinimaxMCPToolkit(api_key="your-key") as toolkit:
147
+ tools = toolkit.get_tools()
148
+ """
149
+ await self.connect()
150
+ return self
151
+
152
+ async def __aexit__(self, _exc_type, _exc_val, _exc_tb) -> None:
153
+ r"""Async context manager exit point.
154
+
155
+ Automatically disconnects from the MiniMax MCP server.
156
+ """
157
+ await self.disconnect()
158
+
159
+ def get_tools(self) -> List[FunctionTool]:
160
+ r"""Returns a list of tools provided by the MiniMax MCP server.
161
+
162
+ This includes tools for:
163
+ - Text-to-audio conversion
164
+ - Voice cloning
165
+ - Video generation
166
+ - Image generation
167
+ - Music generation
168
+ - Voice design
169
+
170
+ Returns:
171
+ List[FunctionTool]: List of available MiniMax AI tools.
172
+ """
173
+ return self._mcp_toolkit.get_tools()
174
+
175
+ def get_text_tools(self) -> str:
176
+ r"""Returns a string containing the descriptions of the tools.
177
+
178
+ Returns:
179
+ str: A string containing the descriptions of all MiniMax tools.
180
+ """
181
+ return self._mcp_toolkit.get_text_tools()
182
+
183
+ async def call_tool(
184
+ self, tool_name: str, tool_args: Dict[str, Any]
185
+ ) -> Any:
186
+ r"""Call a MiniMax tool by name.
187
+
188
+ Args:
189
+ tool_name (str): Name of the tool to call.
190
+ tool_args (Dict[str, Any]): Arguments to pass to the tool.
191
+
192
+ Returns:
193
+ Any: The result of the tool call.
194
+ """
195
+ return await self._mcp_toolkit.call_tool(tool_name, tool_args)
@@ -66,6 +66,9 @@ class TerminalToolkit(BaseToolkit):
66
66
  connecting them to the terminal's standard input. This is useful
67
67
  for commands that require user input, like `ssh`. Interactive mode
68
68
  is only supported on macOS and Linux. (default: :obj:`False`)
69
+ log_dir (Optional[str]): Custom directory path for log files.
70
+ If None, logs are saved to the current working directory.
71
+ (default: :obj:`None`)
69
72
 
70
73
  Note:
71
74
  Most functions are compatible with Unix-based systems (macOS, Linux).
@@ -83,6 +86,7 @@ class TerminalToolkit(BaseToolkit):
83
86
  clone_current_env: bool = False,
84
87
  safe_mode: bool = True,
85
88
  interactive: bool = False,
89
+ log_dir: Optional[str] = None,
86
90
  ):
87
91
  # Store timeout before calling super().__init__
88
92
  self._timeout = timeout
@@ -99,6 +103,7 @@ class TerminalToolkit(BaseToolkit):
99
103
  self.use_shell_mode = use_shell_mode
100
104
  self._human_takeover_active = False
101
105
  self.interactive = interactive
106
+ self.log_dir = log_dir
102
107
 
103
108
  self.python_executable = sys.executable
104
109
  self.is_macos = platform.system() == 'Darwin'
@@ -153,8 +158,13 @@ class TerminalToolkit(BaseToolkit):
153
158
  r"""Set up file output to replace GUI, using a fixed file to simulate
154
159
  terminal.
155
160
  """
156
-
157
- self.log_file = os.path.join(os.getcwd(), "camel_terminal.txt")
161
+ # Use custom log directory if provided, otherwise use current directory
162
+ if self.log_dir:
163
+ # Create the log directory if it doesn't exist
164
+ os.makedirs(self.log_dir, exist_ok=True)
165
+ self.log_file = os.path.join(self.log_dir, "camel_terminal.txt")
166
+ else:
167
+ self.log_file = os.path.join(os.getcwd(), "camel_terminal.txt")
158
168
 
159
169
  # Inform the user
160
170
  logger.info(f"Terminal output will be redirected to: {self.log_file}")
@@ -195,18 +195,24 @@ class VideoAnalysisToolkit(BaseToolkit):
195
195
  destroyed.
196
196
  """
197
197
  # Clean up temporary files
198
- for temp_file in self._temp_files:
199
- if os.path.exists(temp_file):
200
- try:
201
- os.remove(temp_file)
202
- logger.debug(f"Removed temporary file: {temp_file}")
203
- except OSError as e:
204
- logger.warning(
205
- f"Failed to remove temporary file {temp_file}: {e}"
206
- )
198
+ if hasattr(self, '_temp_files'):
199
+ for temp_file in self._temp_files:
200
+ if os.path.exists(temp_file):
201
+ try:
202
+ os.remove(temp_file)
203
+ logger.debug(f"Removed temporary file: {temp_file}")
204
+ except OSError as e:
205
+ logger.warning(
206
+ f"Failed to remove temporary file {temp_file}: {e}"
207
+ )
207
208
 
208
209
  # Clean up temporary directory if needed
209
- if self._cleanup and os.path.exists(self._working_directory):
210
+ if (
211
+ hasattr(self, '_cleanup')
212
+ and self._cleanup
213
+ and hasattr(self, '_working_directory')
214
+ and os.path.exists(self._working_directory)
215
+ ):
210
216
  try:
211
217
  import sys
212
218
 
camel/types/enums.py CHANGED
@@ -64,6 +64,8 @@ class ModelType(UnifiedModelType, Enum):
64
64
  AWS_CLAUDE_OPUS_4 = "anthropic.claude-opus-4-20250514-v1:0"
65
65
  AWS_CLAUDE_OPUS_4_1 = "anthropic.claude-opus-4-1-20250805-v1:0"
66
66
 
67
+ AMD_GPT4 = "dvue-aoai-001-gpt-4.1"
68
+
67
69
  GLM_4 = "glm-4"
68
70
  GLM_4V = "glm-4v"
69
71
  GLM_4V_FLASH = "glm-4v-flash"
@@ -516,6 +518,13 @@ class ModelType(UnifiedModelType, Enum):
516
518
  ModelType.O3,
517
519
  }
518
520
 
521
+ @property
522
+ def is_amd(self) -> bool:
523
+ r"""Returns whether this type of models is a AMD model."""
524
+ return self in {
525
+ ModelType.AMD_GPT4,
526
+ }
527
+
519
528
  @property
520
529
  def is_aws_bedrock(self) -> bool:
521
530
  r"""Returns whether this type of models is an AWS Bedrock model."""
@@ -1349,6 +1358,7 @@ class ModelType(UnifiedModelType, Enum):
1349
1358
  ModelType.GLM_4_LONG,
1350
1359
  ModelType.TOGETHER_LLAMA_4_MAVERICK,
1351
1360
  ModelType.OPENROUTER_LLAMA_4_MAVERICK,
1361
+ ModelType.AMD_GPT4,
1352
1362
  ModelType.GPT_4_1,
1353
1363
  ModelType.GPT_4_1_MINI,
1354
1364
  ModelType.GPT_4_1_NANO,
@@ -1579,6 +1589,7 @@ class ModelPlatformType(Enum):
1579
1589
  COHERE = "cohere"
1580
1590
  YI = "lingyiwanwu"
1581
1591
  QWEN = "tongyi-qianwen"
1592
+ AMD = "amd"
1582
1593
  NVIDIA = "nvidia"
1583
1594
  DEEPSEEK = "deepseek"
1584
1595
  PPIO = "ppio"
camel/utils/commons.py CHANGED
@@ -354,6 +354,8 @@ def api_keys_required(
354
354
  key_way = "https://www.zhipuai.cn/"
355
355
  elif env_var_name == 'KLAVIS_API_KEY':
356
356
  key_way = "https://www.klavis.ai/docs"
357
+ elif env_var_name == 'XAI_API_KEY':
358
+ key_way = "https://api.x.ai/v1"
357
359
 
358
360
  if missing_keys:
359
361
  raise ValueError(
camel/utils/mcp.py CHANGED
@@ -13,7 +13,121 @@
13
13
  # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
14
14
  import functools
15
15
  import inspect
16
- from typing import Any, Callable, Optional
16
+ import warnings
17
+ from typing import (
18
+ Any,
19
+ Callable,
20
+ List,
21
+ Optional,
22
+ Tuple,
23
+ Union,
24
+ get_args,
25
+ get_origin,
26
+ get_type_hints,
27
+ )
28
+
29
+ from pydantic import create_model
30
+ from pydantic.errors import PydanticSchemaGenerationError
31
+
32
+ from camel.logger import get_logger
33
+
34
+ logger = get_logger(__name__)
35
+
36
+
37
+ def _is_pydantic_serializable(type_annotation: Any) -> Tuple[bool, str]:
38
+ r"""Check if a type annotation is Pydantic serializable.
39
+
40
+ Args:
41
+ type_annotation: The type annotation to check
42
+
43
+ Returns:
44
+ Tuple[bool, str]: (is_serializable, error_message)
45
+ """
46
+ # Handle None type
47
+ if type_annotation is type(None) or type_annotation is None:
48
+ return True, ""
49
+
50
+ # Handle generic types (List, Dict, Optional, etc.)
51
+ origin = get_origin(type_annotation)
52
+ if origin is not None:
53
+ args = get_args(type_annotation)
54
+
55
+ # For Union types (including Optional), check all args
56
+ if origin is Union:
57
+ for arg in args:
58
+ is_serializable, error_msg = _is_pydantic_serializable(arg)
59
+ if not is_serializable:
60
+ return False, error_msg
61
+ return True, ""
62
+
63
+ # For List, Set, Tuple, etc., check the contained types
64
+ if origin in (list, set, tuple, frozenset):
65
+ for arg in args:
66
+ is_serializable, error_msg = _is_pydantic_serializable(arg)
67
+ if not is_serializable:
68
+ return False, error_msg
69
+ return True, ""
70
+
71
+ # For Dict, check both key and value types
72
+ if origin is dict:
73
+ for arg in args:
74
+ is_serializable, error_msg = _is_pydantic_serializable(arg)
75
+ if not is_serializable:
76
+ return False, error_msg
77
+ return True, ""
78
+
79
+ # Try to create a simple pydantic model with this type
80
+ try:
81
+ create_model("TestModel", test_field=(type_annotation, ...))
82
+ # If model creation succeeds, the type is serializable
83
+ return True, ""
84
+ except (PydanticSchemaGenerationError, TypeError, ValueError) as e:
85
+ error_msg = (
86
+ f"Type '{type_annotation}' is not Pydantic serializable. "
87
+ f"Consider using a custom serializable type or converting "
88
+ f"to bytes/base64. Error: {e!s}"
89
+ )
90
+ return False, error_msg
91
+
92
+
93
+ def _validate_function_types(func: Callable[..., Any]) -> List[str]:
94
+ r"""Validate function parameter and return types are Pydantic serializable.
95
+
96
+ Args:
97
+ func (Callable[..., Any]): The function to validate.
98
+
99
+ Returns:
100
+ List[str]: List of error messages for incompatible types.
101
+ """
102
+ errors = []
103
+
104
+ try:
105
+ type_hints = get_type_hints(func)
106
+ except (NameError, AttributeError) as e:
107
+ # If we can't get type hints, skip validation
108
+ logger.warning(f"Could not get type hints for {func.__name__}: {e}")
109
+ return []
110
+
111
+ # Check return type
112
+ return_type = type_hints.get('return', Any)
113
+ if return_type != Any:
114
+ is_serializable, error_msg = _is_pydantic_serializable(return_type)
115
+ if not is_serializable:
116
+ errors.append(f"Return type: {error_msg}")
117
+
118
+ # Check parameter types
119
+ sig = inspect.signature(func)
120
+ for param_name, _param in sig.parameters.items():
121
+ if param_name == 'self':
122
+ continue
123
+
124
+ param_type = type_hints.get(param_name, Any)
125
+ if param_type != Any:
126
+ is_serializable, error_msg = _is_pydantic_serializable(param_type)
127
+ if not is_serializable:
128
+ errors.append(f"Parameter '{param_name}': {error_msg}")
129
+
130
+ return errors
17
131
 
18
132
 
19
133
  class MCPServer:
@@ -55,7 +169,7 @@ class MCPServer:
55
169
 
56
170
  def __init__(
57
171
  self,
58
- function_names: Optional[list[str]] = None,
172
+ function_names: Optional[List[str]] = None,
59
173
  server_name: Optional[str] = None,
60
174
  ):
61
175
  self.function_names = function_names
@@ -135,6 +249,26 @@ class MCPServer:
135
249
  f"Method {name} not found in class {cls.__name__} or "
136
250
  "cannot be called."
137
251
  )
252
+
253
+ # Validate function types for Pydantic compatibility
254
+ type_errors = _validate_function_types(func)
255
+ if type_errors:
256
+ error_message = (
257
+ f"Method '{name}' in class '{cls.__name__}' has "
258
+ f"non-Pydantic-serializable types:\n"
259
+ + "\n".join(f" - {error}" for error in type_errors)
260
+ + "\n\nSuggestions:"
261
+ + "\n - Use standard Python types (str, int, float, bool, bytes)" # noqa: E501
262
+ + "\n - Convert complex objects to JSON strings or bytes" # noqa: E501
263
+ + "\n - Create custom Pydantic models for complex data" # noqa: E501
264
+ + "\n - Use base64 encoding for binary data like images" # noqa: E501
265
+ )
266
+
267
+ # For now, issue a warning instead of raising an error
268
+ # This allows gradual migration while alerting developers
269
+ warnings.warn(error_message, UserWarning, stacklevel=3)
270
+ logger.warning(error_message)
271
+
138
272
  wrapper = self.make_wrapper(func)
139
273
  instance.mcp.tool(name=name)(wrapper)
140
274
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: camel-ai
3
- Version: 0.2.75a6
3
+ Version: 0.2.76a0
4
4
  Summary: Communicative Agents for AI Society Study
5
5
  Project-URL: Homepage, https://www.camel-ai.org/
6
6
  Project-URL: Repository, https://github.com/camel-ai/camel
@@ -39,7 +39,7 @@ Requires-Dist: dappier<0.4,>=0.3.3; extra == 'all'
39
39
  Requires-Dist: datacommons-pandas<0.0.4,>=0.0.3; extra == 'all'
40
40
  Requires-Dist: datacommons<2,>=1.4.3; extra == 'all'
41
41
  Requires-Dist: datasets<4,>=3; extra == 'all'
42
- Requires-Dist: daytona-sdk==0.20.0; extra == 'all'
42
+ Requires-Dist: daytona-sdk>=0.20.0; extra == 'all'
43
43
  Requires-Dist: diffusers<0.26,>=0.25.0; extra == 'all'
44
44
  Requires-Dist: discord-py<3,>=2.3.2; extra == 'all'
45
45
  Requires-Dist: docker<8,>=7.1.0; extra == 'all'
@@ -73,6 +73,7 @@ Requires-Dist: markitdown>=0.1.1; extra == 'all'
73
73
  Requires-Dist: math-verify<0.8,>=0.7.0; extra == 'all'
74
74
  Requires-Dist: mcp>=1.3.0; extra == 'all'
75
75
  Requires-Dist: mem0ai>=0.1.67; extra == 'all'
76
+ Requires-Dist: microsandbox>=0.1.8; extra == 'all'
76
77
  Requires-Dist: mistralai<2,>=1.1.0; extra == 'all'
77
78
  Requires-Dist: mock<6,>=5; extra == 'all'
78
79
  Requires-Dist: mypy<2,>=1.5.1; extra == 'all'
@@ -186,13 +187,14 @@ Requires-Dist: uv<0.8,>=0.7.0; extra == 'dev'
186
187
  Provides-Extra: dev-tools
187
188
  Requires-Dist: aci-sdk>=1.0.0b1; extra == 'dev-tools'
188
189
  Requires-Dist: agentops<0.4,>=0.3.21; extra == 'dev-tools'
189
- Requires-Dist: daytona-sdk==0.20.0; extra == 'dev-tools'
190
+ Requires-Dist: daytona-sdk>=0.20.0; extra == 'dev-tools'
190
191
  Requires-Dist: docker<8,>=7.1.0; extra == 'dev-tools'
191
192
  Requires-Dist: e2b-code-interpreter<2,>=1.0.3; extra == 'dev-tools'
192
193
  Requires-Dist: ipykernel<7,>=6.0.0; extra == 'dev-tools'
193
194
  Requires-Dist: jupyter-client<9,>=8.6.2; extra == 'dev-tools'
194
195
  Requires-Dist: langfuse>=2.60.5; extra == 'dev-tools'
195
196
  Requires-Dist: mcp>=1.3.0; extra == 'dev-tools'
197
+ Requires-Dist: microsandbox>=0.1.8; extra == 'dev-tools'
196
198
  Requires-Dist: tree-sitter-python<0.24,>=0.23.6; extra == 'dev-tools'
197
199
  Requires-Dist: tree-sitter<0.24,>=0.23.2; extra == 'dev-tools'
198
200
  Requires-Dist: typer>=0.15.2; extra == 'dev-tools'