camel-ai 0.2.44__py3-none-any.whl → 0.2.46__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.
- camel/__init__.py +1 -1
- camel/configs/__init__.py +6 -0
- camel/configs/bedrock_config.py +73 -0
- camel/configs/lmstudio_config.py +94 -0
- camel/configs/qwen_config.py +3 -3
- camel/models/__init__.py +4 -0
- camel/models/aiml_model.py +11 -104
- camel/models/anthropic_model.py +11 -76
- camel/models/aws_bedrock_model.py +112 -0
- camel/models/cohere_model.py +32 -4
- camel/models/deepseek_model.py +11 -44
- camel/models/gemini_model.py +10 -72
- camel/models/groq_model.py +11 -131
- camel/models/internlm_model.py +11 -61
- camel/models/litellm_model.py +11 -4
- camel/models/lmstudio_model.py +82 -0
- camel/models/mistral_model.py +14 -2
- camel/models/model_factory.py +7 -1
- camel/models/modelscope_model.py +11 -122
- camel/models/moonshot_model.py +10 -76
- camel/models/nemotron_model.py +4 -60
- camel/models/nvidia_model.py +11 -111
- camel/models/ollama_model.py +12 -205
- camel/models/openai_compatible_model.py +51 -12
- camel/models/openrouter_model.py +12 -131
- camel/models/ppio_model.py +10 -99
- camel/models/qwen_model.py +11 -122
- camel/models/reka_model.py +12 -4
- camel/models/sglang_model.py +5 -3
- camel/models/siliconflow_model.py +10 -58
- camel/models/togetherai_model.py +10 -177
- camel/models/vllm_model.py +11 -218
- camel/models/volcano_model.py +8 -17
- camel/models/yi_model.py +11 -98
- camel/models/zhipuai_model.py +11 -102
- camel/runtime/__init__.py +2 -0
- camel/runtime/ubuntu_docker_runtime.py +340 -0
- camel/toolkits/__init__.py +2 -0
- camel/toolkits/audio_analysis_toolkit.py +21 -17
- camel/toolkits/browser_toolkit.py +2 -1
- camel/toolkits/dalle_toolkit.py +15 -0
- camel/toolkits/excel_toolkit.py +14 -1
- camel/toolkits/image_analysis_toolkit.py +9 -1
- camel/toolkits/mcp_toolkit.py +2 -0
- camel/toolkits/networkx_toolkit.py +5 -0
- camel/toolkits/openai_agent_toolkit.py +5 -1
- camel/toolkits/pyautogui_toolkit.py +428 -0
- camel/toolkits/searxng_toolkit.py +7 -0
- camel/toolkits/slack_toolkit.py +15 -2
- camel/toolkits/video_analysis_toolkit.py +218 -78
- camel/toolkits/video_download_toolkit.py +10 -3
- camel/toolkits/weather_toolkit.py +14 -1
- camel/toolkits/zapier_toolkit.py +6 -2
- camel/types/enums.py +73 -0
- camel/types/unified_model_type.py +10 -0
- camel/verifiers/base.py +14 -0
- {camel_ai-0.2.44.dist-info → camel_ai-0.2.46.dist-info}/METADATA +6 -5
- {camel_ai-0.2.44.dist-info → camel_ai-0.2.46.dist-info}/RECORD +60 -54
- {camel_ai-0.2.44.dist-info → camel_ai-0.2.46.dist-info}/WHEEL +0 -0
- {camel_ai-0.2.44.dist-info → camel_ai-0.2.46.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,428 @@
|
|
|
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
|
+
import time
|
|
17
|
+
from typing import List, Literal, Optional, Tuple, Union
|
|
18
|
+
|
|
19
|
+
from camel.logger import get_logger
|
|
20
|
+
from camel.toolkits import BaseToolkit, FunctionTool
|
|
21
|
+
from camel.utils import MCPServer, dependencies_required
|
|
22
|
+
|
|
23
|
+
# Set up logging
|
|
24
|
+
logger = get_logger(__name__)
|
|
25
|
+
|
|
26
|
+
DURATION = 0.1
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@MCPServer()
|
|
30
|
+
class PyAutoGUIToolkit(BaseToolkit):
|
|
31
|
+
r"""A toolkit for automating GUI interactions using PyAutoGUI."""
|
|
32
|
+
|
|
33
|
+
@dependencies_required('pyautogui')
|
|
34
|
+
def __init__(
|
|
35
|
+
self,
|
|
36
|
+
timeout: Optional[float] = None,
|
|
37
|
+
screenshots_dir: str = "tmp",
|
|
38
|
+
):
|
|
39
|
+
r"""Initializes the PyAutoGUIToolkit with optional timeout.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
timeout (Optional[float]): Timeout for API requests in seconds.
|
|
43
|
+
(default: :obj:`None`)
|
|
44
|
+
screenshots_dir (str): Directory to save screenshots.
|
|
45
|
+
(default: :obj:`"tmp"`)
|
|
46
|
+
"""
|
|
47
|
+
import pyautogui
|
|
48
|
+
|
|
49
|
+
super().__init__(timeout=timeout)
|
|
50
|
+
# Configure PyAutoGUI for safety
|
|
51
|
+
self.pyautogui = pyautogui
|
|
52
|
+
|
|
53
|
+
self.pyautogui.FAILSAFE = True # Move mouse to upper-left to abort
|
|
54
|
+
|
|
55
|
+
# Get screen size for safety boundaries
|
|
56
|
+
self.screen_width, self.screen_height = self.pyautogui.size()
|
|
57
|
+
# Define safe boundaries (10% margin from edges)
|
|
58
|
+
self.safe_margin = 0.1
|
|
59
|
+
self.safe_min_x = int(self.screen_width * self.safe_margin)
|
|
60
|
+
self.safe_max_x = int(self.screen_width * (1 - self.safe_margin))
|
|
61
|
+
self.safe_min_y = int(self.screen_height * self.safe_margin)
|
|
62
|
+
self.safe_max_y = int(self.screen_height * (1 - self.safe_margin))
|
|
63
|
+
self.screen_center = (self.screen_width // 2, self.screen_height // 2)
|
|
64
|
+
self.screenshots_dir = os.path.expanduser(screenshots_dir)
|
|
65
|
+
|
|
66
|
+
def _get_safe_coordinates(self, x: int, y: int) -> Tuple[int, int]:
|
|
67
|
+
r"""Ensure coordinates are within safe boundaries to prevent triggering
|
|
68
|
+
failsafe.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
x (int): Original x-coordinate
|
|
72
|
+
y (int): Original y-coordinate
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
Tuple[int, int]: Safe coordinates
|
|
76
|
+
"""
|
|
77
|
+
# Clamp coordinates to safe boundaries
|
|
78
|
+
safe_x = max(self.safe_min_x, min(x, self.safe_max_x))
|
|
79
|
+
safe_y = max(self.safe_min_y, min(y, self.safe_max_y))
|
|
80
|
+
|
|
81
|
+
if safe_x != x or safe_y != y:
|
|
82
|
+
logger.info(
|
|
83
|
+
f"Safety: Adjusted coordinates from ({x}, {y}) to "
|
|
84
|
+
f"({safe_x}, {safe_y})"
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
return safe_x, safe_y
|
|
88
|
+
|
|
89
|
+
def mouse_move(self, x: int, y: int) -> str:
|
|
90
|
+
r"""Move mouse pointer to specified coordinates.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
x (int): X-coordinate to move to.
|
|
94
|
+
y (int): Y-coordinate to move to.
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
str: Success or error message.
|
|
98
|
+
"""
|
|
99
|
+
try:
|
|
100
|
+
# Apply safety boundaries
|
|
101
|
+
safe_x, safe_y = self._get_safe_coordinates(x, y)
|
|
102
|
+
self.pyautogui.moveTo(safe_x, safe_y, duration=DURATION)
|
|
103
|
+
return f"Mouse moved to position ({safe_x}, {safe_y})"
|
|
104
|
+
except Exception as e:
|
|
105
|
+
logger.error(f"Error moving mouse: {e}")
|
|
106
|
+
return f"Error: {e}"
|
|
107
|
+
|
|
108
|
+
def mouse_click(
|
|
109
|
+
self,
|
|
110
|
+
button: Literal["left", "middle", "right"] = "left",
|
|
111
|
+
clicks: int = 1,
|
|
112
|
+
x: Optional[int] = None,
|
|
113
|
+
y: Optional[int] = None,
|
|
114
|
+
) -> str:
|
|
115
|
+
r"""Performs a mouse click at the specified coordinates or current
|
|
116
|
+
position.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
button (Literal["left", "middle", "right"]): The mouse button to
|
|
120
|
+
click.
|
|
121
|
+
- "left": Typically used for selecting items, activating
|
|
122
|
+
buttons, or placing the cursor.
|
|
123
|
+
- "middle": Often used for opening links in a new tab or
|
|
124
|
+
specific application functions.
|
|
125
|
+
- "right": Usually opens a context menu providing options
|
|
126
|
+
related to the clicked item or area.
|
|
127
|
+
(default: :obj:`"left"`)
|
|
128
|
+
clicks (int): The number of times to click the button.
|
|
129
|
+
- 1: A single click, the most common action.
|
|
130
|
+
- 2: A double-click, often used to open files/folders or
|
|
131
|
+
select words.
|
|
132
|
+
(default: :obj:`1`)
|
|
133
|
+
x (Optional[int]): The x-coordinate on the screen to move the mouse
|
|
134
|
+
to before clicking. If None, clicks at the current mouse
|
|
135
|
+
position. (default: :obj:`None`)
|
|
136
|
+
y (Optional[int]): The y-coordinate on the screen to move the mouse
|
|
137
|
+
to before clicking. If None, clicks at the current mouse
|
|
138
|
+
position. (default: :obj:`None`)
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
str: A message indicating the action performed, e.g.,
|
|
142
|
+
"Clicked left button 1 time(s) at coordinates (100, 150)."
|
|
143
|
+
or "Clicked right button 2 time(s) at current position."
|
|
144
|
+
"""
|
|
145
|
+
try:
|
|
146
|
+
# Apply safety boundaries if coordinates are specified
|
|
147
|
+
position_info = "at current position"
|
|
148
|
+
if x is not None and y is not None:
|
|
149
|
+
safe_x, safe_y = self._get_safe_coordinates(x, y)
|
|
150
|
+
self.pyautogui.click(
|
|
151
|
+
x=safe_x, y=safe_y, button=button, clicks=clicks
|
|
152
|
+
)
|
|
153
|
+
position_info = f"at position ({safe_x}, {safe_y})"
|
|
154
|
+
else:
|
|
155
|
+
self.pyautogui.click(button=button, clicks=clicks)
|
|
156
|
+
|
|
157
|
+
return f"Clicked {button} button {clicks} time(s) {position_info}"
|
|
158
|
+
except Exception as e:
|
|
159
|
+
logger.error(f"Error clicking mouse: {e}")
|
|
160
|
+
return f"Error: {e}"
|
|
161
|
+
|
|
162
|
+
def get_mouse_position(self) -> str:
|
|
163
|
+
r"""Get current mouse position.
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
str: Current mouse X and Y coordinates.
|
|
167
|
+
"""
|
|
168
|
+
try:
|
|
169
|
+
x, y = self.pyautogui.position()
|
|
170
|
+
return f"Mouse position: ({x}, {y})"
|
|
171
|
+
except Exception as e:
|
|
172
|
+
logger.error(f"Error getting mouse position: {e}")
|
|
173
|
+
return f"Error: {e}"
|
|
174
|
+
|
|
175
|
+
def take_screenshot(self) -> str:
|
|
176
|
+
r"""Take a screenshot.
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
str: Path to the saved screenshot or error message.
|
|
180
|
+
"""
|
|
181
|
+
try:
|
|
182
|
+
# Create directory for screenshots if it doesn't exist
|
|
183
|
+
os.makedirs(self.screenshots_dir, exist_ok=True)
|
|
184
|
+
|
|
185
|
+
# Take screenshot
|
|
186
|
+
screenshot = self.pyautogui.screenshot()
|
|
187
|
+
|
|
188
|
+
# Save screenshot to file
|
|
189
|
+
timestamp = int(time.time())
|
|
190
|
+
filename = f"screenshot_{timestamp}.png"
|
|
191
|
+
filepath = os.path.join(self.screenshots_dir, filename)
|
|
192
|
+
screenshot.save(filepath)
|
|
193
|
+
|
|
194
|
+
return f"Screenshot saved to {filepath}"
|
|
195
|
+
except Exception as e:
|
|
196
|
+
logger.error(f"Error taking screenshot: {e}")
|
|
197
|
+
return f"Error: {e}"
|
|
198
|
+
|
|
199
|
+
def mouse_drag(
|
|
200
|
+
self,
|
|
201
|
+
start_x: int,
|
|
202
|
+
start_y: int,
|
|
203
|
+
end_x: int,
|
|
204
|
+
end_y: int,
|
|
205
|
+
button: Literal["left", "middle", "right"] = "left",
|
|
206
|
+
) -> str:
|
|
207
|
+
r"""Drag mouse from start position to end position.
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
start_x (int): Starting x-coordinate.
|
|
211
|
+
start_y (int): Starting y-coordinate.
|
|
212
|
+
end_x (int): Ending x-coordinate.
|
|
213
|
+
end_y (int): Ending y-coordinate.
|
|
214
|
+
button (Literal["left", "middle", "right"]): Mouse button to use
|
|
215
|
+
('left', 'middle', 'right'). (default: :obj:`'left'`)
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
str: Success or error message.
|
|
219
|
+
"""
|
|
220
|
+
try:
|
|
221
|
+
# Apply safety boundaries to both start and end positions
|
|
222
|
+
safe_start_x, safe_start_y = self._get_safe_coordinates(
|
|
223
|
+
start_x, start_y
|
|
224
|
+
)
|
|
225
|
+
safe_end_x, safe_end_y = self._get_safe_coordinates(end_x, end_y)
|
|
226
|
+
|
|
227
|
+
# Break operation into smaller steps for safety
|
|
228
|
+
# First move to start position
|
|
229
|
+
self.pyautogui.moveTo(
|
|
230
|
+
safe_start_x, safe_start_y, duration=DURATION
|
|
231
|
+
)
|
|
232
|
+
# Then perform drag
|
|
233
|
+
self.pyautogui.dragTo(
|
|
234
|
+
safe_end_x, safe_end_y, duration=DURATION, button=button
|
|
235
|
+
)
|
|
236
|
+
# Finally, move to a safe position (screen center) afterwards
|
|
237
|
+
self.pyautogui.moveTo(
|
|
238
|
+
self.screen_center[0],
|
|
239
|
+
self.screen_center[1],
|
|
240
|
+
duration=DURATION,
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
return (
|
|
244
|
+
f"Dragged from ({safe_start_x}, {safe_start_y}) "
|
|
245
|
+
f"to ({safe_end_x}, {safe_end_y})"
|
|
246
|
+
)
|
|
247
|
+
except Exception as e:
|
|
248
|
+
logger.error(f"Error dragging mouse: {e}")
|
|
249
|
+
# Try to move to safe position even after error
|
|
250
|
+
try:
|
|
251
|
+
self.pyautogui.moveTo(
|
|
252
|
+
self.screen_center[0],
|
|
253
|
+
self.screen_center[1],
|
|
254
|
+
duration=DURATION,
|
|
255
|
+
)
|
|
256
|
+
except Exception as recovery_error:
|
|
257
|
+
logger.error(
|
|
258
|
+
f"Failed to move to safe position: {recovery_error}"
|
|
259
|
+
)
|
|
260
|
+
return f"Error: {e}"
|
|
261
|
+
|
|
262
|
+
def scroll(
|
|
263
|
+
self,
|
|
264
|
+
scroll_amount: int,
|
|
265
|
+
x: Optional[int] = None,
|
|
266
|
+
y: Optional[int] = None,
|
|
267
|
+
) -> str:
|
|
268
|
+
r"""Scroll the mouse wheel.
|
|
269
|
+
|
|
270
|
+
Args:
|
|
271
|
+
scroll_amount (int): Amount to scroll. Positive values scroll up,
|
|
272
|
+
negative values scroll down.
|
|
273
|
+
x (Optional[int]): X-coordinate to scroll at. If None, uses current
|
|
274
|
+
position. (default: :obj:`None`)
|
|
275
|
+
y (Optional[int]): Y-coordinate to scroll at. If None, uses current
|
|
276
|
+
position. (default: :obj:`None`)
|
|
277
|
+
|
|
278
|
+
Returns:
|
|
279
|
+
str: Success or error message.
|
|
280
|
+
"""
|
|
281
|
+
try:
|
|
282
|
+
# Get current mouse position if coordinates are not specified
|
|
283
|
+
if x is None or y is None:
|
|
284
|
+
current_x, current_y = self.pyautogui.position()
|
|
285
|
+
x = x if x is not None else current_x
|
|
286
|
+
y = y if y is not None else current_y
|
|
287
|
+
|
|
288
|
+
# Always apply safety boundaries
|
|
289
|
+
safe_x, safe_y = self._get_safe_coordinates(x, y)
|
|
290
|
+
self.pyautogui.scroll(scroll_amount, x=safe_x, y=safe_y)
|
|
291
|
+
|
|
292
|
+
# Move mouse back to screen center for added safety
|
|
293
|
+
self.pyautogui.moveTo(self.screen_center[0], self.screen_center[1])
|
|
294
|
+
logger.info(
|
|
295
|
+
f"Safety: Moving mouse back to screen center "
|
|
296
|
+
f"({self.screen_center[0]}, {self.screen_center[1]})"
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
return (
|
|
300
|
+
f"Scrolled {scroll_amount} clicks at position "
|
|
301
|
+
f"{safe_x}, {safe_y}"
|
|
302
|
+
)
|
|
303
|
+
except Exception as e:
|
|
304
|
+
logger.error(f"Error scrolling: {e}")
|
|
305
|
+
return f"Error: {e}"
|
|
306
|
+
|
|
307
|
+
def keyboard_type(self, text: str, interval: float = 0.0) -> str:
|
|
308
|
+
r"""Type text on the keyboard.
|
|
309
|
+
|
|
310
|
+
Args:
|
|
311
|
+
text (str): Text to type.
|
|
312
|
+
interval (float): Seconds to wait between keypresses.
|
|
313
|
+
(default: :obj:`0.0`)
|
|
314
|
+
|
|
315
|
+
Returns:
|
|
316
|
+
str: Success or error message.
|
|
317
|
+
"""
|
|
318
|
+
try:
|
|
319
|
+
if not text:
|
|
320
|
+
return "Error: Empty text provided"
|
|
321
|
+
|
|
322
|
+
if len(text) > 1000: # Set a reasonable maximum length limit
|
|
323
|
+
warn_msg = (
|
|
324
|
+
f"Warning: Very long text ({len(text)} characters) may "
|
|
325
|
+
f"cause performance issues"
|
|
326
|
+
)
|
|
327
|
+
logger.warning(warn_msg)
|
|
328
|
+
|
|
329
|
+
# First, move mouse to a safe position to prevent potential issues
|
|
330
|
+
self.pyautogui.moveTo(
|
|
331
|
+
self.screen_center[0], self.screen_center[1], duration=DURATION
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
self.pyautogui.write(text, interval=interval)
|
|
335
|
+
return f"Typed text: {text[:20]}{'...' if len(text) > 20 else ''}"
|
|
336
|
+
except Exception as e:
|
|
337
|
+
logger.error(f"Error typing text: {e}")
|
|
338
|
+
return f"Error: {e}"
|
|
339
|
+
|
|
340
|
+
def press_key(self, key: Union[str, List[str]]) -> str:
|
|
341
|
+
r"""Press a key on the keyboard.
|
|
342
|
+
|
|
343
|
+
Args:
|
|
344
|
+
key (Union[str, List[str]]): The key to be pressed. Can also be a
|
|
345
|
+
list of such strings. Valid key names include:
|
|
346
|
+
- Basic characters: a-z, 0-9, and symbols like !, @, #, etc.
|
|
347
|
+
- Special keys: enter, esc, space, tab, backspace, delete
|
|
348
|
+
- Function keys: f1-f24
|
|
349
|
+
- Navigation: up, down, left, right, home, end, pageup,
|
|
350
|
+
pagedown
|
|
351
|
+
- Modifiers: shift, ctrl, alt, command, option, win
|
|
352
|
+
- Media keys: volumeup, volumedown, volumemute, playpause
|
|
353
|
+
|
|
354
|
+
Returns:
|
|
355
|
+
str: Success or error message.
|
|
356
|
+
"""
|
|
357
|
+
if isinstance(key, str):
|
|
358
|
+
key = [key]
|
|
359
|
+
try:
|
|
360
|
+
for k in key:
|
|
361
|
+
# Length validation (most valid key names are short)
|
|
362
|
+
if len(k) > 20:
|
|
363
|
+
logger.warning(
|
|
364
|
+
f"Warning: Key name '{k}' is too long "
|
|
365
|
+
"(max 20 characters)"
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
# Special character validation
|
|
369
|
+
# (key names usually don't contain special characters)
|
|
370
|
+
import re
|
|
371
|
+
|
|
372
|
+
if re.search(r'[^\w+\-_]', k) and len(k) > 1:
|
|
373
|
+
logger.warning(
|
|
374
|
+
f"Warning: Key '{k}' contains unusual characters"
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
# First, move mouse to a safe position to prevent potential issues
|
|
378
|
+
self.pyautogui.moveTo(
|
|
379
|
+
self.screen_center[0], self.screen_center[1], duration=DURATION
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
self.pyautogui.press(key)
|
|
383
|
+
return f"Pressed key: {key}"
|
|
384
|
+
except Exception as e:
|
|
385
|
+
logger.error(f"Error pressing key: {e}")
|
|
386
|
+
return f"Error: Invalid key '{key}' or error pressing it. {e}"
|
|
387
|
+
|
|
388
|
+
def hotkey(self, keys: List[str]) -> str:
|
|
389
|
+
r"""Press keys in succession and release in reverse order.
|
|
390
|
+
|
|
391
|
+
Args:
|
|
392
|
+
keys (List[str]): The series of keys to press, in order. This can
|
|
393
|
+
be either:
|
|
394
|
+
- Multiple string arguments, e.g., hotkey('ctrl', 'c')
|
|
395
|
+
- A single list of strings, e.g., hotkey(['ctrl', 'c'])
|
|
396
|
+
|
|
397
|
+
Returns:
|
|
398
|
+
str: Success or error message.
|
|
399
|
+
"""
|
|
400
|
+
try:
|
|
401
|
+
# First, move mouse to a safe position to prevent potential issues
|
|
402
|
+
self.pyautogui.moveTo(
|
|
403
|
+
self.screen_center[0], self.screen_center[1], duration=DURATION
|
|
404
|
+
)
|
|
405
|
+
|
|
406
|
+
self.pyautogui.hotkey(*keys)
|
|
407
|
+
return f"Pressed hotkey: {'+'.join(keys)}"
|
|
408
|
+
except Exception as e:
|
|
409
|
+
logger.error(f"Error pressing hotkey: {e}")
|
|
410
|
+
return f"Error: {e}"
|
|
411
|
+
|
|
412
|
+
def get_tools(self) -> List[FunctionTool]:
|
|
413
|
+
r"""Returns a list of FunctionTool objects for PyAutoGUI operations.
|
|
414
|
+
|
|
415
|
+
Returns:
|
|
416
|
+
List[FunctionTool]: List of PyAutoGUI functions.
|
|
417
|
+
"""
|
|
418
|
+
return [
|
|
419
|
+
FunctionTool(self.mouse_move),
|
|
420
|
+
FunctionTool(self.mouse_click),
|
|
421
|
+
FunctionTool(self.keyboard_type),
|
|
422
|
+
FunctionTool(self.take_screenshot),
|
|
423
|
+
FunctionTool(self.get_mouse_position),
|
|
424
|
+
FunctionTool(self.press_key),
|
|
425
|
+
FunctionTool(self.hotkey),
|
|
426
|
+
FunctionTool(self.mouse_drag),
|
|
427
|
+
FunctionTool(self.scroll),
|
|
428
|
+
]
|
|
@@ -20,10 +20,12 @@ import requests
|
|
|
20
20
|
from camel.logger import get_logger
|
|
21
21
|
from camel.toolkits.base import BaseToolkit
|
|
22
22
|
from camel.toolkits.function_tool import FunctionTool
|
|
23
|
+
from camel.utils import MCPServer
|
|
23
24
|
|
|
24
25
|
logger = get_logger(__name__)
|
|
25
26
|
|
|
26
27
|
|
|
28
|
+
@MCPServer()
|
|
27
29
|
class SearxNGToolkit(BaseToolkit):
|
|
28
30
|
r"""A toolkit for performing web searches using SearxNG search engine.
|
|
29
31
|
|
|
@@ -42,6 +44,9 @@ class SearxNGToolkit(BaseToolkit):
|
|
|
42
44
|
values are "day", "week", "month", "year". (default: :obj:`None`)
|
|
43
45
|
safe_search (int, optional): Safe search level (0: None, 1: Moderate,
|
|
44
46
|
2: Strict). (default: :obj:`1`)
|
|
47
|
+
timeout (Optional[float]): The timeout value for API requests
|
|
48
|
+
in seconds. If None, no timeout is applied.
|
|
49
|
+
(default: :obj:`None`)
|
|
45
50
|
|
|
46
51
|
Raises:
|
|
47
52
|
ValueError: If searxng_host is not a valid HTTP/HTTPS URL.
|
|
@@ -65,7 +70,9 @@ class SearxNGToolkit(BaseToolkit):
|
|
|
65
70
|
categories: Optional[List[str]] = None,
|
|
66
71
|
time_range: Optional[str] = None,
|
|
67
72
|
safe_search: int = 1,
|
|
73
|
+
timeout: Optional[float] = None,
|
|
68
74
|
) -> None:
|
|
75
|
+
super().__init__(timeout=timeout)
|
|
69
76
|
self._validate_searxng_host(searxng_host)
|
|
70
77
|
self._validate_safe_search(safe_search)
|
|
71
78
|
if time_range is not None:
|
camel/toolkits/slack_toolkit.py
CHANGED
|
@@ -15,7 +15,6 @@
|
|
|
15
15
|
from __future__ import annotations
|
|
16
16
|
|
|
17
17
|
import json
|
|
18
|
-
import logging
|
|
19
18
|
import os
|
|
20
19
|
from typing import TYPE_CHECKING, List, Optional
|
|
21
20
|
|
|
@@ -27,9 +26,10 @@ if TYPE_CHECKING:
|
|
|
27
26
|
|
|
28
27
|
from slack_sdk import WebClient
|
|
29
28
|
|
|
29
|
+
from camel.logger import get_logger
|
|
30
30
|
from camel.toolkits import FunctionTool
|
|
31
31
|
|
|
32
|
-
logger =
|
|
32
|
+
logger = get_logger(__name__)
|
|
33
33
|
|
|
34
34
|
|
|
35
35
|
@MCPServer()
|
|
@@ -40,6 +40,19 @@ class SlackToolkit(BaseToolkit):
|
|
|
40
40
|
channel, joining an existing channel, leaving a channel.
|
|
41
41
|
"""
|
|
42
42
|
|
|
43
|
+
def __init__(
|
|
44
|
+
self,
|
|
45
|
+
timeout: Optional[float] = None,
|
|
46
|
+
):
|
|
47
|
+
r"""Initializes a new instance of the SlackToolkit class.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
timeout (Optional[float]): The timeout value for API requests
|
|
51
|
+
in seconds. If None, no timeout is applied.
|
|
52
|
+
(default: :obj:`None`)
|
|
53
|
+
"""
|
|
54
|
+
super().__init__(timeout=timeout)
|
|
55
|
+
|
|
43
56
|
def _login_slack(
|
|
44
57
|
self,
|
|
45
58
|
slack_token: Optional[str] = None,
|