camel-ai 0.2.57__py3-none-any.whl → 0.2.59__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/human.py CHANGED
@@ -136,3 +136,17 @@ class Human:
136
136
  content = self.parse_input(human_input)
137
137
  message = meta_chat_message.create_new_instance(content)
138
138
  return ChatAgentResponse(msgs=[message], terminated=False, info={})
139
+
140
+ def clone(self, with_memory: bool = False) -> 'Human':
141
+ r"""Creates a new instance of the Human class with the same
142
+ attributes.
143
+
144
+ Args:
145
+ with_memory (bool): Flag indicating whether to include memory in
146
+ the cloned instance. Currently not used.
147
+ (default: :obj:`False`)
148
+
149
+ Returns:
150
+ Human: A new Human instance with the same name and logger_color.
151
+ """
152
+ return Human(name=self.name, logger_color=self.logger_color)
camel/loaders/__init__.py CHANGED
@@ -18,6 +18,7 @@ from .chunkr_reader import ChunkrReader
18
18
  from .crawl4ai_reader import Crawl4AI
19
19
  from .firecrawl_reader import Firecrawl
20
20
  from .jina_url_reader import JinaURLReader
21
+ from .markitdown import MarkItDownLoader
21
22
  from .mineru_extractor import MinerU
22
23
  from .pandas_reader import PandasReader
23
24
  from .scrapegraph_reader import ScrapeGraphAI
@@ -35,5 +36,6 @@ __all__ = [
35
36
  'PandasReader',
36
37
  'MinerU',
37
38
  'Crawl4AI',
39
+ 'MarkItDownLoader',
38
40
  'ScrapeGraphAI',
39
41
  ]
