camel-ai 0.2.70__py3-none-any.whl → 0.2.71a2__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 CHANGED
@@ -14,7 +14,7 @@
14
14
 
15
15
  from camel.logger import disable_logging, enable_logging, set_log_level
16
16
 
17
- __version__ = '0.2.70'
17
+ __version__ = '0.2.71a2'
18
18
 
19
19
  __all__ = [
20
20
  '__version__',
@@ -13,10 +13,12 @@
13
13
  # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
14
14
  from __future__ import annotations
15
15
 
16
+ import asyncio
16
17
  import json
17
18
  import logging
18
19
  import textwrap
19
20
  import threading
21
+ import time
20
22
  import uuid
21
23
  from collections import defaultdict
22
24
  from pathlib import Path
@@ -173,6 +175,11 @@ class ChatAgent(BaseAgent):
173
175
  stop_event (Optional[threading.Event], optional): Event to signal
174
176
  termination of the agent's operation. When set, the agent will
175
177
  terminate its execution. (default: :obj:`None`)
178
+ mask_tool_output (Optional[bool]): Whether to return a sanitized
179
+ placeholder instead of the raw tool output. (default: :obj:`False`)
180
+ pause_event (Optional[asyncio.Event]): Event to signal pause of the
181
+ agent's operation. When clear, the agent will pause its execution.
182
+ (default: :obj:`None`)
176
183
  """
177
184
 
178
185
  def __init__(
@@ -206,6 +213,8 @@ class ChatAgent(BaseAgent):
206
213
  max_iteration: Optional[int] = None,
207
214
  agent_id: Optional[str] = None,
208
215
  stop_event: Optional[threading.Event] = None,
216
+ mask_tool_output: bool = False,
217
+ pause_event: Optional[asyncio.Event] = None,
209
218
  ) -> None:
210
219
  if isinstance(model, ModelManager):
211
220
  self.model_backend = model
@@ -280,6 +289,9 @@ class ChatAgent(BaseAgent):
280
289
  self.response_terminators = response_terminators or []
281
290
  self.max_iteration = max_iteration
282
291
  self.stop_event = stop_event
292
+ self.mask_tool_output = mask_tool_output
293
+ self._secure_result_store: Dict[str, Any] = {}
294
+ self.pause_event = pause_event
283
295
 
284
296
  def reset(self):
285
297
  r"""Resets the :obj:`ChatAgent` to its initial state."""
@@ -1143,6 +1155,10 @@ class ChatAgent(BaseAgent):
1143
1155
  iteration_count = 0
1144
1156
 
1145
1157
  while True:
1158
+ if self.pause_event is not None and not self.pause_event.is_set():
1159
+ while not self.pause_event.is_set():
1160
+ time.sleep(0.001)
1161
+
1146
1162
  try:
1147
1163
  openai_messages, num_tokens = self.memory.get_context()
1148
1164
  accumulated_context_tokens += num_tokens
@@ -1184,6 +1200,12 @@ class ChatAgent(BaseAgent):
1184
1200
  external_tool_call_requests = []
1185
1201
  external_tool_call_requests.append(tool_call_request)
1186
1202
  else:
1203
+ if (
1204
+ self.pause_event is not None
1205
+ and not self.pause_event.is_set()
1206
+ ):
1207
+ while not self.pause_event.is_set():
1208
+ time.sleep(0.001)
1187
1209
  tool_call_records.append(
1188
1210
  self._execute_tool(tool_call_request)
1189
1211
  )
@@ -1287,6 +1309,8 @@ class ChatAgent(BaseAgent):
1287
1309
  step_token_usage = self._create_token_usage_tracker()
1288
1310
  iteration_count = 0
1289
1311
  while True:
1312
+ if self.pause_event is not None and not self.pause_event.is_set():
1313
+ await self.pause_event.wait()
1290
1314
  try:
1291
1315
  openai_messages, num_tokens = self.memory.get_context()
1292
1316
  accumulated_context_tokens += num_tokens
@@ -1328,6 +1352,11 @@ class ChatAgent(BaseAgent):
1328
1352
  external_tool_call_requests = []
1329
1353
  external_tool_call_requests.append(tool_call_request)
1330
1354
  else:
1355
+ if (
1356
+ self.pause_event is not None
1357
+ and not self.pause_event.is_set()
1358
+ ):
1359
+ await self.pause_event.wait()
1331
1360
  tool_call_record = await self._aexecute_tool(
1332
1361
  tool_call_request
1333
1362
  )
@@ -1958,14 +1987,27 @@ class ChatAgent(BaseAgent):
1958
1987
  tool_call_id = tool_call_request.tool_call_id
1959
1988
  tool = self._internal_tools[func_name]
1960
1989
  try:
1961
- result = tool(**args)
1990
+ raw_result = tool(**args)
1991
+ if self.mask_tool_output:
1992
+ self._secure_result_store[tool_call_id] = raw_result
1993
+ result = (
1994
+ "[The tool has been executed successfully, but the output"
1995
+ " from the tool is masked. You can move forward]"
1996
+ )
1997
+ mask_flag = True
1998
+ else:
1999
+ result = raw_result
2000
+ mask_flag = False
1962
2001
  except Exception as e:
1963
2002
  # Capture the error message to prevent framework crash
1964
2003
  error_msg = f"Error executing tool '{func_name}': {e!s}"
1965
- result = {"error": error_msg}
2004
+ result = f"Tool execution failed: {error_msg}"
2005
+ mask_flag = False
1966
2006
  logging.warning(error_msg)
1967
2007
 
1968
- return self._record_tool_calling(func_name, args, result, tool_call_id)
2008
+ return self._record_tool_calling(
2009
+ func_name, args, result, tool_call_id, mask_output=mask_flag
2010
+ )
1969
2011
 
1970
2012
  async def _aexecute_tool(
1971
2013
  self,
@@ -2015,9 +2057,23 @@ class ChatAgent(BaseAgent):
2015
2057
  args: Dict[str, Any],
2016
2058
  result: Any,
2017
2059
  tool_call_id: str,
2060
+ mask_output: bool = False,
2018
2061
  ):
2019
2062
  r"""Record the tool calling information in the memory, and return the
2020
2063
  tool calling record.
2064
+
2065
+ Args:
2066
+ func_name (str): The name of the tool function called.
2067
+ args (Dict[str, Any]): The arguments passed to the tool.
2068
+ result (Any): The result returned by the tool execution.
2069
+ tool_call_id (str): A unique identifier for the tool call.
2070
+ mask_output (bool, optional): Whether to return a sanitized
2071
+ placeholder instead of the raw tool output.
2072
+ (default: :obj:`False`)
2073
+
2074
+ Returns:
2075
+ ToolCallingRecord: A struct containing information about
2076
+ this tool call.
2021
2077
  """
2022
2078
  assist_msg = FunctionCallingMessage(
2023
2079
  role_name=self.role_name,
@@ -2036,6 +2092,7 @@ class ChatAgent(BaseAgent):
2036
2092
  func_name=func_name,
2037
2093
  result=result,
2038
2094
  tool_call_id=tool_call_id,
2095
+ mask_output=mask_output,
2039
2096
  )
2040
2097
 
2041
2098
  # Use precise timestamps to ensure correct ordering
@@ -2140,6 +2197,7 @@ class ChatAgent(BaseAgent):
2140
2197
  ),
2141
2198
  max_iteration=self.max_iteration,
2142
2199
  stop_event=self.stop_event,
2200
+ pause_event=self.pause_event,
2143
2201
  )
2144
2202
 
2145
2203
  # Copy memory if requested
@@ -47,12 +47,16 @@ class FunctionCallingMessage(BaseMessage):
47
47
  (default: :obj:`None`)
48
48
  tool_call_id (Optional[str]): The ID of the tool call, if available.
49
49
  (default: :obj:`None`)
50
+ mask_output (Optional[bool]): Whether to return a sanitized placeholder
51
+ instead of the raw tool output.
52
+ (default: :obj:`False`)
50
53
  """
51
54
 
52
55
  func_name: Optional[str] = None
53
56
  args: Optional[Dict] = None
54
57
  result: Optional[Any] = None
55
58
  tool_call_id: Optional[str] = None
59
+ mask_output: Optional[bool] = False
56
60
 
57
61
  def to_openai_message(
58
62
  self,
@@ -105,10 +109,13 @@ class FunctionCallingMessage(BaseMessage):
105
109
  # This is a function response
106
110
  # TODO: Allow for more flexible setting of tool role,
107
111
  # optionally to be the same as assistant messages
108
- content = function_format.format_tool_response(
109
- self.func_name, # type: ignore[arg-type]
110
- self.result, # type: ignore[arg-type]
111
- )
112
+ if self.mask_output:
113
+ content = "[MASKED]"
114
+ else:
115
+ content = function_format.format_tool_response(
116
+ self.func_name, # type: ignore[arg-type]
117
+ self.result, # type: ignore[arg-type]
118
+ )
112
119
  return ShareGPTMessage(from_="tool", value=content) # type: ignore[call-arg]
113
120
 
114
121
  def to_openai_assistant_message(self) -> OpenAIAssistantMessage:
@@ -154,10 +161,30 @@ class FunctionCallingMessage(BaseMessage):
154
161
  " due to missing function name."
155
162
  )
156
163
 
157
- result_content = str(self.result)
164
+ if self.mask_output:
165
+ result_content = "[MASKED]"
166
+ else:
167
+ result_content = str(self.result)
158
168
 
159
169
  return {
160
170
  "role": "tool",
161
171
  "content": result_content,
162
172
  "tool_call_id": self.tool_call_id or "null",
163
173
  }
174
+
175
+ def to_dict(self) -> Dict:
176
+ r"""Converts the message to a dictionary.
177
+
178
+ Returns:
179
+ dict: The converted dictionary.
180
+ """
181
+ base = super().to_dict()
182
+ base["func_name"] = self.func_name
183
+ if self.args is not None:
184
+ base["args"] = self.args
185
+ if self.result is not None:
186
+ base["result"] = self.result
187
+ if self.tool_call_id is not None:
188
+ base["tool_call_id"] = self.tool_call_id
189
+ base["mask_output"] = self.mask_output
190
+ return base
@@ -27,7 +27,7 @@ from camel.societies.workforce.prompts import (
27
27
  )
28
28
  from camel.societies.workforce.utils import TaskResult
29
29
  from camel.societies.workforce.worker import Worker
30
- from camel.tasks.task import Task, TaskState, validate_task_content
30
+ from camel.tasks.task import Task, TaskState, is_task_result_insufficient
31
31
 
32
32
 
33
33
  class RolePlayingWorker(Worker):
@@ -178,14 +178,14 @@ class RolePlayingWorker(Worker):
178
178
  result_dict = json.loads(response.msg.content)
179
179
  task_result = TaskResult(**result_dict)
180
180
 
181
- if not validate_task_content(task_result.content, task.id):
181
+ task.result = task_result.content
182
+
183
+ if is_task_result_insufficient(task):
182
184
  print(
183
185
  f"{Fore.RED}Task {task.id}: Content validation failed - "
184
186
  f"task marked as failed{Fore.RESET}"
185
187
  )
186
188
  return TaskState.FAILED
187
189
 
188
- task.result = task_result.content
189
-
190
190
  print(f"Task result: {task.result}\n")
191
191
  return TaskState.DONE
@@ -26,7 +26,7 @@ from camel.agents import ChatAgent
26
26
  from camel.societies.workforce.prompts import PROCESS_TASK_PROMPT
27
27
  from camel.societies.workforce.utils import TaskResult
28
28
  from camel.societies.workforce.worker import Worker
29
- from camel.tasks.task import Task, TaskState, validate_task_content
29
+ from camel.tasks.task import Task, TaskState, is_task_result_insufficient
30
30
 
31
31
 
32
32
  class AgentPool:
@@ -43,8 +43,6 @@ class AgentPool:
43
43
  (default: :obj:`10`)
44
44
  auto_scale (bool): Whether to automatically scale the pool size.
45
45
  (default: :obj:`True`)
46
- scale_factor (float): Factor by which to scale the pool when needed.
47
- (default: :obj:`1.5`)
48
46
  idle_timeout (float): Time in seconds after which idle agents are
49
47
  removed. (default: :obj:`180.0`)
50
48
  """
@@ -55,13 +53,11 @@ class AgentPool:
55
53
  initial_size: int = 1,
56
54
  max_size: int = 10,
57
55
  auto_scale: bool = True,
58
- scale_factor: float = 1.5,
59
56
  idle_timeout: float = 180.0, # 3 minutes
60
57
  ):
61
58
  self.base_agent = base_agent
62
59
  self.max_size = max_size
63
60
  self.auto_scale = auto_scale
64
- self.scale_factor = scale_factor
65
61
  self.idle_timeout = idle_timeout
66
62
 
67
63
  # Pool management
@@ -332,7 +328,7 @@ class SingleAgentWorker(Worker):
332
328
  # Store the actual token usage for this specific task
333
329
  task.additional_info["token_usage"] = {"total_tokens": total_tokens}
334
330
 
335
- print(f"======\n{Fore.GREEN}Reply from {self}:{Fore.RESET}")
331
+ print(f"======\n{Fore.GREEN}Response from {self}:{Fore.RESET}")
336
332
 
337
333
  try:
338
334
  result_dict = json.loads(response.msg.content)
@@ -352,14 +348,14 @@ class SingleAgentWorker(Worker):
352
348
  if task_result.failed:
353
349
  return TaskState.FAILED
354
350
 
355
- if not validate_task_content(task_result.content, task.id):
351
+ task.result = task_result.content
352
+
353
+ if is_task_result_insufficient(task):
356
354
  print(
357
355
  f"{Fore.RED}Task {task.id}: Content validation failed - "
358
356
  f"task marked as failed{Fore.RESET}"
359
357
  )
360
358
  return TaskState.FAILED
361
-
362
- task.result = task_result.content
363
359
  return TaskState.DONE
364
360
 
365
361
  async def _listen_to_channel(self):