camel-ai 0.2.51__py3-none-any.whl → 0.2.53__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.

@@ -0,0 +1,248 @@
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 inspect
16
+ import json
17
+ import os
18
+ from functools import wraps
19
+ from typing import Any, Dict, List, Optional, Union
20
+
21
+ from pydantic import BaseModel
22
+
23
+ from camel.logger import get_logger
24
+ from camel.runtime import BaseRuntime
25
+ from camel.toolkits.function_tool import FunctionTool
26
+
27
+ logger = get_logger(__name__)
28
+
29
+
30
+ class DaytonaRuntime(BaseRuntime):
31
+ r"""A runtime that executes functions in a Daytona sandbox environment.
32
+ Requires the Daytona server to be running and an API key configured.
33
+
34
+ Args:
35
+ api_key (Optional[str]): The Daytona API key for authentication. If not
36
+ provided, it will try to use the DAYTONA_API_KEY environment
37
+ variable. (default: :obj: `None`)
38
+ api_url (Optional[str]): The URL of the Daytona server. If not
39
+ provided, it will try to use the DAYTONA_API_URL environment
40
+ variable. If none is provided, it will use "http://localhost:8000".
41
+ (default: :obj: `None`)
42
+ language (Optional[str]): The programming language for the sandbox.
43
+ (default: :obj: `"python"`)
44
+ """
45
+
46
+ def __init__(
47
+ self,
48
+ api_key: Optional[str] = None,
49
+ api_url: Optional[str] = None,
50
+ language: Optional[str] = "python",
51
+ ):
52
+ from daytona_sdk import Daytona, DaytonaConfig
53
+
54
+ super().__init__()
55
+ self.api_key = api_key or os.environ.get('DAYTONA_API_KEY')
56
+ self.api_url = api_url or os.environ.get('DAYTONA_API_URL')
57
+ self.language = language
58
+ self.config = DaytonaConfig(api_key=self.api_key, api_url=self.api_url)
59
+ self.daytona = Daytona(self.config)
60
+ self.sandbox = None
61
+ self.entrypoint: Dict[str, str] = dict()
62
+
63
+ def build(self) -> "DaytonaRuntime":
64
+ r"""Create and start a Daytona sandbox.
65
+
66
+ Returns:
67
+ DaytonaRuntime: The current runtime.
68
+ """
69
+ from daytona_sdk import CreateSandboxParams
70
+
71
+ try:
72
+ params = CreateSandboxParams(language=self.language)
73
+ self.sandbox = self.daytona.create(params)
74
+ if self.sandbox is None:
75
+ raise RuntimeError("Failed to create sandbox.")
76
+ logger.info(f"Sandbox created with ID: {self.sandbox.id}")
77
+ except Exception as e:
78
+ logger.error(f"Failed to create sandbox: {e!s}")
79
+ raise RuntimeError(f"Daytona sandbox creation failed: {e!s}")
80
+ return self
81
+
82
+ def _cleanup(self):
83
+ r"""Clean up the sandbox when exiting."""
84
+ if self.sandbox:
85
+ try:
86
+ self.daytona.remove(self.sandbox)
87
+ logger.info(f"Sandbox {self.sandbox.id} removed")
88
+ self.sandbox = None
89
+ except Exception as e:
90
+ logger.error(f"Failed to remove sandbox: {e!s}")
91
+
92
+ def add(
93
+ self,
94
+ funcs: Union[FunctionTool, List[FunctionTool]],
95
+ entrypoint: str,
96
+ arguments: Optional[Dict[str, Any]] = None,
97
+ ) -> "DaytonaRuntime":
98
+ r"""Add a function or list of functions to the runtime.
99
+
100
+ Args:
101
+ funcs (Union[FunctionTool, List[FunctionTool]]): The function or
102
+ list of functions to add.
103
+ entrypoint (str): The entrypoint for the function.
104
+ arguments (Optional[Dict[str, Any]]): The arguments for the
105
+ function. (default: :obj: `None`)
106
+
107
+ Returns:
108
+ DaytonaRuntime: The current runtime.
109
+ """
110
+ if not isinstance(funcs, list):
111
+ funcs = [funcs]
112
+ if arguments is not None:
113
+ entrypoint += json.dumps(arguments, ensure_ascii=False)
114
+
115
+ def make_wrapper(inner_func, func_name, func_code):
116
+ r"""Creates a wrapper for a function to execute it in the
117
+ Daytona sandbox.
118
+
119
+ Args:
120
+ inner_func (Callable): The function to wrap.
121
+ func_name (str): The name of the function.
122
+ func_code (str): The source code of the function.
123
+
124
+ Returns:
125
+ Callable: A wrapped function that executes in the sandbox.
126
+ """
127
+
128
+ @wraps(inner_func)
129
+ def wrapper(*args, **kwargs):
130
+ if not self.sandbox:
131
+ raise RuntimeError(
132
+ "Sandbox not initialized. Call build() first."
133
+ )
134
+
135
+ try:
136
+ for key, value in kwargs.items():
137
+ if isinstance(value, BaseModel):
138
+ kwargs[key] = value.model_dump()
139
+ args_str = json.dumps(args, ensure_ascii=True)
140
+ kwargs_str = json.dumps(kwargs, ensure_ascii=True)
141
+ except (TypeError, ValueError) as e:
142
+ logger.error(f"Failed to serialize arguments: {e!s}")
143
+ return {"error": f"Argument serialization failed: {e!s}"}
144
+
145
+ # Upload function code to the sandbox
146
+ script_path = f"/home/daytona/{func_name}.py"
147
+ try:
148
+ self.sandbox.fs.upload_file(
149
+ script_path, func_code.encode()
150
+ )
151
+ except Exception as e:
152
+ logger.error(
153
+ f"Failed to upload function {func_name}: {e!s}"
154
+ )
155
+ return {"error": f"Upload failed: {e!s}"}
156
+
157
+ exec_code = (
158
+ f"import sys\n"
159
+ f"sys.path.append('/home/daytona')\n"
160
+ f"import json\n"
161
+ f"from {func_name} import {func_name}\n"
162
+ f"args = json.loads('{args_str}')\n"
163
+ f"kwargs = json.loads('{kwargs_str}')\n"
164
+ f"result = {func_name}(*args, **kwargs)\n"
165
+ f"print(json.dumps(result) if result is not "
166
+ f"None else 'null')"
167
+ )
168
+
169
+ # Execute the function in the sandbox
170
+ try:
171
+ response = self.sandbox.process.code_run(exec_code)
172
+ return (
173
+ json.loads(response.result)
174
+ if response.result
175
+ else None
176
+ )
177
+ except json.JSONDecodeError as e:
178
+ logger.error(
179
+ f"Failed to decode JSON response for {func_name}"
180
+ )
181
+ return {"error": f"JSON decoding failed: {e!s}"}
182
+ except Exception as e:
183
+ logger.error(
184
+ f"Failed to execute function {func_name}: {e!s}"
185
+ )
186
+ return {"error": f"Execution failed: {e!s}"}
187
+
188
+ return wrapper
189
+
190
+ for func in funcs:
191
+ inner_func = func.func
192
+ func_name = func.get_function_name()
193
+ func_code = inspect.getsource(inner_func).strip()
194
+
195
+ func.func = make_wrapper(inner_func, func_name, func_code)
196
+ self.tools_map[func_name] = func
197
+ self.entrypoint[func_name] = entrypoint
198
+
199
+ return self
200
+
201
+ def info(self) -> str:
202
+ r"""Get information about the current sandbox.
203
+
204
+ Returns:
205
+ str: Information about the sandbox.
206
+
207
+ Raises:
208
+ RuntimeError: If the sandbox is not initialized.
209
+ """
210
+ if self.sandbox is None:
211
+ raise RuntimeError("Failed to create sandbox.")
212
+ info = self.sandbox.info()
213
+ return (
214
+ f"Sandbox {info.name}:\n"
215
+ f"State: {info.state}\n"
216
+ f"Resources: {info.resources.cpu} CPU, {info.resources.memory} RAM"
217
+ )
218
+
219
+ def __del__(self):
220
+ r"""Clean up the sandbox when the object is deleted."""
221
+ if hasattr(self, 'sandbox'):
222
+ self._cleanup()
223
+
224
+ def stop(self) -> "DaytonaRuntime":
225
+ r"""Stop and remove the sandbox.
226
+
227
+ Returns:
228
+ DaytonaRuntime: The current runtime.
229
+ """
230
+ self._cleanup()
231
+ return self
232
+
233
+ def reset(self) -> "DaytonaRuntime":
234
+ r"""Reset the sandbox by stopping and rebuilding it.
235
+
236
+ Returns:
237
+ DaytonaRuntime: The current runtime.
238
+ """
239
+ return self.stop().build()
240
+
241
+ @property
242
+ def docs(self) -> str:
243
+ r"""Get the URL for the Daytona API documentation.
244
+
245
+ Returns:
246
+ str: The URL for the API documentation.
247
+ """
248
+ return "https://www.daytona.io/docs/python-sdk/daytona/"
@@ -12,6 +12,7 @@
12
12
  # limitations under the License.