@@ -0,0 +1,204 @@
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
+ import os
15
+ from concurrent.futures import ThreadPoolExecutor, as_completed
16
+ from typing import ClassVar, Dict, List, Optional
17
+
18
+ from camel.logger import get_logger
19
+
20
+ logger = get_logger(__name__)
21
+
22
+
23
+ class MarkItDownLoader:
24
+ r"""MarkitDown convert various file types into Markdown format.
25
+
26
+ Supported Input Formats:
27
+ - PDF
28
+ - Microsoft Office documents:
29
+ - Word (.doc, .docx)
30
+ - Excel (.xls, .xlsx)
31
+ - PowerPoint (.ppt, .pptx)
32
+ - EPUB
33
+ - HTML
34
+ - Images (with EXIF metadata and OCR support)
35
+ - Audio files (with EXIF metadata and speech transcription)
36
+ - Text-based formats:
37
+ - CSV
38
+ - JSON
39
+ - XML
40
+ - ZIP archives (iterates over contents)
41
+ - YouTube URLs (via transcript extraction)
42
+ """
43
+
44
+ SUPPORTED_FORMATS: ClassVar[List[str]] = [
45
+ ".pdf",
46
+ ".doc",
47
+ ".docx",
48
+ ".xls",
49
+ ".xlsx",
50
+ ".ppt",
51
+ ".pptx",
52
+ ".epub",
53
+ ".html",
54
+ ".htm",
55
+ ".jpg",
56
+ ".jpeg",
57
+ ".png",
58
+ ".mp3",
59
+ ".wav",
60
+ ".csv",
61
+ ".json",
62
+ ".xml",
63
+ ".zip",
64
+ ".txt",
65
+ ]
66
+
67
+ def __init__(
68
+ self,
69
+ llm_client: Optional[object] = None,
70
+ llm_model: Optional[str] = None,
71
+ ):
72
+ r"""Initializes the Converter.
73
+
74
+ Args:
75
+ llm_client (Optional[object]): Optional client for LLM integration.
76
+ (default: :obj:`None`)
77
+ llm_model (Optional[str]): Optional model name for the LLM.
78
+ (default: :obj:`None`)
79
+ """
80
+ from markitdown import MarkItDown
81
+
82
+ try:
83
+ self.converter = MarkItDown(
84
+ llm_client=llm_client, llm_model=llm_model
85
+ )
86
+ logger.info("MarkItDownLoader initialized successfully.")
87
+ except Exception as e:
88
+ logger.error(f"Failed to initialize MarkItDown Converter: {e}")
89
+ raise Exception(f"Failed to initialize MarkItDown Converter: {e}")
90
+
91
+ def _validate_format(self, file_path: str) -> bool:
92
+ r"""Validates if the file format is supported.
93
+
94
+ Args:
95
+ file_path (str): Path to the input file.
96
+
97
+ Returns:
98
+ bool: True if the format is supported, False otherwise.
99
+ """
100
+ _, ext = os.path.splitext(file_path)
101
+ return ext.lower() in self.SUPPORTED_FORMATS
102
+
103
+ def convert_file(self, file_path: str) -> str:
104
+ r"""Converts the given file to Markdown format.
105
+
106
+ Args:
107
+ file_path (str): Path to the input file.
108
+
109
+ Returns:
110
+ str: Converted Markdown text.
111
+
112
+ Raises:
113
+ FileNotFoundError: If the specified file does not exist.
114
+ ValueError: If the file format is not supported.
115
+ Exception: For other errors during conversion.
116
+ """
117
+ if not os.path.isfile(file_path):
118
+ logger.error(f"File not found: {file_path}")
119
+ raise FileNotFoundError(f"File not found: {file_path}")
120
+
121
+ if not self._validate_format(file_path):
122
+ logger.error(
123
+ f"Unsupported file format: {file_path}."
124
+ f"Supported formats are "
125
+ f"{MarkItDownLoader.SUPPORTED_FORMATS}"
126
+ )
127
+ raise ValueError(f"Unsupported file format: {file_path}")
128
+
129
+ try:
130
+ logger.info(f"Converting file: {file_path}")
131
+ result = self.converter.convert(file_path)
132
+ logger.info(f"File converted successfully: {file_path}")
133
+ return result.text_content
134
+ except Exception as e:
135
+ logger.error(f"Error converting file '{file_path}': {e}")
136
+ raise Exception(f"Error converting file '{file_path}': {e}")
137
+
138
+ def convert_files(
139
+ self,
140
+ file_paths: List[str],
141
+ parallel: bool = False,
142
+ skip_failed: bool = False,
143
+ ) -> Dict[str, str]:
144
+ r"""Converts multiple files to Markdown format.
145
+
146
+ Args:
147
+ file_paths (List[str]): List of file paths to convert.
148
+ parallel (bool): Whether to process files in parallel.
149
+ (default: :obj:`False`)
150
+ skip_failed (bool): Whether to skip failed files instead
151
+ of including error messages.
152
+ (default: :obj:`False`)
153
+
154
+ Returns:
155
+ Dict[str, str]: Dictionary mapping file paths to their
156
+ converted Markdown text.
157
+
158
+ Raises:
159
+ Exception: For errors during conversion of any file if
160
+ skip_failed is False.
161
+ """
162
+ from tqdm.auto import tqdm
163
+
164
+ converted_files = {}
165
+
166
+ if parallel:
167
+ with ThreadPoolExecutor() as executor:
168
+ future_to_path = {
169
+ executor.submit(self.convert_file, path): path
170
+ for path in file_paths
171
+ }
172
+ for future in tqdm(
173
+ as_completed(future_to_path),
174
+ total=len(file_paths),
175
+ desc="Converting files (parallel)",
176
+ ):
177
+ path = future_to_path[future]
178
+ try:
179
+ converted_files[path] = future.result()
180
+ except Exception as e:
181
+ if skip_failed:
182
+ logger.warning(
183
+ f"Skipping file '{path}' due to error: {e}"
184
+ )
185
+ else:
186
+ logger.error(
187
+ f"Error processing file '{path}': {e}"
188
+ )
189
+ converted_files[path] = f"Error: {e}"
190
+ else:
191
+ for path in tqdm(file_paths, desc="Converting files (sequential)"):
192
+ try:
193
+ logger.info(f"Processing file: {path}")
194
+ converted_files[path] = self.convert_file(path)
195
+ except Exception as e:
196
+ if skip_failed:
197
+ logger.warning(
198
+ f"Skipping file '{path}' due to error: {e}"
199
+ )
200
+ else:
201
+ logger.error(f"Error processing file '{path}': {e}")
202
+ converted_files[path] = f"Error: {e}"
203
+
204
+ return converted_files
@@ -16,7 +16,7 @@ import os
16
16
  from json import JSONDecodeError
