camel-ai 0.2.76a14__py3-none-any.whl → 0.2.78__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/agents/chat_agent.py +278 -154
- camel/data_collectors/alpaca_collector.py +15 -6
- camel/societies/workforce/prompts.py +131 -50
- camel/societies/workforce/single_agent_worker.py +390 -11
- camel/societies/workforce/structured_output_handler.py +30 -18
- camel/societies/workforce/utils.py +105 -12
- camel/societies/workforce/workforce.py +818 -224
- camel/societies/workforce/workforce_logger.py +24 -5
- camel/toolkits/context_summarizer_toolkit.py +2 -2
- camel/toolkits/excel_toolkit.py +1 -1
- camel/toolkits/file_toolkit.py +3 -2
- camel/toolkits/terminal_toolkit/utils.py +106 -154
- camel/types/enums.py +4 -4
- camel/utils/context_utils.py +379 -22
- {camel_ai-0.2.76a14.dist-info → camel_ai-0.2.78.dist-info}/METADATA +10 -1
- {camel_ai-0.2.76a14.dist-info → camel_ai-0.2.78.dist-info}/RECORD +19 -19
- {camel_ai-0.2.76a14.dist-info → camel_ai-0.2.78.dist-info}/WHEEL +0 -0
- {camel_ai-0.2.76a14.dist-info → camel_ai-0.2.78.dist-info}/licenses/LICENSE +0 -0
|
@@ -495,6 +495,11 @@ class WorkforceLogger:
|
|
|
495
495
|
|
|
496
496
|
tasks_handled_by_worker: Dict[str, int] = {}
|
|
497
497
|
|
|
498
|
+
# Track unique task final states to avoid double-counting
|
|
499
|
+
task_final_states: Dict[
|
|
500
|
+
str, str
|
|
501
|
+
] = {} # task_id -> 'completed' or 'failed'
|
|
502
|
+
|
|
498
503
|
# Helper function to check if a task is the main task (has no parent)
|
|
499
504
|
def is_main_task(task_id: str) -> bool:
|
|
500
505
|
return (
|
|
@@ -528,7 +533,9 @@ class WorkforceLogger:
|
|
|
528
533
|
elif event_type == 'task_completed':
|
|
529
534
|
# Exclude main task from total count
|
|
530
535
|
if not is_main_task(task_id):
|
|
531
|
-
|
|
536
|
+
# Track final state - a completed task overwrites any
|
|
537
|
+
# previous failed state
|
|
538
|
+
task_final_states[task_id] = 'completed'
|
|
532
539
|
# Count tasks handled by worker (only for non-main tasks)
|
|
533
540
|
if 'worker_id' in entry and entry['worker_id'] is not None:
|
|
534
541
|
worker_id = entry['worker_id']
|
|
@@ -550,7 +557,11 @@ class WorkforceLogger:
|
|
|
550
557
|
elif event_type == 'task_failed':
|
|
551
558
|
# Exclude main task from total count
|
|
552
559
|
if not is_main_task(task_id):
|
|
553
|
-
|
|
560
|
+
# Only track as failed if not already completed
|
|
561
|
+
# (in case of retries, the final completion overwrites
|
|
562
|
+
# failed state)
|
|
563
|
+
if task_final_states.get(task_id) != 'completed':
|
|
564
|
+
task_final_states[task_id] = 'failed'
|
|
554
565
|
# Count tasks handled by worker (only for non-main tasks)
|
|
555
566
|
if 'worker_id' in entry and entry['worker_id'] is not None:
|
|
556
567
|
worker_id = entry['worker_id']
|
|
@@ -565,6 +576,14 @@ class WorkforceLogger:
|
|
|
565
576
|
kpis['total_workforce_running_time_seconds'] = (
|
|
566
577
|
last_timestamp - first_timestamp
|
|
567
578
|
).total_seconds()
|
|
579
|
+
|
|
580
|
+
# Count unique tasks by final state
|
|
581
|
+
for _task_id, state in task_final_states.items():
|
|
582
|
+
if state == 'completed':
|
|
583
|
+
kpis['total_tasks_completed'] += 1
|
|
584
|
+
elif state == 'failed':
|
|
585
|
+
kpis['total_tasks_failed'] += 1
|
|
586
|
+
|
|
568
587
|
# Calculate worker utilization based on proportion of tasks handled
|
|
569
588
|
total_tasks_processed_for_utilization = (
|
|
570
589
|
kpis['total_tasks_completed'] + kpis['total_tasks_failed']
|
|
@@ -610,9 +629,9 @@ class WorkforceLogger:
|
|
|
610
629
|
|
|
611
630
|
kpis['total_workers_created'] = len(self._worker_information)
|
|
612
631
|
|
|
613
|
-
# Current pending tasks
|
|
614
|
-
kpis['current_pending_tasks'] = kpis['total_tasks_created'] - (
|
|
615
|
-
|
|
632
|
+
# Current pending tasks - tasks created but not yet completed or failed
|
|
633
|
+
kpis['current_pending_tasks'] = kpis['total_tasks_created'] - len(
|
|
634
|
+
task_final_states
|
|
616
635
|
)
|
|
617
636
|
|
|
618
637
|
return kpis
|
|
@@ -191,7 +191,7 @@ class ContextSummarizerToolkit(BaseToolkit):
|
|
|
191
191
|
summary (str): The summary text to save.
|
|
192
192
|
|
|
193
193
|
Returns:
|
|
194
|
-
str:
|
|
194
|
+
str: "success" or error message starting with "Error:".
|
|
195
195
|
"""
|
|
196
196
|
try:
|
|
197
197
|
# prepare metadata for unified markdown saving
|
|
@@ -221,7 +221,7 @@ class ContextSummarizerToolkit(BaseToolkit):
|
|
|
221
221
|
to save.
|
|
222
222
|
|
|
223
223
|
Returns:
|
|
224
|
-
str:
|
|
224
|
+
str: "success" or error message starting with "Error:".
|
|
225
225
|
"""
|
|
226
226
|
try:
|
|
227
227
|
# prepare metadata for markdown saving
|
camel/toolkits/excel_toolkit.py
CHANGED
|
@@ -872,7 +872,7 @@ class ExcelToolkit(BaseToolkit):
|
|
|
872
872
|
import csv
|
|
873
873
|
|
|
874
874
|
with open(
|
|
875
|
-
resolved_csv_path, 'w', newline='', encoding='utf-8'
|
|
875
|
+
resolved_csv_path, 'w', newline='', encoding='utf-8-sig'
|
|
876
876
|
) as csvfile:
|
|
877
877
|
writer = csv.writer(csvfile)
|
|
878
878
|
writer.writerows(data)
|
camel/toolkits/file_toolkit.py
CHANGED
|
@@ -906,7 +906,7 @@ class FileToolkit(BaseToolkit):
|
|
|
906
906
|
self,
|
|
907
907
|
file_path: Path,
|
|
908
908
|
content: Union[str, List[List]],
|
|
909
|
-
encoding: str = "utf-8",
|
|
909
|
+
encoding: str = "utf-8-sig",
|
|
910
910
|
) -> None:
|
|
911
911
|
r"""Write CSV content to a file.
|
|
912
912
|
|
|
@@ -914,7 +914,8 @@ class FileToolkit(BaseToolkit):
|
|
|
914
914
|
file_path (Path): The target file path.
|
|
915
915
|
content (Union[str, List[List]]): The CSV content as a string or
|
|
916
916
|
list of lists.
|
|
917
|
-
encoding (str): Character encoding to use.
|
|
917
|
+
encoding (str): Character encoding to use.
|
|
918
|
+
(default: :obj:`utf-8-sig`)
|
|
918
919
|
"""
|
|
919
920
|
import csv
|
|
920
921
|
|
|
@@ -15,7 +15,6 @@
|
|
|
15
15
|
import os
|
|
16
16
|
import platform
|
|
17
17
|
import re
|
|
18
|
-
import shlex
|
|
19
18
|
import shutil
|
|
20
19
|
import subprocess
|
|
21
20
|
import sys
|
|
@@ -27,38 +26,99 @@ from camel.logger import get_logger
|
|
|
27
26
|
logger = get_logger(__name__)
|
|
28
27
|
|
|
29
28
|
|
|
30
|
-
def
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
def check_command_safety(
|
|
30
|
+
command: str,
|
|
31
|
+
allowed_commands: Optional[Set[str]] = None,
|
|
32
|
+
) -> Tuple[bool, str]:
|
|
33
|
+
r"""Check if a command (potentially with chaining) is safe to execute.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
command (str): The command string to check
|
|
37
|
+
allowed_commands (Optional[Set[str]]): Set of allowed commands
|
|
38
|
+
(whitelist mode)
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
Tuple[bool, str]: (is_safe, reason)
|
|
33
42
|
"""
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
chaining_pattern = r'''
|
|
37
|
-
(?<!\\) # Not preceded by backslash (not escaped)
|
|
38
|
-
(?: # Group for alternation
|
|
39
|
-
; # Semicolon
|
|
40
|
-
| # OR
|
|
41
|
-
\|\| # Logical OR
|
|
42
|
-
| # OR
|
|
43
|
-
&& # Logical AND
|
|
44
|
-
| # OR
|
|
45
|
-
(?<!\|) # Not preceded by pipe (to avoid matching ||)
|
|
46
|
-
\| # Single pipe
|
|
47
|
-
(?!\|) # Not followed by pipe (to avoid matching ||)
|
|
48
|
-
)
|
|
49
|
-
(?= # Positive lookahead
|
|
50
|
-
(?: # Group
|
|
51
|
-
[^"'] # Not a quote
|
|
52
|
-
| # OR
|
|
53
|
-
"[^"]*" # Content in double quotes
|
|
54
|
-
| # OR
|
|
55
|
-
'[^']*' # Content in single quotes
|
|
56
|
-
)* # Zero or more times
|
|
57
|
-
$ # End of string
|
|
58
|
-
)
|
|
59
|
-
'''
|
|
43
|
+
if not command.strip():
|
|
44
|
+
return False, "Empty command is not allowed."
|
|
60
45
|
|
|
61
|
-
|
|
46
|
+
# Dangerous commands list - including ALL rm operations
|
|
47
|
+
dangerous_commands = [
|
|
48
|
+
# System administration
|
|
49
|
+
'sudo',
|
|
50
|
+
'su',
|
|
51
|
+
'reboot',
|
|
52
|
+
'shutdown',
|
|
53
|
+
'halt',
|
|
54
|
+
'poweroff',
|
|
55
|
+
'init',
|
|
56
|
+
# File system manipulation
|
|
57
|
+
'rm',
|
|
58
|
+
'chown',
|
|
59
|
+
'chgrp',
|
|
60
|
+
'umount',
|
|
61
|
+
'mount',
|
|
62
|
+
# Disk operations
|
|
63
|
+
'dd',
|
|
64
|
+
'mkfs',
|
|
65
|
+
'fdisk',
|
|
66
|
+
'parted',
|
|
67
|
+
'fsck',
|
|
68
|
+
'mkswap',
|
|
69
|
+
'swapon',
|
|
70
|
+
'swapoff',
|
|
71
|
+
# Process management
|
|
72
|
+
'service',
|
|
73
|
+
'systemctl',
|
|
74
|
+
'systemd',
|
|
75
|
+
# Network configuration
|
|
76
|
+
'iptables',
|
|
77
|
+
'ip6tables',
|
|
78
|
+
'ifconfig',
|
|
79
|
+
'route',
|
|
80
|
+
'iptables-save',
|
|
81
|
+
# Cron and scheduling
|
|
82
|
+
'crontab',
|
|
83
|
+
'at',
|
|
84
|
+
'batch',
|
|
85
|
+
# User management
|
|
86
|
+
'useradd',
|
|
87
|
+
'userdel',
|
|
88
|
+
'usermod',
|
|
89
|
+
'passwd',
|
|
90
|
+
'chpasswd',
|
|
91
|
+
'newgrp',
|
|
92
|
+
# Kernel modules
|
|
93
|
+
'modprobe',
|
|
94
|
+
'rmmod',
|
|
95
|
+
'insmod',
|
|
96
|
+
'lsmod',
|
|
97
|
+
]
|
|
98
|
+
|
|
99
|
+
# Remove quoted strings to avoid false positives
|
|
100
|
+
clean_command = re.sub(r'''["'][^"']*["']''', ' ', command)
|
|
101
|
+
|
|
102
|
+
# If whitelist mode, check ALL commands against the whitelist
|
|
103
|
+
if allowed_commands is not None:
|
|
104
|
+
# Extract all command words (at start or after operators)
|
|
105
|
+
cmd_pattern = r'(?:^|;|\||&&)\s*\b([a-zA-Z_/][\w\-/]*)'
|
|
106
|
+
found_commands = re.findall(cmd_pattern, clean_command, re.IGNORECASE)
|
|
107
|
+
for cmd in found_commands:
|
|
108
|
+
if cmd.lower() not in allowed_commands:
|
|
109
|
+
return (
|
|
110
|
+
False,
|
|
111
|
+
f"Command '{cmd}' is not in the allowed commands list.",
|
|
112
|
+
)
|
|
113
|
+
return True, ""
|
|
114
|
+
|
|
115
|
+
# Check for dangerous commands
|
|
116
|
+
for cmd in dangerous_commands:
|
|
117
|
+
pattern = rf'(?:^|;|\||&&)\s*\b{re.escape(cmd)}\b'
|
|
118
|
+
if re.search(pattern, clean_command, re.IGNORECASE):
|
|
119
|
+
return False, f"Command '{cmd}' is blocked for safety."
|
|
120
|
+
|
|
121
|
+
return True, ""
|
|
62
122
|
|
|
63
123
|
|
|
64
124
|
def sanitize_command(
|
|
@@ -80,133 +140,25 @@ def sanitize_command(
|
|
|
80
140
|
Returns:
|
|
81
141
|
Tuple[bool, str]: (is_safe, message_or_command)
|
|
82
142
|
"""
|
|
83
|
-
# Apply security checks to both backends - security should be consistent
|
|
84
143
|
if not safe_mode:
|
|
85
144
|
return True, command # Skip all checks if safe_mode is disabled
|
|
86
145
|
|
|
87
|
-
#
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
# If whitelist is defined, only allow whitelisted commands
|
|
101
|
-
if allowed_commands is not None:
|
|
102
|
-
if base_cmd not in allowed_commands:
|
|
103
|
-
return (
|
|
104
|
-
False,
|
|
105
|
-
f"Command '{base_cmd}' is not in the allowed commands list.",
|
|
146
|
+
# Use safety checker
|
|
147
|
+
is_safe, reason = check_command_safety(command, allowed_commands)
|
|
148
|
+
if not is_safe:
|
|
149
|
+
return False, reason
|
|
150
|
+
|
|
151
|
+
# Additional check for Docker backend: prevent cd outside working directory
|
|
152
|
+
if not use_docker_backend and working_dir and 'cd ' in command:
|
|
153
|
+
# Extract cd commands and check their targets
|
|
154
|
+
cd_pattern = r'\bcd\s+([^\s;|&]+)'
|
|
155
|
+
for match in re.finditer(cd_pattern, command):
|
|
156
|
+
target_path = match.group(1).strip('\'"')
|
|
157
|
+
target_dir = os.path.abspath(
|
|
158
|
+
os.path.join(working_dir, target_path)
|
|
106
159
|
)
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
else:
|
|
110
|
-
# Block dangerous commands (only when no whitelist is defined)
|
|
111
|
-
dangerous_commands = [
|
|
112
|
-
# System administration
|
|
113
|
-
'sudo',
|
|
114
|
-
'su',
|
|
115
|
-
'reboot',
|
|
116
|
-
'shutdown',
|
|
117
|
-
'halt',
|
|
118
|
-
'poweroff',
|
|
119
|
-
'init',
|
|
120
|
-
# File system manipulation
|
|
121
|
-
'rm',
|
|
122
|
-
'mv',
|
|
123
|
-
'chmod',
|
|
124
|
-
'chown',
|
|
125
|
-
'chgrp',
|
|
126
|
-
'umount',
|
|
127
|
-
'mount',
|
|
128
|
-
# Disk operations
|
|
129
|
-
'dd',
|
|
130
|
-
'mkfs',
|
|
131
|
-
'fdisk',
|
|
132
|
-
'parted',
|
|
133
|
-
'fsck',
|
|
134
|
-
'mkswap',
|
|
135
|
-
'swapon',
|
|
136
|
-
'swapoff',
|
|
137
|
-
# Process management
|
|
138
|
-
'kill',
|
|
139
|
-
'killall',
|
|
140
|
-
'pkill',
|
|
141
|
-
'service',
|
|
142
|
-
'systemctl',
|
|
143
|
-
'systemd',
|
|
144
|
-
# Network configuration
|
|
145
|
-
'iptables',
|
|
146
|
-
'ip6tables',
|
|
147
|
-
'ifconfig',
|
|
148
|
-
'route',
|
|
149
|
-
'iptables-save',
|
|
150
|
-
# Cron and scheduling
|
|
151
|
-
'crontab',
|
|
152
|
-
'at',
|
|
153
|
-
'batch',
|
|
154
|
-
# User management
|
|
155
|
-
'useradd',
|
|
156
|
-
'userdel',
|
|
157
|
-
'usermod',
|
|
158
|
-
'passwd',
|
|
159
|
-
'chpasswd',
|
|
160
|
-
'newgrp',
|
|
161
|
-
# Kernel modules
|
|
162
|
-
'modprobe',
|
|
163
|
-
'rmmod',
|
|
164
|
-
'insmod',
|
|
165
|
-
'lsmod',
|
|
166
|
-
# System information that could leak sensitive data
|
|
167
|
-
'dmesg',
|
|
168
|
-
'last',
|
|
169
|
-
'lastlog',
|
|
170
|
-
'who',
|
|
171
|
-
'w',
|
|
172
|
-
]
|
|
173
|
-
if base_cmd in dangerous_commands:
|
|
174
|
-
# Special handling for rm command - use regex for precise checking
|
|
175
|
-
if base_cmd == 'rm':
|
|
176
|
-
# Check for dangerous rm options using regex
|
|
177
|
-
dangerous_rm_pattern = (
|
|
178
|
-
r'\s-[^-\s]*[rf][^-\s]*\s|\s--force\s|'
|
|
179
|
-
r'\s--recursive\s|\s-rf\s|\s-fr\s'
|
|
180
|
-
)
|
|
181
|
-
if re.search(dangerous_rm_pattern, command, re.IGNORECASE):
|
|
182
|
-
return (
|
|
183
|
-
False,
|
|
184
|
-
f"Command '{base_cmd}' with forceful or "
|
|
185
|
-
f"recursive options is blocked for safety.",
|
|
186
|
-
)
|
|
187
|
-
# Also block rm without any target (could be dangerous)
|
|
188
|
-
if len(parts) < 2:
|
|
189
|
-
return (
|
|
190
|
-
False,
|
|
191
|
-
"rm command requires target "
|
|
192
|
-
"file/directory specification.",
|
|
193
|
-
)
|
|
194
|
-
else:
|
|
195
|
-
return False, f"Command '{base_cmd}' is blocked for safety."
|
|
196
|
-
|
|
197
|
-
# For local backend only: prevent changing
|
|
198
|
-
# directory outside the workspace
|
|
199
|
-
# Docker containers are already sandboxed,
|
|
200
|
-
# so this check is not needed there
|
|
201
|
-
if (
|
|
202
|
-
not use_docker_backend
|
|
203
|
-
and base_cmd == 'cd'
|
|
204
|
-
and len(parts) > 1
|
|
205
|
-
and working_dir
|
|
206
|
-
):
|
|
207
|
-
target_dir = os.path.abspath(os.path.join(working_dir, parts[1]))
|
|
208
|
-
if not target_dir.startswith(working_dir):
|
|
209
|
-
return False, "Cannot 'cd' outside of the working directory."
|
|
160
|
+
if not target_dir.startswith(working_dir):
|
|
161
|
+
return False, "Cannot 'cd' outside of the working directory."
|
|
210
162
|
|
|
211
163
|
return True, command
|
|
212
164
|
|
camel/types/enums.py
CHANGED
|
@@ -30,7 +30,7 @@ class RoleType(Enum):
|
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
class ModelType(UnifiedModelType, Enum):
|
|
33
|
-
DEFAULT = os.getenv("DEFAULT_MODEL_TYPE", "gpt-
|
|
33
|
+
DEFAULT = os.getenv("DEFAULT_MODEL_TYPE", "gpt-4.1-mini-2025-04-14")
|
|
34
34
|
|
|
35
35
|
GPT_3_5_TURBO = "gpt-3.5-turbo"
|
|
36
36
|
GPT_4 = "gpt-4"
|
|
@@ -207,7 +207,7 @@ class ModelType(UnifiedModelType, Enum):
|
|
|
207
207
|
CLAUDE_3_5_SONNET = "claude-3-5-sonnet-latest"
|
|
208
208
|
CLAUDE_3_5_HAIKU = "claude-3-5-haiku-latest"
|
|
209
209
|
CLAUDE_3_7_SONNET = "claude-3-7-sonnet-latest"
|
|
210
|
-
|
|
210
|
+
CLAUDE_SONNET_4_5 = "claude-sonnet-4-5"
|
|
211
211
|
CLAUDE_SONNET_4 = "claude-sonnet-4-20250514"
|
|
212
212
|
CLAUDE_OPUS_4 = "claude-opus-4-20250514"
|
|
213
213
|
CLAUDE_OPUS_4_1 = "claude-opus-4-1-20250805"
|
|
@@ -642,7 +642,7 @@ class ModelType(UnifiedModelType, Enum):
|
|
|
642
642
|
ModelType.CLAUDE_3_5_SONNET,
|
|
643
643
|
ModelType.CLAUDE_3_5_HAIKU,
|
|
644
644
|
ModelType.CLAUDE_3_7_SONNET,
|
|
645
|
-
ModelType.
|
|
645
|
+
ModelType.CLAUDE_SONNET_4_5,
|
|
646
646
|
ModelType.CLAUDE_SONNET_4,
|
|
647
647
|
ModelType.CLAUDE_OPUS_4,
|
|
648
648
|
ModelType.CLAUDE_OPUS_4_1,
|
|
@@ -1423,7 +1423,7 @@ class ModelType(UnifiedModelType, Enum):
|
|
|
1423
1423
|
ModelType.CLAUDE_3_5_SONNET,
|
|
1424
1424
|
ModelType.CLAUDE_3_5_HAIKU,
|
|
1425
1425
|
ModelType.CLAUDE_3_7_SONNET,
|
|
1426
|
-
ModelType.
|
|
1426
|
+
ModelType.CLAUDE_SONNET_4_5,
|
|
1427
1427
|
ModelType.CLAUDE_SONNET_4,
|
|
1428
1428
|
ModelType.CLAUDE_OPUS_4,
|
|
1429
1429
|
ModelType.CLAUDE_OPUS_4_1,
|