13
13
  # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
14
14
  import logging
15
+ import threading
15
16
  from typing import Dict, List, Optional, Sequence, Tuple, Union
16
17
 
17
18
  from camel.agents import (
@@ -77,6 +78,9 @@ class RolePlaying:
77
78
  task specify meta dict with. (default: :obj:`None`)
78
79
  output_language (str, optional): The language to be output by the
79
80
  agents. (default: :obj:`None`)
81
+ stop_event (Optional[threading.Event], optional): Event to signal
82
+ termination of the agent's operation. When set, the agent will
83
+ terminate its execution. (default: :obj:`None`)
80
84
  """
81
85
 
82
86
  def __init__(
@@ -101,6 +105,7 @@ class RolePlaying:
101
105
  extend_sys_msg_meta_dicts: Optional[List[Dict]] = None,
102
106
  extend_task_specify_meta_dict: Optional[Dict] = None,
103
107
  output_language: Optional[str] = None,
108
+ stop_event: Optional[threading.Event] = None,
104
109
  ) -> None:
105
110
  if model is not None:
106
111
  logger.warning(
@@ -156,6 +161,7 @@ class RolePlaying:
156
161
  assistant_agent_kwargs=assistant_agent_kwargs,
157
162
  user_agent_kwargs=user_agent_kwargs,
158
163
  output_language=output_language,
164
+ stop_event=stop_event,
159
165
  )
160
166
  self.critic: Optional[Union[CriticAgent, Human]] = None
161
167
  self.critic_sys_msg: Optional[BaseMessage] = None
@@ -316,6 +322,7 @@ class RolePlaying:
316
322
  assistant_agent_kwargs: Optional[Dict] = None,
317
323
  user_agent_kwargs: Optional[Dict] = None,
318
324
  output_language: Optional[str] = None,
325
+ stop_event: Optional[threading.Event] = None,
319
326
  ) -> None:
320
327
  r"""Initialize assistant and user agents with their system messages.
321
328
 
@@ -330,6 +337,9 @@ class RolePlaying:
330
337
  pass to the user agent. (default: :obj:`None`)
331
338
  output_language (str, optional): The language to be output by the
332
339
  agents. (default: :obj:`None`)
340
+ stop_event (Optional[threading.Event], optional): Event to signal
341
+ termination of the agent's operation. When set, the agent will
342
+ terminate its execution. (default: :obj:`None`)
333
343
  """
334
344
  if self.model is not None:
335
345
  if assistant_agent_kwargs is None:
@@ -344,6 +354,7 @@ class RolePlaying:
344
354
  self.assistant_agent = ChatAgent(
345
355
  init_assistant_sys_msg,
346
356
  output_language=output_language,
357
+ stop_event=stop_event,
347
358
  **(assistant_agent_kwargs or {}),
348
359
  )
349
360
  self.assistant_sys_msg = self.assistant_agent.system_message
@@ -351,6 +362,7 @@ class RolePlaying:
351
362
  self.user_agent = ChatAgent(
352
363
  init_user_sys_msg,
353
364
  output_language=output_language,
365
+ stop_event=stop_event,
354
366
  **(user_agent_kwargs or {}),
355
367
  )
356
368
  self.user_sys_msg = self.user_agent.system_message
@@ -68,6 +68,8 @@ from .pyautogui_toolkit import PyAutoGUIToolkit
68
68
  from .openai_agent_toolkit import OpenAIAgentToolkit
69
69
  from .searxng_toolkit import SearxNGToolkit
70
70
  from .jina_reranker_toolkit import JinaRerankerToolkit
71
+ from .klavis_toolkit import KlavisToolkit
72
+ from .aci_toolkit import ACIToolkit
71
73
 
72
74
 
73
75
  __all__ = [
@@ -124,4 +126,6 @@ __all__ = [
124
126
  'OpenAIAgentToolkit',
125
127
  'SearxNGToolkit',
126
128
  'JinaRerankerToolkit',
129
+ 'KlavisToolkit',
130
+ 'ACIToolkit',
127
131
  ]