17
17
  from typing import Any, Dict, List, Optional, Type, Union
18
18
 
19
- from openai import AsyncOpenAI, AsyncStream, OpenAI, Stream
19
+ from openai import AsyncOpenAI, AsyncStream, BadRequestError, OpenAI, Stream
20
20
  from pydantic import BaseModel, ValidationError
21
21
 
22
22
  from camel.logger import get_logger
@@ -198,7 +198,7 @@ class OpenAICompatibleModel(BaseModelBackend):
198
198
  model=self.model_type,
199
199
  **request_config,
200
200
  )
201
- except (ValidationError, JSONDecodeError) as e:
201
+ except (ValidationError, JSONDecodeError, BadRequestError) as e:
202
202
  logger.warning(
203
203
  f"Format validation error: {e}. "
204
204
  f"Attempting fallback with JSON format."
@@ -237,7 +237,7 @@ class OpenAICompatibleModel(BaseModelBackend):
237
237
  model=self.model_type,
238
238
  **request_config,
239
239
  )
240
- except (ValidationError, JSONDecodeError) as e:
240
+ except (ValidationError, JSONDecodeError, BadRequestError) as e:
241
241
  logger.warning(
242
242
  f"Format validation error: {e}. "
243
243
  f"Attempting fallback with JSON format."
@@ -120,6 +120,9 @@ class RolePlaying:
120
120
  self.task_type = task_type
121
121
  self.task_prompt = task_prompt
122
122
 
123
+ self.task_specify_agent_kwargs = task_specify_agent_kwargs
124
+ self.task_planner_agent_kwargs = task_planner_agent_kwargs
125
+
123
126
  self.specified_task_prompt: Optional[TextPrompt] = None
124
127
  self._init_specified_task_prompt(
125
128
  assistant_role_name,
@@ -680,3 +683,50 @@ class RolePlaying:
680
683
  info=user_response.info,
681
684
  ),
682
685
  )
