camel-ai 0.2.72a1__py3-none-any.whl → 0.2.72a4__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.72a1'
17
+ __version__ = '0.2.72a4'
18
18
 
19
19
  __all__ = [
20
20
  '__version__',
@@ -202,16 +202,20 @@ TASK_DECOMPOSE_PROMPT = r"""You need to decompose the given task into subtasks a
202
202
  * **DO NOT** use relative references like "the first task," "the paper mentioned above," or "the result from the previous step."
203
203
  * **DO** write explicit instructions. For example, instead of "Analyze the document," write "Analyze the document titled 'The Future of AI'." The system will automatically provide the necessary inputs (like the document itself) from previous steps.
204
204
 
205
- 1. **Strategic Grouping for Sequential Work**:
205
+ 2. **Define Clear Deliverables**: Each subtask must specify a clear, concrete deliverable. This tells the agent exactly what to produce and provides a clear "definition of done."
206
+ * **DO NOT** use vague verbs like "analyze," "look into," or "research" without defining the output.
207
+ * **DO** specify the format and content of the output. For example, instead of "Analyze the attached report," write "Summarize the key findings of the attached report in a 3-bullet-point list." Instead of "Find contacts," write "Extract all names and email addresses from the document and return them as a JSON list of objects, where each object has a 'name' and 'email' key."
208
+
209
+ 3. **Strategic Grouping for Sequential Work**:
206
210
  * If a series of steps must be done in order *and* can be handled by the same worker type, group them into a single subtask to maintain flow and minimize handoffs.
207
211
 
208
- 2. **Aggressive Parallelization**:
212
+ 4. **Aggressive Parallelization**:
209
213
  * **Across Different Worker Specializations**: If distinct phases of the overall task require different types of workers (e.g., research by a 'SearchAgent', then content creation by a 'DocumentAgent'), define these as separate subtasks.
210
214
  * **Within a Single Phase (Data/Task Parallelism)**: If a phase involves repetitive operations on multiple items (e.g., processing 10 documents, fetching 5 web pages, analyzing 3 datasets):
211
215
  * Decompose this into parallel subtasks, one for each item or a small batch of items.
212
216
  * This applies even if the same type of worker handles these parallel subtasks. The goal is to leverage multiple available workers or allow concurrent processing.
213
217
 
214
- 3. **Subtask Design for Efficiency**:
218
+ 5. **Subtask Design for Efficiency**:
215
219
  * **Actionable and Well-Defined**: Each subtask should have a clear, achievable goal.
216
220
  * **Balanced Granularity**: Make subtasks large enough to be meaningful but small enough to enable parallelism and quick feedback. Avoid overly large subtasks that hide parallel opportunities.
217
221
  * **Consider Dependencies**: While you list tasks sequentially, think about the true dependencies. The workforce manager will handle execution based on these implied dependencies and worker availability.
@@ -229,10 +233,10 @@ These principles aim to reduce overall completion time by maximizing concurrent
229
233
  * **Correct Decomposition**:
230
234
  ```xml
231
235
  <tasks>
232
- <task>Create a short blog post about the benefits of Python by researching key benefits, writing a 300-word article, and finding a suitable image.</task>
236
+ <task>Create a short blog post about the benefits of Python by researching key benefits, writing a 300-word article, and finding a suitable image. The final output should be a single string containing the 300-word article followed by the image URL.</task>
233
237
  </tasks>
234
238
  ```
235
- * **Reasoning**: All steps are sequential and can be handled by the same worker type (`Document Agent`). Grouping them into one subtask is efficient and maintains the workflow, following the "Strategic Grouping" principle.
239
+ * **Reasoning**: All steps are sequential and can be handled by the same worker type (`Document Agent`). Grouping them into one subtask is efficient and maintains the workflow, following the "Strategic Grouping" principle. **The deliverable is clearly defined as a single string.**
236
240
 
237
241
  ***
238
242
  **Example 2: Parallel Task Across Different Workers**
@@ -245,14 +249,14 @@ These principles aim to reduce overall completion time by maximizing concurrent
245
249
  * **Correct Decomposition**:
246
250
  ```xml
247
251
  <tasks>
248
- <task>Create a financial summary for Apple (AAPL) for Q2.</task>
249
- <task>Create a financial summary for Google (GOOGL) for Q2.</task>
250
- <task>Perform market sentiment analysis for Apple (AAPL) for Q2.</task>
251
- <task>Perform market sentiment analysis for Google (GOOGL) for Q2.</task>
252
- <task>Compile the provided financial summaries and market sentiment analyses for Apple (AAPL) and Google (GOOGL) into a single Q2 performance report.</task>
252
+ <task>Create a 1-paragraph financial summary for Apple (AAPL) for Q2, covering revenue, net income, and EPS. The output must be a plain text paragraph.</task>
253
+ <task>Create a 1-paragraph financial summary for Google (GOOGL) for Q2, covering revenue, net income, and EPS. The output must be a plain text paragraph.</task>
254
+ <task>Perform a market sentiment analysis for Apple (AAPL) for Q2, returning a single sentiment score from -1 (very negative) to 1 (very positive). The output must be a single floating-point number.</task>
255
+ <task>Perform a market sentiment analysis for Google (GOOGL) for Q2, returning a single sentiment score from -1 (very negative) to 1 (very positive). The output must be a single floating-point number.</task>
256
+ <task>Compile the provided financial summaries and market sentiment scores for Apple (AAPL) and Google (GOOGL) into a single Q2 performance report. The report should be a markdown-formatted document.</task>
253
257
  </tasks>
254
258
  ```
255
- * **Reasoning**: The financial analysis and market research can be done in parallel for both companies. The final report depends on all previous steps. This decomposition leverages worker specialization and parallelism, following the "Aggressive Parallelization" principle.
259
+ * **Reasoning**: The financial analysis and market research can be done in parallel for both companies. The final report depends on all previous steps. This decomposition leverages worker specialization and parallelism, following the "Aggressive Parallelization" principle. **Each subtask has a clearly defined deliverable.**
256
260
  ***
257
261
 
258
262
  **END OF EXAMPLES** - Now, apply these principles and examples to decompose the following task.
@@ -16,6 +16,7 @@ import atexit
16
16
  import os
17
17
  import platform
18
18
  import queue
19
+ import shutil
19
20
  import subprocess
20
21
  import sys
21
22
  import threading
@@ -41,21 +42,25 @@ class TerminalToolkit(BaseToolkit):
41
42
 
42
43
  Args:
43
44
  timeout (Optional[float]): The timeout for terminal operations.
45
+ (default: :obj:`None`)
44
46
  shell_sessions (Optional[Dict[str, Any]]): A dictionary to store
45
- shell session information. If None, an empty dictionary will be
46
- used. (default: :obj:`{}`)
47
- working_dir (str): The working directory for operations.
48
- If specified, all execution and write operations will be restricted
49
- to this directory. Read operations can access paths outside this
50
- directory.(default: :obj:`"./workspace"`)
47
+ shell session information. If :obj:`None`, an empty dictionary
48
+ will be used. (default: :obj:`None`)
49
+ working_directory (Optional[str]): The working directory for
50
+ operations. If not provided, it will be determined by the
51
+ `CAMEL_WORKDIR` environment variable (if set). If the
52
+ environment variable is not set, it defaults to `./workspace`. All
53
+ execution and write operations will be restricted to this
54
+ directory. Read operations can access paths outside this
55
+ directory. (default: :obj:`None`)
51
56
  need_terminal (bool): Whether to create a terminal interface.
52
57
  (default: :obj:`True`)
53
- use_shell_mode (bool): Whether to use shell mode for command execution.
54
- (default: :obj:`True`)
58
+ use_shell_mode (bool): Whether to use shell mode for command
59
+ execution. (default: :obj:`True`)
55
60
  clone_current_env (bool): Whether to clone the current Python
56
- environment.(default: :obj:`False`)
57
- safe_mode (bool): Whether to enable safe mode to restrict operations.
58
- (default: :obj:`True`)
61
+ environment. (default: :obj:`False`)
62
+ safe_mode (bool): Whether to enable safe mode to restrict
63
+ operations. (default: :obj:`True`)
59
64
 
60
65
  Note:
61
66
  Most functions are compatible with Unix-based systems (macOS, Linux).
@@ -67,7 +72,7 @@ class TerminalToolkit(BaseToolkit):
67
72
  self,
68
73
  timeout: Optional[float] = None,
69
74
  shell_sessions: Optional[Dict[str, Any]] = None,
70
- working_dir: str = "./workspace",
75
+ working_directory: Optional[str] = None,
71
76
  need_terminal: bool = True,
72
77
  use_shell_mode: bool = True,
73
78
  clone_current_env: bool = False,
@@ -88,12 +93,22 @@ class TerminalToolkit(BaseToolkit):
88
93
 
89
94
  self.python_executable = sys.executable
90
95
  self.is_macos = platform.system() == 'Darwin'
96
+ self.initial_env_path: Optional[str] = None
97
+ self.initial_env_prepared = False
91
98
 
92
99
  atexit.register(self.__del__)
93
100
 
94
- if not os.path.exists(working_dir):
95
- os.makedirs(working_dir, exist_ok=True)
96
- self.working_dir = os.path.abspath(working_dir)
101
+ if working_directory:
102
+ self.working_dir = os.path.abspath(working_directory)
103
+ else:
104
+ camel_workdir = os.environ.get("CAMEL_WORKDIR")
105
+ if camel_workdir:
106
+ self.working_dir = os.path.abspath(camel_workdir)
107
+ else:
108
+ self.working_dir = os.path.abspath("./workspace")
109
+
110
+ if not os.path.exists(self.working_dir):
111
+ os.makedirs(self.working_dir, exist_ok=True)
97
112
  self._update_terminal_output(
98
113
  f"Working directory set to: {self.working_dir}\n"
99
114
  )
@@ -108,6 +123,7 @@ class TerminalToolkit(BaseToolkit):
108
123
  self._clone_current_environment()
109
124
  else:
110
125
  self.cloned_env_path = None
126
+ self._prepare_initial_environment()
111
127
 
112
128
  if need_terminal:
113
129
  if self.is_macos:
@@ -178,20 +194,243 @@ class TerminalToolkit(BaseToolkit):
178
194
  return
179
195
 
180
196
  self._update_terminal_output(
181
- f"Creating new Python environment at:{self.cloned_env_path}\n"
197
+ f"Creating new Python environment at: {self.cloned_env_path}\n"
182
198
  )
183
199
 
200
+ # Create virtual environment with pip
184
201
  venv.create(self.cloned_env_path, with_pip=True)
202
+
203
+ # Ensure pip is properly available by upgrading it
204
+ if self.os_type == 'Windows':
205
+ python_path = os.path.join(
206
+ self.cloned_env_path, "Scripts", "python.exe"
207
+ )
208
+ else:
209
+ python_path = os.path.join(
210
+ self.cloned_env_path, "bin", "python"
211
+ )
212
+
213
+ # Verify python executable exists
214
+ if os.path.exists(python_path):
215
+ # Use python -m pip to ensure pip is available
216
+ subprocess.run(
217
+ [python_path, "-m", "pip", "install", "--upgrade", "pip"],
218
+ check=True,
219
+ capture_output=True,
220
+ cwd=self.working_dir,
221
+ timeout=60,
222
+ )
223
+ self._update_terminal_output(
224
+ "New Python environment created successfully with pip!\n"
225
+ )
226
+ else:
227
+ self._update_terminal_output(
228
+ f"Warning: Python executable not found at {python_path}\n"
229
+ )
230
+
231
+ except subprocess.CalledProcessError as e:
232
+ error_msg = e.stderr.decode() if e.stderr else str(e)
185
233
  self._update_terminal_output(
186
- "New Python environment created successfully!\n"
234
+ f"Failed to upgrade pip in cloned environment: {error_msg}\n"
235
+ )
236
+ logger.error(f"Failed to upgrade pip: {error_msg}")
237
+ except subprocess.TimeoutExpired:
238
+ self._update_terminal_output(
239
+ "Pip upgrade timed out, but environment may still be usable\n"
187
240
  )
188
-
189
241
  except Exception as e:
190
242
  self._update_terminal_output(
191
243
  f"Failed to create environment: {e!s}\n"
192
244
  )
193
245
  logger.error(f"Failed to create environment: {e}")
194
246
 
247
+ def _is_uv_environment(self) -> bool:
248
+ r"""Detect whether the current Python runtime is managed by uv."""
249
+ return (
250
+ "UV_CACHE_DIR" in os.environ
251
+ or "uv" in sys.executable
252
+ or shutil.which("uv") is not None
253
+ )
254
+
255
+ def _prepare_initial_environment(self):
256
+ r"""Prepare initial environment with Python 3.10, pip, and other
257
+ essential tools.
258
+ """
259
+ try:
260
+ self.initial_env_path = os.path.join(
261
+ self.working_dir, ".initial_env"
262
+ )
263
+
264
+ if os.path.exists(self.initial_env_path):
265
+ self._update_terminal_output(
266
+ f"Using existing initial environment"
267
+ f": {self.initial_env_path}\n"
268
+ )
269
+ self.initial_env_prepared = True
270
+ return
271
+
272
+ self._update_terminal_output(
273
+ f"Preparing initial environment at: {self.initial_env_path}\n"
274
+ )
275
+
276
+ # Create the initial environment directory
277
+ os.makedirs(self.initial_env_path, exist_ok=True)
278
+
279
+ # Check if we should use uv
280
+ if self._is_uv_environment():
281
+ self._setup_initial_env_with_uv()
282
+ else:
283
+ self._setup_initial_env_with_venv()
284
+
285
+ self.initial_env_prepared = True
286
+ self._update_terminal_output(
287
+ "Initial environment prepared successfully!\n"
288
+ )
289
+
290
+ except Exception as e:
291
+ self._update_terminal_output(
292
+ f"Failed to prepare initial environment: {e!s}\n"
293
+ )
294
+ logger.error(f"Failed to prepare initial environment: {e}")
295
+
296
+ def _setup_initial_env_with_uv(self):
297
+ r"""Set up initial environment using uv."""
298
+ if self.initial_env_path is None:
299
+ raise Exception("Initial environment path not set")
300
+
301
+ try:
302
+ # Create virtual environment with Python 3.10 using uv
303
+ subprocess.run(
304
+ ["uv", "venv", "--python", "3.10", self.initial_env_path],
305
+ check=True,
306
+ capture_output=True,
307
+ cwd=self.working_dir,
308
+ timeout=300,
309
+ )
310
+
311
+ # Get the python path from the new environment
312
+ if self.os_type == 'Windows':
313
+ python_path = os.path.join(
314
+ self.initial_env_path, "Scripts", "python.exe"
315
+ )
316
+ else:
317
+ python_path = os.path.join(
318
+ self.initial_env_path, "bin", "python"
319
+ )
320
+
321
+ # Install essential packages using uv
322
+ essential_packages = ["pip", "setuptools", "wheel"]
323
+ subprocess.run(
324
+ [
325
+ "uv",
326
+ "pip",
327
+ "install",
328
+ "--python",
329
+ python_path,
330
+ *essential_packages,
331
+ ],
332
+ check=True,
333
+ capture_output=True,
334
+ cwd=self.working_dir,
335
+ timeout=300,
336
+ )
337
+
338
+ # Check if Node.js is available (but don't install it)
339
+ self._check_nodejs_availability()
340
+
341
+ self._update_terminal_output(
342
+ "[UV] Initial environment created with Python 3.10 "
343
+ "and essential packages\n"
344
+ )
345
+
346
+ except subprocess.CalledProcessError as e:
347
+ error_msg = e.stderr.decode() if e.stderr else str(e)
348
+ raise Exception(f"UV setup failed: {error_msg}")
349
+ except subprocess.TimeoutExpired:
350
+ raise Exception("UV setup timed out after 5 minutes")
351
+
352
+ def _setup_initial_env_with_venv(self):
353
+ r"""Set up initial environment using standard venv."""
354
+ if self.initial_env_path is None:
355
+ raise Exception("Initial environment path not set")
356
+
357
+ try:
358
+ # Create virtual environment with system Python
359
+ venv.create(
360
+ self.initial_env_path,
361
+ with_pip=True,
362
+ system_site_packages=False,
363
+ )
364
+
365
+ # Get pip path
366
+ if self.os_type == 'Windows':
367
+ pip_path = os.path.join(
368
+ self.initial_env_path, "Scripts", "pip.exe"
369
+ )
370
+ else:
371
+ pip_path = os.path.join(self.initial_env_path, "bin", "pip")
372
+
373
+ # Upgrade pip and install essential packages
374
+ essential_packages = ["pip", "setuptools", "wheel"]
375
+ subprocess.run(
376
+ [pip_path, "install", "--upgrade", *essential_packages],
377
+ check=True,
378
+ capture_output=True,
379
+ cwd=self.working_dir,
380
+ timeout=300,
381
+ )
382
+
383
+ # Check if Node.js is available (but don't install it)
384
+ self._check_nodejs_availability()
385
+
386
+ self._update_terminal_output(
387
+ "Initial environment created with system Python and "
388
+ "essential packages\n"
389
+ )
390
+
391
+ except subprocess.CalledProcessError as e:
392
+ error_msg = e.stderr.decode() if e.stderr else str(e)
393
+ raise Exception(f"Venv setup failed: {error_msg}")
394
+ except subprocess.TimeoutExpired:
395
+ raise Exception("Venv setup timed out after 5 minutes")
396
+
397
+ def _check_nodejs_availability(self):
398
+ r"""Check if Node.js is available without modifying the system."""
399
+ try:
400
+ # Check if Node.js is already available in the system
401
+ node_result = subprocess.run(
402
+ ["node", "--version"],
403
+ check=False,
404
+ capture_output=True,
405
+ timeout=10,
406
+ )
407
+
408
+ npm_result = subprocess.run(
409
+ ["npm", "--version"],
410
+ check=False,
411
+ capture_output=True,
412
+ timeout=10,
413
+ )
414
+
415
+ if node_result.returncode == 0 and npm_result.returncode == 0:
416
+ node_version = node_result.stdout.decode().strip()
417
+ npm_version = npm_result.stdout.decode().strip()
418
+ self._update_terminal_output(
419
+ f"Node.js {node_version} and npm {npm_version} "
420
+ "are available\n"
421
+ )
422
+ else:
423
+ self._update_terminal_output(
424
+ "Note: Node.js not found. If needed, please install it "
425
+ "manually.\n"
426
+ )
427
+
428
+ except Exception as e:
429
+ self._update_terminal_output(
430
+ f"Note: Could not check Node.js availability - {e}.\n"
431
+ )
432
+ logger.warning(f"Failed to check Node.js: {e}")
433
+
195
434
  def _create_terminal(self):
196
435
  r"""Create a terminal GUI. If GUI creation fails, fallback
197
436
  to file output."""
@@ -335,108 +574,6 @@ class TerminalToolkit(BaseToolkit):
335
574
  logger.error(f"Failed to copy file: {e}")
336
575
  return None
337
576
 
338
- def file_find_in_content(
339
- self, file: str, regex: str, sudo: bool = False
340
- ) -> str:
341
- r"""Search for text within a file's content using a regular expression.
342
-
343
- This function is useful for finding specific patterns or lines of text
344
- within a given file. It uses `grep` on Unix-like systems and `findstr`
345
- on Windows.
346
-
347
- Args:
348
- file (str): The absolute path of the file to search within.
349
- regex (str): The regular expression pattern to match.
350
- sudo (bool, optional): Whether to use sudo privileges for the
351
- search. Defaults to False. Note: Using sudo requires the
352
- process to have appropriate permissions.
353
- (default: :obj:`False`)
354
-
355
- Returns:
356
- str: The matching content found in the file. If no matches are
357
- found, an empty string is returned. Returns an error message
358
- if the file does not exist or another error occurs.
359
- """
360
-
361
- if not os.path.exists(file):
362
- return f"File not found: {file}"
363
-
364
- if not os.path.isfile(file):
365
- return f"The path provided is not a file: {file}"
366
-
367
- command = []
368
- if sudo:
369
- error_msg = self._enforce_working_dir_for_execution(file)
370
- if error_msg:
371
- return error_msg
372
- command.extend(["sudo"])
373
-
374
- if self.os_type in ['Darwin', 'Linux']: # macOS or Linux
375
- command.extend(["grep", "-E", regex, file])
376
- else: # Windows
377
- # For Windows, we could use PowerShell or findstr
378
- command.extend(["findstr", "/R", regex, file])
379
-
380
- try:
381
- result = subprocess.run(
382
- command, check=False, capture_output=True, text=True
383
- )
384
- return result.stdout.strip()
385
- except subprocess.SubprocessError as e:
386
- logger.error(f"Error searching in file content: {e}")
387
- return f"Error: {e!s}"
388
-
389
- def file_find_by_name(self, path: str, glob: str) -> str:
390
- r"""Find files by name in a specified directory using a glob pattern.
391
-
392
- This function recursively searches for files matching a given name or
393
- pattern within a directory. It uses `find` on Unix-like systems and
394
- `dir` on Windows.
395
-
396
- Args:
397
- path (str): The absolute path of the directory to search in.
398
- glob (str): The filename pattern to search for, using glob syntax
399
- (e.g., "*.py", "data*").
400
-
401
- Returns:
402
- str: A newline-separated string containing the paths of the files
403
- that match the pattern. Returns an error message if the
404
- directory does not exist or another error occurs.
405
- """
406
- if not os.path.exists(path):
407
- return f"Directory not found: {path}"
408
-
409
- if not os.path.isdir(path):
410
- return f"The path provided is not a directory: {path}"
411
-
412
- command = []
413
- if self.os_type in ['Darwin', 'Linux']: # macOS or Linux
414
- command.extend(["find", path, "-name", glob])
415
- else: # Windows
416
- # For Windows, we use dir command with /s for recursive search
417
- # and /b for bare format
418
-
419
- pattern = glob
420
- file_path = os.path.join(path, pattern).replace('/', '\\')
421
- command.extend(["cmd", "/c", "dir", "/s", "/b", file_path])
422
-
423
- try:
424
- result = subprocess.run(
425
- command,
426
- check=False,
427
- capture_output=True,
428
- text=True,
429
- shell=False,
430
- )
431
-
432
- output = result.stdout.strip()
433
- if self.os_type == 'Windows':
434
- output = output.replace('\\', '/')
435
- return output
436
- except subprocess.SubprocessError as e:
437
- logger.error(f"Error finding files by name: {e}")
438
- return f"Error: {e!s}"
439
-
440
577
  def _sanitize_command(self, command: str, exec_dir: str) -> Tuple:
441
578
  r"""Check and modify command to ensure safety.
442
579
 
@@ -721,24 +858,75 @@ class TerminalToolkit(BaseToolkit):
721
858
  self._update_terminal_output(f"\n$ {command}\n")
722
859
 
723
860
  if command.startswith('python') or command.startswith('pip'):
724
- if self.cloned_env_path:
861
+ python_path = None
862
+ pip_path = None
863
+
864
+ # Try cloned environment first
865
+ if self.cloned_env_path and os.path.exists(
866
+ self.cloned_env_path
867
+ ):
725
868
  if self.os_type == 'Windows':
726
869
  base_path = os.path.join(
727
870
  self.cloned_env_path, "Scripts"
728
871
  )
729
- python_path = os.path.join(base_path, "python.exe")
730
- pip_path = os.path.join(base_path, "pip.exe")
872
+ python_candidate = os.path.join(
873
+ base_path, "python.exe"
874
+ )
875
+ pip_candidate = os.path.join(base_path, "pip.exe")
731
876
  else:
732
877
  base_path = os.path.join(self.cloned_env_path, "bin")
733
- python_path = os.path.join(base_path, "python")
734
- pip_path = os.path.join(base_path, "pip")
735
- else:
878
+ python_candidate = os.path.join(base_path, "python")
879
+ pip_candidate = os.path.join(base_path, "pip")
880
+
881
+ # Verify the executables exist
882
+ if os.path.exists(python_candidate):
883
+ python_path = python_candidate
884
+ # For pip, use python -m pip if pip executable doesn't
885
+ # exist
886
+ if os.path.exists(pip_candidate):
887
+ pip_path = pip_candidate
888
+ else:
889
+ pip_path = f'"{python_path}" -m pip'
890
+
891
+ # Try initial environment if cloned environment failed
892
+ if (
893
+ python_path is None
894
+ and self.initial_env_prepared
895
+ and self.initial_env_path
896
+ and os.path.exists(self.initial_env_path)
897
+ ):
898
+ if self.os_type == 'Windows':
899
+ base_path = os.path.join(
900
+ self.initial_env_path, "Scripts"
901
+ )
902
+ python_candidate = os.path.join(
903
+ base_path, "python.exe"
904
+ )
905
+ pip_candidate = os.path.join(base_path, "pip.exe")
906
+ else:
907
+ base_path = os.path.join(self.initial_env_path, "bin")
908
+ python_candidate = os.path.join(base_path, "python")
909
+ pip_candidate = os.path.join(base_path, "pip")
910
+
911
+ # Verify the executables exist
912
+ if os.path.exists(python_candidate):
913
+ python_path = python_candidate
914
+ # For pip, use python -m pip if pip executable doesn't
915
+ # exist
916
+ if os.path.exists(pip_candidate):
917
+ pip_path = pip_candidate
918
+ else:
919
+ pip_path = f'"{python_path}" -m pip'
920
+
921
+ # Fall back to system Python
922
+ if python_path is None:
736
923
  python_path = self.python_executable
737
924
  pip_path = f'"{python_path}" -m pip'
738
925
 
739
- if command.startswith('python'):
926
+ # Ensure we have valid paths before replacement
927
+ if python_path and command.startswith('python'):
740
928
  command = command.replace('python', f'"{python_path}"', 1)
741
- elif command.startswith('pip'):
929
+ elif pip_path and command.startswith('pip'):
742
930
  command = command.replace('pip', pip_path, 1)
743
931
 
744
932
  if not interactive:
@@ -1298,13 +1486,25 @@ class TerminalToolkit(BaseToolkit):
1298
1486
  f"'{session_id}': {e}"
1299
1487
  )
