camel-ai 0.2.73a1__py3-none-any.whl → 0.2.73a3__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.73a1'
17
+ __version__ = '0.2.73a3'
18
18
 
19
19
  __all__ = [
20
20
  '__version__',
@@ -2929,7 +2929,32 @@ class ChatAgent(BaseAgent):
2929
2929
  if function_name in self._internal_tools:
2930
2930
  tool = self._internal_tools[function_name]
2931
2931
  try:
2932
- result = await tool.async_call(**args)
2932
+ # Try different invocation paths in order of preference
2933
+ if hasattr(tool, 'func') and hasattr(
2934
+ tool.func, 'async_call'
2935
+ ):
2936
+ # Case: FunctionTool wrapping an MCP tool
2937
+ result = await tool.func.async_call(**args)
2938
+
2939
+ elif hasattr(tool, 'async_call') and callable(
2940
+ tool.async_call
2941
+ ):
2942
+ # Case: tool itself has async_call
2943
+ result = await tool.async_call(**args)
2944
+
2945
+ elif hasattr(tool, 'func') and asyncio.iscoroutinefunction(
2946
+ tool.func
2947
+ ):
2948
+ # Case: tool wraps a direct async function
2949
+ result = await tool.func(**args)
2950
+
2951
+ elif asyncio.iscoroutinefunction(tool):
2952
+ # Case: tool is itself a coroutine function
2953
+ result = await tool(**args)
2954
+
2955
+ else:
2956
+ # Fallback: synchronous call
2957
+ result = tool(**args)
2933
2958
 
2934
2959
  # Only record the tool response message, not the assistant
2935
2960
  # message assistant message with tool_calls was already
@@ -14,6 +14,7 @@
14
14
 
15
15
  # Enables postponed evaluation of annotations (for string-based type hints)
16
16
  import os
17
+ from pathlib import Path
17
18
  from typing import TYPE_CHECKING, List, Optional, Union
18
19
 
19
20
  from camel.logger import get_logger
@@ -41,7 +42,7 @@ class ExcelToolkit(BaseToolkit):
41
42
  def __init__(
42
43
  self,
43
44
  timeout: Optional[float] = None,
44
- file_path: Optional[str] = None,
45
+ working_directory: Optional[str] = None,
45
46
  ):
46
47
  r"""Initializes a new instance of the ExcelToolkit class.
47
48
 
@@ -49,16 +50,28 @@ class ExcelToolkit(BaseToolkit):
49
50
  timeout (Optional[float]): The timeout value for API requests
50
51
  in seconds. If None, no timeout is applied.
51
52
  (default: :obj:`None`)
52
- file_path (Optional[str]): Path to an existing Excel file to load.
53
- (default: :obj:`None`)
53
+ working_directory (str, optional): The default directory for
54
+ output files. If not provided, it will be determined by the
55
+ `CAMEL_WORKDIR` environment variable (if set). If the
56
+ environment variable is not set, it defaults to
57
+ `camel_working_dir`.
54
58
  """
55
59
  super().__init__(timeout=timeout)
56
- self.file_path = file_path
57
60
  self.wb = None
58
- if file_path and os.path.exists(file_path):
59
- from openpyxl import load_workbook
61
+ if working_directory:
62
+ self.working_directory = Path(working_directory).resolve()
63
+ else:
64
+ camel_workdir = os.environ.get("CAMEL_WORKDIR")
65
+ if camel_workdir:
66
+ self.working_directory = Path(camel_workdir).resolve()
67
+ else:
68
+ self.working_directory = Path("./camel_working_dir").resolve()
60
69
 
61
- self.wb = load_workbook(file_path)
70
+ self.working_directory.mkdir(parents=True, exist_ok=True)
71
+ logger.info(
72
+ f"ExcelToolkit initialized with output directory: "
73
+ f"{self.working_directory}"
74
+ )
62
75
 
63
76
  def _validate_file_path(self, file_path: str) -> bool:
64
77
  r"""Validate file path for security.
@@ -232,12 +245,11 @@ class ExcelToolkit(BaseToolkit):
232
245
  logger.error(f"Failed to process Excel file {document_path}: {e}")
233
246
  return f"Failed to process Excel file {document_path}: {e}"
234
247
 
235
- def _save_workbook(self, file_path: Optional[str] = None) -> str:
248
+ def _save_workbook(self, file_path: str) -> str:
236
249
  r"""Save the current workbook to file.
237
250
 
238
251
  Args:
239
- file_path (Optional[str]): The path to save the workbook.
240
- If None, uses self.file_path.
252
+ file_path (str): The path to save the workbook.
241
253
 
242
254
  Returns:
243
255
  str: Success or error message.
@@ -245,23 +257,46 @@ class ExcelToolkit(BaseToolkit):
245
257
  if not self.wb:
246
258
  return "Error: No workbook loaded to save."
247
259
 
248
- save_path = file_path or self.file_path
249
- if not save_path:
250
- return "Error: No file path specified for saving."
251
-
252
- if not self._validate_file_path(save_path):
260
+ if not self._validate_file_path(file_path):
253
261
  return "Error: Invalid file path for saving."
254
262
 
255
263
  try:
256
- self.wb.save(save_path)
257
- return f"Workbook saved successfully to {save_path}"
264
+ self.wb.save(file_path)
265
+ return f"Workbook saved successfully to {file_path}"
258
266
  except Exception as e:
259
267
  logger.error(f"Failed to save workbook: {e}")
260
268
  return f"Error: Failed to save workbook: {e}"
261
269
 
270
+ def save_workbook(self, filename: str) -> str:
271
+ r"""Save the current in-memory workbook to a file.
272
+
273
+ Args:
274
+ filename (str): The filename to save the workbook. Must end with
275
+ .xlsx extension. The file will be saved in self.
276
+ working_directory.
277
+
278
+ Returns:
279
+ str: Success message or error details.
280
+ """
281
+ if not self.wb:
282
+ return "Error: No workbook is currently loaded in memory."
283
+
284
+ # Validate filename
285
+ if not filename:
286
+ return "Error: Filename is required."
287
+
288
+ if not filename.endswith('.xlsx'):
289
+ return "Error: Filename must end with .xlsx extension."
290
+
291
+ # Create full path in working directory
292
+ file_path = self.working_directory / filename
293
+ resolved_file_path = str(file_path.resolve())
294
+
295
+ return self._save_workbook(resolved_file_path)
296
+
262
297
  def create_workbook(
263
298
  self,
264
- file_path: str,
299
+ filename: Optional[str] = None,
265
300
  sheet_name: Optional[str] = None,
266
301
  data: Optional[List[List[Union[str, int, float, None]]]] = None,
267
302
  ) -> str:
@@ -271,8 +306,9 @@ class ExcelToolkit(BaseToolkit):
271
306
  toolkit to work with the new file and optionally adds initial data.
272
307
 
273
308
  Args:
274
- file_path (str): Where to save the new Excel file. Must end with .
275
- xlsx.
309
+ filename (Optional[str]): The filename for the workbook. Must end
310
+ with .xlsx extension. The file will be saved in
311
+ self.working_directory. (default: :obj:`None`)
276
312
  sheet_name (Optional[str]): Name for the first sheet. If None,
277
313
  creates "Sheet1". (default: :obj:`None`)
278
314
  data (Optional[List[List[Union[str, int, float, None]]]]): Initial
@@ -284,14 +320,31 @@ class ExcelToolkit(BaseToolkit):
284
320
  """
285
321
  from openpyxl import Workbook
286
322
 
287
- if not self._validate_file_path(file_path):
323
+ # Validate filename
324
+ if filename is None:
325
+ return "Error: Filename is required."
326
+
327
+ if not filename.endswith('.xlsx'):
328
+ return "Error: Filename must end with .xlsx extension."
329
+
330
+ # Create full path in working directory
331
+ file_path = self.working_directory / filename
332
+ resolved_file_path = str(file_path.resolve())
333
+
334
+ if not self._validate_file_path(resolved_file_path):
288
335
  return "Error: Invalid file path."
289
336
 
337
+ # Check if file already exists
338
+ if os.path.exists(resolved_file_path):
339
+ return (
340
+ f"Error: File {filename} already exists in "
341
+ f"{self.working_directory}."
342
+ )
343
+
290
344
  try:
291
345
  # Create a new workbook
292
346
  wb = Workbook()
293
347
  self.wb = wb
294
- self.file_path = file_path
295
348
 
296
349
  # Handle sheet creation safely
297
350
  if sheet_name:
@@ -312,42 +365,55 @@ class ExcelToolkit(BaseToolkit):
312
365
  ws.append(row)
313
366
 
314
367
  # Save the workbook to the specified file path
315
- wb.save(file_path)
368
+ wb.save(resolved_file_path)
316
369
 
317
- return f"Workbook created successfully at {file_path}"
370
+ return f"Workbook created successfully at {resolved_file_path}"
318
371
  except Exception as e:
319
372
  logger.error(f"Failed to create workbook: {e}")
320
373
  return f"Error: Failed to create workbook: {e}"
321
374
 
322
- def delete_workbook(self, file_path: Optional[str] = None) -> str:
323
- r"""Delete a spreadsheet file.
375
+ def delete_workbook(self, filename: str) -> str:
376
+ r"""Delete a spreadsheet file from the working directory.
324
377
 
325
378
  Args:
326
- file_path (Optional[str]): The path of the file to delete.
327
- If None, uses self.file_path.
379
+ filename (str): The filename to delete. Must end with .xlsx
380
+ extension. The file will be deleted from self.
381
+ working_directory.
328
382
 
329
383
  Returns:
330
- str: Success message.
384
+ str: Success message or error details.
331
385
  """
332
- target_path = file_path or self.file_path
333
- if not target_path:
334
- return "Error: No file path specified for deletion."
386
+ # Validate filename
387
+ if not filename:
388
+ return "Error: Filename is required."
389
+
390
+ if not filename.endswith('.xlsx'):
391
+ return "Error: Filename must end with .xlsx extension."
392
+
393
+ # Create full path in working directory
394
+ file_path = self.working_directory / filename
395
+ target_path = str(file_path.resolve())
335
396
 
336
397
  if not self._validate_file_path(target_path):
337
398
  return "Error: Invalid file path."
338
399
 
339
400
  if not os.path.exists(target_path):
340
- return f"File {target_path} does not exist."
401
+ return (
402
+ f"Error: File {filename} does not exist in "
403
+ f"{self.working_directory}."
404
+ )
341
405
 
342
406
  try:
343
407
  os.remove(target_path)
344
- if target_path == self.file_path:
345
- self.wb = None
346
- self.file_path = None
347
- return f"Workbook {target_path} deleted successfully."
408
+ # Clean up workbook if one is loaded
409
+ self.wb = None
410
+ return (
411
+ f"Workbook {filename} deleted successfully from "
412
+ f"{self.working_directory}."
413
+ )
348
414
  except Exception as e:
349
415
  logger.error(f"Failed to delete workbook: {e}")
350
- return f"Failed to delete workbook {target_path}: {e}"
416
+ return f"Error: Failed to delete workbook {filename}: {e}"
351
417
 
352
418
  def create_sheet(
353
419
  self,
@@ -379,9 +445,6 @@ class ExcelToolkit(BaseToolkit):
379
445
  for row in data:
380
446
  ws.append(row)
381
447
 
382
- save_result = self._save_workbook()
383
- if save_result.startswith("Error"):
384
- return save_result
385
448
  return f"Sheet {sheet_name} created successfully."
386
449
  except Exception as e:
387
450
  logger.error(f"Failed to create sheet: {e}")
@@ -408,9 +471,6 @@ class ExcelToolkit(BaseToolkit):
408
471
  try:
409
472
  ws = self.wb[sheet_name]
410
473
  self.wb.remove(ws)
411
- save_result = self._save_workbook()
412
- if save_result.startswith("Error"):
413
- return save_result
414
474
  return f"Sheet {sheet_name} deleted successfully."
415
475
  except Exception as e:
416
476
  logger.error(f"Failed to delete sheet: {e}")
@@ -439,9 +499,6 @@ class ExcelToolkit(BaseToolkit):
439
499
  for cell in row:
440
500
  cell.value = None
441
501
 
442
- save_result = self._save_workbook()
443
- if save_result.startswith("Error"):
444
- return save_result
445
502
  return f"Sheet {sheet_name} cleared successfully."
446
503
  except Exception as e:
447
504
  logger.error(f"Failed to clear sheet: {e}")
@@ -479,7 +536,6 @@ class ExcelToolkit(BaseToolkit):
479
536
  num_rows = end_row - start_row + 1
480
537
  ws.delete_rows(start_row, num_rows)
481
538
 
482
- self._save_workbook()
483
539
  return (
484
540
  f"Deleted rows {start_row} to {end_row} from sheet "
485
541
  f"{sheet_name} successfully."
@@ -518,7 +574,6 @@ class ExcelToolkit(BaseToolkit):
518
574
  num_cols = end_col - start_col + 1
519
575
  ws.delete_cols(start_col, num_cols)
520
576
 
521
- self._save_workbook()
522
577
  return (
523
578
  f"Deleted columns {start_col} to {end_col} from sheet "
524
579
  f"{sheet_name} successfully."
@@ -585,9 +640,6 @@ class ExcelToolkit(BaseToolkit):
585
640
  ws[cell_reference].value = None
586
641
  else:
587
642
  ws[cell_reference] = value
588
- save_result = self._save_workbook()
589
- if save_result.startswith("Error"):
590
- return save_result
591
643
  return (
592
644
  f"Cell {cell_reference} updated successfully in sheet "
593
645
  f"{sheet_name}."
@@ -765,10 +817,9 @@ class ExcelToolkit(BaseToolkit):
765
817
  if col_idx < len(values[row_idx]):
766
818
  cell.value = values[row_idx][col_idx]
767
819
 
768
- self._save_workbook()
769
820
  return f"Values set for range {cell_range} in sheet {sheet_name}."
770
821
 
771
- def export_sheet_to_csv(self, sheet_name: str, csv_path: str) -> str:
822
+ def export_sheet_to_csv(self, sheet_name: str, csv_filename: str) -> str:
772
823
  r"""Export a specific sheet to CSV format.
773
824
 
774
825
  Use this to convert Excel sheets to CSV files for compatibility or
@@ -776,24 +827,63 @@ class ExcelToolkit(BaseToolkit):
776
827
 
777
828
  Args:
778
829
  sheet_name (str): Name of the sheet to export.
779
- csv_path (str): File path where CSV will be saved.
830
+ csv_filename (str): Filename for the CSV file. Must end with .csv
831
+ extension. The file will be saved in self.working_directory.
780
832
 
781
833
  Returns:
782
834
  str: Success confirmation message or error details.
783
835
  """
784
836
  if not self.wb:
785
- return "Error: Workbook not initialized."
837
+ return (
838
+ "Error: No workbook is currently loaded. Use "
839
+ "extract_excel_content to load a workbook first."
840
+ )
786
841
 
787
842
  if sheet_name not in self.wb.sheetnames:
788
- return f"Error: Sheet {sheet_name} does not exist."
843
+ return (
844
+ f"Error: Sheet {sheet_name} does not exist in the current "
845
+ "workbook."
846
+ )
789
847
 
790
- import pandas as pd
848
+ # Validate filename
849
+ if not csv_filename:
850
+ return "Error: CSV filename is required."
791
851
 
792
- # Read the specific sheet
793
- df = pd.read_excel(self.file_path, sheet_name=sheet_name)
794
- df.to_csv(csv_path, index=False)
852
+ if not csv_filename.endswith('.csv'):
853
+ return "Error: CSV filename must end with .csv extension."
795
854
 
796
- return f"Sheet {sheet_name} exported to CSV: {csv_path}"
855
+ # Create full path in working directory
856
+ csv_path = self.working_directory / csv_filename
857
+ resolved_csv_path = str(csv_path.resolve())
858
+
859
+ if not self._validate_file_path(resolved_csv_path):
860
+ return "Error: Invalid file path."
861
+
862
+ try:
863
+ # Get the worksheet
864
+ ws = self.wb[sheet_name]
865
+
866
+ # Convert worksheet to list of lists
867
+ data = []
868
+ for row in ws.iter_rows(values_only=True):
869
+ data.append(list(row))
870
+
871
+ # Write to CSV
872
+ import csv
873
+
874
+ with open(
875
+ resolved_csv_path, 'w', newline='', encoding='utf-8'
876
+ ) as csvfile:
877
+ writer = csv.writer(csvfile)
878
+ writer.writerows(data)
879
+
880
+ return (
881
+ f"Sheet {sheet_name} exported to {csv_filename} "
882
+ f"in {self.working_directory}."
883
+ )
884
+ except Exception as e:
885
+ logger.error(f"Failed to export sheet to CSV: {e}")
886
+ return f"Error: Failed to export sheet {sheet_name} to CSV: {e}"
797
887
 
798
888
  def get_rows(
799
889
  self,
@@ -862,7 +952,6 @@ class ExcelToolkit(BaseToolkit):
862
952
  return f"Error: Sheet {sheet_name} does not exist."
863
953
  ws = self.wb[sheet_name]
864
954
  ws.append(row_data)
865
- self._save_workbook()
866
955
  return f"Row appended to sheet {sheet_name} successfully."
867
956
 
868
957
  def update_row(
@@ -901,7 +990,6 @@ class ExcelToolkit(BaseToolkit):
901
990
  for col_idx, value in enumerate(row_data, 1):
902
991
  ws.cell(row=row_number, column=col_idx).value = value
903
992
 
904
- self._save_workbook()
905
993
  return f"Row {row_number} updated in sheet {sheet_name} successfully."
906
994
 
907
995
  def get_tools(self) -> List[FunctionTool]:
@@ -916,6 +1004,7 @@ class ExcelToolkit(BaseToolkit):
916
1004
  # File operations
917
1005
  FunctionTool(self.extract_excel_content),
918
1006
  FunctionTool(self.create_workbook),
1007
+ FunctionTool(self.save_workbook),
919
1008
  FunctionTool(self.delete_workbook),
920
1009
  FunctionTool(self.export_sheet_to_csv),
921
1010
  # Sheet operations