686
+
687
+ def clone(
688
+ self, task_prompt: str, with_memory: bool = False
689
+ ) -> 'RolePlaying':
690
+ r"""Creates a new instance of RolePlaying with the same configuration.
691
+
692
+ Args:
693
+ task_prompt (str): The task prompt to be used by the new instance.
694
+ with_memory (bool, optional): Whether to copy the memory
695
+ (conversation history) to the new instance. If True, the new
696
+ instance will have the same conversation history. If False,
697
+ the new instance will have a fresh memory.
698
+ (default: :obj:`False`)
699
+
700
+ Returns:
701
+ RolePlaying: A new instance of RolePlaying with the same
702
+ configuration.
703
+ """
704
+
705
+ new_instance = RolePlaying(
706
+ assistant_role_name=self.assistant_agent.role_name,
707
+ user_role_name=self.user_agent.role_name,
708
+ task_prompt=task_prompt,
709
+ with_task_specify=self.with_task_specify,
710
+ task_specify_agent_kwargs=self.task_specify_agent_kwargs,
711
+ with_task_planner=self.with_task_planner,
712
+ task_planner_agent_kwargs=self.task_planner_agent_kwargs,
713
+ with_critic_in_the_loop=False,
714
+ model=self.model,
715
+ task_type=self.task_type,
716
+ )
717
+ tmp_assistant_sys_msg = new_instance.assistant_sys_msg
718
+ new_instance.assistant_agent = self.assistant_agent.clone(with_memory)
719
+ new_instance.assistant_sys_msg = tmp_assistant_sys_msg
720
+ new_instance.assistant_agent._system_message = tmp_assistant_sys_msg
721
+
722
+ tmp_user_sys_msg = new_instance.user_sys_msg
723
+ new_instance.user_agent = self.user_agent.clone(with_memory)
724
+ new_instance.user_sys_msg = tmp_user_sys_msg
725
+ new_instance.user_agent._system_message = tmp_user_sys_msg
726
+
727
+ new_instance.with_critic_in_the_loop = self.with_critic_in_the_loop
728
+ new_instance.critic_sys_msg = self.critic_sys_msg
729
+ if self.critic:
730
+ new_instance.critic = self.critic.clone(with_memory)
731
+
732
+ return new_instance
@@ -38,14 +38,17 @@ class RolePlayingWorker(Worker):
38
38
  description (str): Description of the node.
39
39
  assistant_role_name (str): The role name of the assistant agent.
40
40
  user_role_name (str): The role name of the user agent.
41
- assistant_agent_kwargs (Optional[Dict], optional): The keyword
42
- arguments to initialize the assistant agent in the role playing,
43
- like the model name, etc. Defaults to None.
44
- user_agent_kwargs (Optional[Dict], optional): The keyword arguments to
41
+ assistant_agent_kwargs (Optional[Dict]): The keyword arguments to
42
+ initialize the assistant agent in the role playing, like the model
43
+ name, etc. (default: :obj:`None`)
44
+ user_agent_kwargs (Optional[Dict]): The keyword arguments to
45
45
  initialize the user agent in the role playing, like the model name,
46
- etc. Defaults to None.
47
- chat_turn_limit (int, optional): The maximum number of chat turns in
48
- the role playing. Defaults to 3.
46
+ etc. (default: :obj:`None`)
47
+ summarize_agent_kwargs (Optional[Dict]): The keyword arguments to
48
+ initialize the summarize agent, like the model name, etc.
49
+ (default: :obj:`None`)
50
+ chat_turn_limit (int): The maximum number of chat turns in the role
51
+ playing. (default: :obj:`3`)
49
52
  """
50
53
 
51
54
  def __init__(
@@ -55,9 +58,11 @@ class RolePlayingWorker(Worker):
55
58
  user_role_name: str,
56
59
  assistant_agent_kwargs: Optional[Dict] = None,
57
60
  user_agent_kwargs: Optional[Dict] = None,
61
+ summarize_agent_kwargs: Optional[Dict] = None,
58
62
  chat_turn_limit: int = 3,
59
63
  ) -> None:
60
64
  super().__init__(description)
65
+ self.summarize_agent_kwargs = summarize_agent_kwargs
61
66
  summ_sys_msg = BaseMessage.make_assistant_message(
62
67
  role_name="Summarizer",
63
68
  content="You are a good summarizer. You will be presented with "
@@ -65,7 +70,11 @@ class RolePlayingWorker(Worker):
65
70
  "are trying to solve a task. Your job is summarizing the result "
66
71
  "of the task based on the chat history.",
67
72
  )
68
- self.summarize_agent = ChatAgent(summ_sys_msg)
73
+ summarize_agent_dict = (
74
+ summarize_agent_kwargs if summarize_agent_kwargs else {}
75
+ )
76
+ summarize_agent_dict['system_message'] = summ_sys_msg
77
+ self.summarize_agent = ChatAgent(**summarize_agent_dict)
69
78
  self.chat_turn_limit = chat_turn_limit
70
79
  self.assistant_role_name = assistant_role_name
71
80
  self.user_role_name = user_role_name
@@ -41,7 +41,7 @@ from camel.societies.workforce.utils import (
41
41
  )
42
42
  from camel.societies.workforce.worker import Worker
43
43
  from camel.tasks.task import Task, TaskState
44
- from camel.toolkits import GoogleMapsToolkit, SearchToolkit, WeatherToolkit
44
+ from camel.toolkits import CodeExecutionToolkit, SearchToolkit, ThinkingToolkit
45
45
  from camel.types import ModelPlatformType, ModelType
46
46
 
47
47
  logger = get_logger(__name__)
@@ -202,6 +202,7 @@ class Workforce(BaseNode):
202
202
  user_role_name: str,
203
203
  assistant_agent_kwargs: Optional[Dict] = None,
204
204
  user_agent_kwargs: Optional[Dict] = None,
205
+ summarize_agent_kwargs: Optional[Dict] = None,
205
206
  chat_turn_limit: int = 3,
206
207
  ) -> Workforce:
207
208
  r"""Add a worker node to the workforce that uses `RolePlaying` system.