1300
1488
 
1301
- # Close file output if it exists
1489
+ # Clean up file output if it exists
1302
1490
  if hasattr(self, 'log_file') and self.is_macos:
1303
1491
  try:
1304
1492
  logger.info(f"Final terminal log saved to: {self.log_file}")
1305
1493
  except Exception as e:
1306
1494
  logger.error(f"Error logging file information: {e}")
1307
1495
 
1496
+ # Clean up initial environment if it exists
1497
+ if hasattr(self, 'initial_env_path') and self.initial_env_path:
1498
+ try:
1499
+ if os.path.exists(self.initial_env_path):
1500
+ shutil.rmtree(self.initial_env_path)
1501
+ logger.info(
1502
+ f"Cleaned up initial environment: "
1503
+ f"{self.initial_env_path}"
1504
+ )
1505
+ except Exception as e:
1506
+ logger.error(f"Error cleaning up initial environment: {e}")
1507
+
1308
1508
  # Clean up GUI resources if they exist
1309
1509
  if hasattr(self, 'root') and self.root:
1310
1510
  try:
@@ -1325,8 +1525,6 @@ class TerminalToolkit(BaseToolkit):
1325
1525
  functions in the toolkit.
1326
1526
  """
1327
1527
  return [
1328
- FunctionTool(self.file_find_in_content),
1329
- FunctionTool(self.file_find_by_name),
1330
1528
  FunctionTool(self.shell_exec),
1331
1529
  FunctionTool(self.shell_view),
1332
1530
  FunctionTool(self.shell_wait),
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: camel-ai
3
- Version: 0.2.72a1
3
+ Version: 0.2.72a4
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
@@ -1,4 +1,4 @@
1
- camel/__init__.py,sha256=bOXAnmMhIZ0wlMeQKroxC61PwHhhG0gX82k7PrX9PP4,901
1
+ camel/__init__.py,sha256=-uCv1AX5Ap45hQH56gS_ZVzKQCGj0wr5aWajREYrato,901
2
2
  camel/generators.py,sha256=JRqj9_m1PF4qT6UtybzTQ-KBT9MJQt18OAAYvQ_fr2o,13844
3
3
  camel/human.py,sha256=Xg8x1cS5KK4bQ1SDByiHZnzsRpvRP-KZViNvmu38xo4,5475
4
4
  camel/logger.py,sha256=WgEwael_eT6D-lVAKHpKIpwXSTjvLbny5jbV1Ab8lnA,5760
@@ -273,7 +273,7 @@ camel/societies/babyagi_playing.py,sha256=KbTdpHfZ2V8AripVck0bNTOyF-RSaMPCRARz3D
273
273
  camel/societies/role_playing.py,sha256=0XScr3WfxX1QOC71RhBLmrcS5y2c7DMQB_mAFOHU34M,31421
274
274
  camel/societies/workforce/__init__.py,sha256=bkTI-PE-MSK9AQ2V2gR6cR2WY-R7Jqy_NmXRtAoqo8o,920
275
275
  camel/societies/workforce/base.py,sha256=z2DmbTP5LL5-aCAAqglznQqCLfPmnyM5zD3w6jjtsb8,2175
276
- camel/societies/workforce/prompts.py,sha256=CYTkUJOYr3lcQAVAUAeKa_R-oyQHl5aECOkriMcXmjM,16518
276
+ camel/societies/workforce/prompts.py,sha256=WhqfedVdRjkh3AXCfX8YhELvS706NFKxayw0gg-6yVk,17908
277
277
  camel/societies/workforce/role_playing_worker.py,sha256=Zm89lZTlV0T3o9C-DJ0HAV68Iq2Kdg8QqJRWs1TV9_A,10320
278
278
  camel/societies/workforce/single_agent_worker.py,sha256=NpwUz7SpEL0E9HBXg_Zodmc272EbsTO-iwMaqE61tO4,19342
279
279
  camel/societies/workforce/structured_output_handler.py,sha256=xr8szFN86hg3jQ825aEkJTjkSFQnTlbinVg4j1vZJVI,17870
@@ -373,7 +373,7 @@ camel/toolkits/slack_toolkit.py,sha256=ZT6Ndlce2qjGsyZaNMfQ54nSEi7DOC9Ro7YqtK-u5
373
373
  camel/toolkits/stripe_toolkit.py,sha256=07swo5znGTnorafC1uYLKB4NRcJIOPOx19J7tkpLYWk,10102
374
374
  camel/toolkits/sympy_toolkit.py,sha256=BAQnI8EFJydNUpKQWXBdleQ1Cm-srDBhFlqp9V9pbPQ,33757
375
375
  camel/toolkits/task_planning_toolkit.py,sha256=Ttw9fHae4omGC1SA-6uaeXVHJ1YkwiVloz_hO-fm1gw,4855
376
- camel/toolkits/terminal_toolkit.py,sha256=fvFE-VJl5dgAmp27pplxc1y1Bw1KlkUjCU5jlnrdWKA,50543
376
+ camel/toolkits/terminal_toolkit.py,sha256=i01BSPGs3hbM76SXNt5XpUjco2AoUkWl5K0wUxxm5JQ,58349
377
377
  camel/toolkits/thinking_toolkit.py,sha256=nZYLvKWIx2BM1DYu69I9B5EISAG7aYcLYXKv9663BVk,8000
378
378
  camel/toolkits/twitter_toolkit.py,sha256=Px4N8aUxUzy01LhGSWkdrC2JgwKkrY3cvxgMeJ2XYfU,15939
379
379
  camel/toolkits/video_analysis_toolkit.py,sha256=Wh08MAVvs3PtgXN88Sk0TXYaGfVmQAol8FPCXMPPpIM,23375
@@ -446,7 +446,7 @@ camel/verifiers/math_verifier.py,sha256=tA1D4S0sm8nsWISevxSN0hvSVtIUpqmJhzqfbuMo
446
446
  camel/verifiers/models.py,sha256=GdxYPr7UxNrR1577yW4kyroRcLGfd-H1GXgv8potDWU,2471
447
447
  camel/verifiers/physics_verifier.py,sha256=c1grrRddcrVN7szkxhv2QirwY9viIRSITWeWFF5HmLs,30187
448
448
  camel/verifiers/python_verifier.py,sha256=ogTz77wODfEcDN4tMVtiSkRQyoiZbHPY2fKybn59lHw,20558
449
- camel_ai-0.2.72a1.dist-info/METADATA,sha256=rns02LzW1tpLpitGjyuN0uXG0j_S-Kp4oOXyUqpWnIk,49998
450
- camel_ai-0.2.72a1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
451
- camel_ai-0.2.72a1.dist-info/licenses/LICENSE,sha256=id0nB2my5kG0xXeimIu5zZrbHLS6EQvxvkKkzIHaT2k,11343
452
- camel_ai-0.2.72a1.dist-info/RECORD,,
449
+ camel_ai-0.2.72a4.dist-info/METADATA,sha256=kOuH0htxiRDCQnwwcynOhM_PqLTFaIicdxG9lmE-kqw,49998
450
+ camel_ai-0.2.72a4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
451
+ camel_ai-0.2.72a4.dist-info/licenses/LICENSE,sha256=id0nB2my5kG0xXeimIu5zZrbHLS6EQvxvkKkzIHaT2k,11343
452
+ camel_ai-0.2.72a4.dist-info/RECORD,,