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/__init__.py +1 -2
- camel/agents/chat_agent.py +136 -9
- camel/agents/critic_agent.py +73 -8
- camel/agents/mcp_agent.py +296 -83
- camel/benchmarks/__init__.py +2 -0
- camel/benchmarks/browsecomp.py +854 -0
- camel/human.py +14 -0
- camel/loaders/__init__.py +2 -0
- camel/loaders/markitdown.py +204 -0
- camel/models/openai_compatible_model.py +3 -3
- camel/societies/role_playing.py +50 -0
- camel/societies/workforce/role_playing_worker.py +17 -8
- camel/societies/workforce/workforce.py +74 -18
- camel/toolkits/__init__.py +4 -0
- camel/toolkits/async_browser_toolkit.py +1800 -0
- camel/toolkits/base.py +12 -1
- camel/toolkits/browser_toolkit.py +19 -3
- camel/toolkits/excel_toolkit.py +3 -2
- camel/toolkits/function_tool.py +1 -1
- camel/toolkits/mcp_toolkit.py +134 -6
- camel/toolkits/search_toolkit.py +0 -169
- camel/toolkits/wolfram_alpha_toolkit.py +237 -0
- camel/types/__init__.py +10 -0
- camel/types/enums.py +3 -3
- camel/types/mcp_registries.py +157 -0
- {camel_ai-0.2.57.dist-info → camel_ai-0.2.59.dist-info}/METADATA +4 -2
- {camel_ai-0.2.57.dist-info → camel_ai-0.2.59.dist-info}/RECORD +29 -24
- {camel_ai-0.2.57.dist-info → camel_ai-0.2.59.dist-info}/WHEEL +0 -0
- {camel_ai-0.2.57.dist-info → camel_ai-0.2.59.dist-info}/licenses/LICENSE +0 -0
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."
|
camel/societies/role_playing.py
CHANGED
|
@@ -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]
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
user_agent_kwargs (Optional[Dict]
|
|
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.
|
|
47
|
-
|
|
48
|
-
the
|
|
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
|
-
|
|
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
|
|
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]
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
user_agent_kwargs (Optional[Dict]
|
|
217
|
-
|
|
218
|
-
model name, etc.
|
|
219
|
-
|
|
220
|
-
|
|
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
|
-
|
|
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
|
-
|
|
374
|
-
*
|
|
375
|
-
*
|
|
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
|
camel/toolkits/__init__.py
CHANGED
|
@@ -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
|
]
|