@@ -210,25 +211,29 @@ class Workforce(BaseNode):
210
211
  description (str): Description of the node.
211
212
  assistant_role_name (str): The role name of the assistant agent.
212
213
  user_role_name (str): The role name of the user agent.
213
- assistant_agent_kwargs (Optional[Dict], optional): The keyword
214
- arguments to initialize the assistant agent in the role
215
- playing, like the model name, etc. Defaults to `None`.
216
- user_agent_kwargs (Optional[Dict], optional): The keyword arguments
217
- to initialize the user agent in the role playing, like the
218
- model name, etc. Defaults to `None`.
219
- chat_turn_limit (int, optional): The maximum number of chat turns
220
- in the role playing. Defaults to 3.
214
+ assistant_agent_kwargs (Optional[Dict]): The keyword arguments to
215
+ initialize the assistant agent in the role playing, like the
216
+ model name, etc. (default: :obj:`None`)
217
+ user_agent_kwargs (Optional[Dict]): The keyword arguments to
218
+ initialize the user agent in the role playing, like the
219
+ model name, etc. (default: :obj:`None`)
220
+ summarize_agent_kwargs (Optional[Dict]): The keyword arguments to
221
+ initialize the summarize agent, like the model name, etc.
222
+ (default: :obj:`None`)
223
+ chat_turn_limit (int): The maximum number of chat turns in the
224
+ role playing. (default: :obj:`3`)
221
225
 
222
226
  Returns:
223
227
  Workforce: The workforce node itself.
224
228
  """
225
229
  worker_node = RolePlayingWorker(
226
- description,
227
- assistant_role_name,
228
- user_role_name,
229
- assistant_agent_kwargs,
230
- user_agent_kwargs,
231
- chat_turn_limit,
230
+ description=description,
231
+ assistant_role_name=assistant_role_name,
232
+ user_role_name=user_role_name,
233
+ assistant_agent_kwargs=assistant_agent_kwargs,
234
+ user_agent_kwargs=user_agent_kwargs,
235
+ summarize_agent_kwargs=summarize_agent_kwargs,
236
+ chat_turn_limit=chat_turn_limit,
232
237
  )
233
238
  self._children.append(worker_node)
234
239
  return self
@@ -370,9 +375,9 @@ class Workforce(BaseNode):
370
375
 
371
376
  # Default tools for a new agent
372
377
  function_list = [
373
- *SearchToolkit().get_tools(),
374
- *WeatherToolkit().get_tools(),
375
- *GoogleMapsToolkit().get_tools(),
378
+ SearchToolkit().search_duckduckgo,
379
+ *CodeExecutionToolkit().get_tools(),
380
+ *ThinkingToolkit().get_tools(),
376
381
  ]
377
382
 
378
383
  model_config_dict = ChatGPTConfig(
@@ -501,3 +506,54 @@ class Workforce(BaseNode):
501
506
  for child_task in self._child_listening_tasks:
502
507
  child_task.cancel()
503
508
  self._running = False
509
+
510
+ def clone(self, with_memory: bool = False) -> 'Workforce':
511
+ r"""Creates a new instance of Workforce with the same configuration.
512
+
513
+ Args:
514
+ with_memory (bool, optional): Whether to copy the memory
515
+ (conversation history) to the new instance. If True, the new
516
+ instance will have the same conversation history. If False,
517
+ the new instance will have a fresh memory.
518
+ (default: :obj:`False`)
519
+
520
+ Returns:
521
+ Workforce: A new instance of Workforce with the same configuration.
522
+ """
523
+
524
+ # Create a new instance with the same configuration
525
+ new_instance = Workforce(
526
+ description=self.description,
527
+ coordinator_agent_kwargs={},
528
+ task_agent_kwargs={},
529
+ new_worker_agent_kwargs=self.new_worker_agent_kwargs,
530
+ )
531
+
532
+ new_instance.task_agent = self.task_agent.clone(with_memory)
533
+ new_instance.coordinator_agent = self.coordinator_agent.clone(
534
+ with_memory
535
+ )
536
+
537
+ for child in self._children:
538
+ if isinstance(child, SingleAgentWorker):
539
+ cloned_worker = child.worker.clone(with_memory)
540
+ new_instance.add_single_agent_worker(
541
+ child.description, cloned_worker
542
+ )
543
+ elif isinstance(child, RolePlayingWorker):
544
+ new_instance.add_role_playing_worker(
545
+ child.description,
546
+ child.assistant_role_name,
547
+ child.user_role_name,
548
+ child.assistant_agent_kwargs,
549
+ child.user_agent_kwargs,
550
+ child.summarize_agent_kwargs,
551
+ child.chat_turn_limit,
552
+ )
553
+ elif isinstance(child, Workforce):
554
+ new_instance.add_workforce(child.clone(with_memory))
555
+ else:
556
+ logger.warning(f"{type(child)} is not being cloned.")
557
+ continue
558
+
559
+ return new_instance
@@ -59,6 +59,7 @@ from .video_analysis_toolkit import VideoAnalysisToolkit
59
59
  from .image_analysis_toolkit import ImageAnalysisToolkit
60
60
  from .mcp_toolkit import MCPToolkit
61
61
  from .browser_toolkit import BrowserToolkit
62
+ from .async_browser_toolkit import AsyncBrowserToolkit
62
63
  from .file_write_toolkit import FileWriteToolkit
63
64
  from .terminal_toolkit import TerminalToolkit
64
65
  from .pubmed_toolkit import PubMedToolkit
@@ -72,6 +73,7 @@ from .pulse_mcp_search_toolkit import PulseMCPSearchToolkit
72
73
  from .klavis_toolkit import KlavisToolkit
73
74
  from .aci_toolkit import ACIToolkit
74
75
  from .playwright_mcp_toolkit import PlaywrightMCPToolkit
76
+ from .wolfram_alpha_toolkit import WolframAlphaToolkit
75
77
 
76
78
 
77
79
  __all__ = [
@@ -119,6 +121,7 @@ __all__ = [
119
121
  'VideoAnalysisToolkit',
120
122
  'ImageAnalysisToolkit',
121
123
  'BrowserToolkit',
124
+ 'AsyncBrowserToolkit',
122
125
  'FileWriteToolkit',
123
126
  'TerminalToolkit',
124
127
  'PubMedToolkit',
@@ -132,4 +135,5 @@ __all__ = [
132
135
  'KlavisToolkit',
133
136
  'ACIToolkit',
134
137
  'PlaywrightMCPToolkit',
138
+ 'WolframAlphaToolkit',
135
139
  ]