camel-ai 0.2.0__py3-none-any.whl → 0.2.1__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 -1
- camel/agents/chat_agent.py +2 -0
- camel/loaders/firecrawl_reader.py +11 -43
- camel/models/mistral_model.py +1 -1
- camel/tasks/task.py +11 -4
- camel/tasks/task_prompt.py +4 -0
- camel/utils/commons.py +8 -1
- camel/workforce/__init__.py +6 -6
- camel/workforce/base.py +9 -5
- camel/workforce/prompts.py +175 -0
- camel/workforce/role_playing_worker.py +181 -0
- camel/workforce/{single_agent_node.py → single_agent_worker.py} +49 -22
- camel/workforce/task_channel.py +3 -5
- camel/workforce/utils.py +20 -50
- camel/workforce/{worker_node.py → worker.py} +15 -12
- camel/workforce/workforce.py +456 -19
- {camel_ai-0.2.0.dist-info → camel_ai-0.2.1.dist-info}/METADATA +2 -2
- {camel_ai-0.2.0.dist-info → camel_ai-0.2.1.dist-info}/RECORD +19 -20
- camel/workforce/manager_node.py +0 -299
- camel/workforce/role_playing_node.py +0 -168
- camel/workforce/workforce_prompt.py +0 -125
- {camel_ai-0.2.0.dist-info → camel_ai-0.2.1.dist-info}/WHEEL +0 -0
camel/__init__.py
CHANGED
camel/agents/chat_agent.py
CHANGED
|
@@ -622,9 +622,11 @@ class ChatAgent(BaseAgent):
|
|
|
622
622
|
|
|
623
623
|
# Replace the original tools with the structuring function
|
|
624
624
|
self.func_dict = {func.get_function_name(): func.func}
|
|
625
|
+
self.model_backend.model_config_dict = original_model_dict.copy()
|
|
625
626
|
self.model_backend.model_config_dict["tools"] = [
|
|
626
627
|
func.get_openai_tool_schema()
|
|
627
628
|
]
|
|
629
|
+
self.model_backend.model_config_dict["tool_choice"] = "required"
|
|
628
630
|
|
|
629
631
|
openai_messages, num_tokens = self.memory.get_context()
|
|
630
632
|
(
|
|
@@ -49,7 +49,6 @@ class Firecrawl:
|
|
|
49
49
|
self,
|
|
50
50
|
url: str,
|
|
51
51
|
params: Optional[Dict[str, Any]] = None,
|
|
52
|
-
wait_until_done: bool = True,
|
|
53
52
|
**kwargs: Any,
|
|
54
53
|
) -> Any:
|
|
55
54
|
r"""Crawl a URL and all accessible subpages. Customize the crawl by
|
|
@@ -60,14 +59,12 @@ class Firecrawl:
|
|
|
60
59
|
url (str): The URL to crawl.
|
|
61
60
|
params (Optional[Dict[str, Any]]): Additional parameters for the
|
|
62
61
|
crawl request. Defaults to `None`.
|
|
63
|
-
wait_until_done (bool): Whether to wait until the crawl job is
|
|
64
|
-
completed. Defaults to `True`.
|
|
65
62
|
**kwargs (Any): Additional keyword arguments, such as
|
|
66
|
-
`poll_interval`, `idempotency_key
|
|
63
|
+
`poll_interval`, `idempotency_key`.
|
|
67
64
|
|
|
68
65
|
Returns:
|
|
69
|
-
Any: The
|
|
70
|
-
|
|
66
|
+
Any: The crawl job ID or the crawl results if waiting until
|
|
67
|
+
completion.
|
|
71
68
|
|
|
72
69
|
Raises:
|
|
73
70
|
RuntimeError: If the crawling process fails.
|
|
@@ -78,13 +75,8 @@ class Firecrawl:
|
|
|
78
75
|
url=url,
|
|
79
76
|
params=params,
|
|
80
77
|
**kwargs,
|
|
81
|
-
wait_until_done=wait_until_done,
|
|
82
|
-
)
|
|
83
|
-
return (
|
|
84
|
-
crawl_response
|
|
85
|
-
if wait_until_done
|
|
86
|
-
else crawl_response.get("jobId")
|
|
87
78
|
)
|
|
79
|
+
return crawl_response
|
|
88
80
|
except Exception as e:
|
|
89
81
|
raise RuntimeError(f"Failed to crawl the URL: {e}")
|
|
90
82
|
|
|
@@ -103,7 +95,10 @@ class Firecrawl:
|
|
|
103
95
|
"""
|
|
104
96
|
|
|
105
97
|
try:
|
|
106
|
-
crawl_result = self.app.crawl_url(
|
|
98
|
+
crawl_result = self.app.crawl_url(
|
|
99
|
+
url,
|
|
100
|
+
{'formats': ['markdown']},
|
|
101
|
+
)
|
|
107
102
|
if not isinstance(crawl_result, list):
|
|
108
103
|
raise ValueError("Unexpected response format")
|
|
109
104
|
markdown_contents = [
|
|
@@ -180,41 +175,14 @@ class Firecrawl:
|
|
|
180
175
|
data = self.app.scrape_url(
|
|
181
176
|
url,
|
|
182
177
|
{
|
|
183
|
-
'
|
|
184
|
-
|
|
185
|
-
"extractionPrompt": "Based on the information on "
|
|
186
|
-
"the page, extract the information from the schema.",
|
|
187
|
-
'extractionSchema': output_schema.model_json_schema(),
|
|
188
|
-
},
|
|
189
|
-
'pageOptions': {'onlyMainContent': True},
|
|
178
|
+
'formats': ['extract'],
|
|
179
|
+
'extract': {'schema': output_schema.model_json_schema()},
|
|
190
180
|
},
|
|
191
181
|
)
|
|
192
|
-
return data.get("
|
|
182
|
+
return data.get("extract", {})
|
|
193
183
|
except Exception as e:
|
|
194
184
|
raise RuntimeError(f"Failed to perform structured scrape: {e}")
|
|
195
185
|
|
|
196
|
-
def tidy_scrape(self, url: str) -> str:
|
|
197
|
-
r"""Only return the main content of the page, excluding headers,
|
|
198
|
-
navigation bars, footers, etc. in Markdown format.
|
|
199
|
-
|
|
200
|
-
Args:
|
|
201
|
-
url (str): The URL to read.
|
|
202
|
-
|
|
203
|
-
Returns:
|
|
204
|
-
str: The markdown content of the URL.
|
|
205
|
-
|
|
206
|
-
Raises:
|
|
207
|
-
RuntimeError: If the scrape process fails.
|
|
208
|
-
"""
|
|
209
|
-
|
|
210
|
-
try:
|
|
211
|
-
scrape_result = self.app.scrape_url(
|
|
212
|
-
url, {'pageOptions': {'onlyMainContent': True}}
|
|
213
|
-
)
|
|
214
|
-
return scrape_result.get("markdown", "")
|
|
215
|
-
except Exception as e:
|
|
216
|
-
raise RuntimeError(f"Failed to perform tidy scrape: {e}")
|
|
217
|
-
|
|
218
186
|
def map_site(
|
|
219
187
|
self, url: str, params: Optional[Dict[str, Any]] = None
|
|
220
188
|
) -> list:
|
camel/models/mistral_model.py
CHANGED
|
@@ -93,7 +93,7 @@ class MistralModel(BaseModelBackend):
|
|
|
93
93
|
"name": tool_call.function.name, # type: ignore[union-attr]
|
|
94
94
|
"arguments": tool_call.function.arguments, # type: ignore[union-attr]
|
|
95
95
|
},
|
|
96
|
-
type=tool_call.
|
|
96
|
+
type=tool_call.type, # type: ignore[union-attr]
|
|
97
97
|
)
|
|
98
98
|
for tool_call in response.choices[0].message.tool_calls
|
|
99
99
|
]
|
camel/tasks/task.py
CHANGED
|
@@ -93,6 +93,10 @@ class Task(BaseModel):
|
|
|
93
93
|
|
|
94
94
|
result: Optional[str] = ""
|
|
95
95
|
|
|
96
|
+
failure_count: int = 0
|
|
97
|
+
|
|
98
|
+
additional_info: Optional[str] = None
|
|
99
|
+
|
|
96
100
|
@classmethod
|
|
97
101
|
def from_message(cls, message: BaseMessage) -> "Task":
|
|
98
102
|
r"""Create a task from a message.
|
|
@@ -193,7 +197,7 @@ class Task(BaseModel):
|
|
|
193
197
|
def decompose(
|
|
194
198
|
self,
|
|
195
199
|
agent: ChatAgent,
|
|
196
|
-
|
|
200
|
+
prompt: Optional[str] = None,
|
|
197
201
|
task_parser: Callable[[str, str], List["Task"]] = parse_response,
|
|
198
202
|
) -> List["Task"]:
|
|
199
203
|
r"""Decompose a task to a list of sub-tasks. It can be used for data
|
|
@@ -201,8 +205,8 @@ class Task(BaseModel):
|
|
|
201
205
|
|
|
202
206
|
Args:
|
|
203
207
|
agent (ChatAgent): An agent that used to decompose the task.
|
|
204
|
-
|
|
205
|
-
|
|
208
|
+
prompt (str, optional): A prompt to decompose the task. If not
|
|
209
|
+
provided, the default prompt will be used.
|
|
206
210
|
task_parser (Callable[[str, str], List[Task]], optional): A
|
|
207
211
|
function to extract Task from response. If not provided,
|
|
208
212
|
the default parse_response will be used.
|
|
@@ -212,7 +216,7 @@ class Task(BaseModel):
|
|
|
212
216
|
"""
|
|
213
217
|
|
|
214
218
|
role_name = agent.role_name
|
|
215
|
-
content =
|
|
219
|
+
content = prompt or TASK_DECOMPOSE_PROMPT.format(
|
|
216
220
|
role_name=role_name,
|
|
217
221
|
content=self.content,
|
|
218
222
|
)
|
|
@@ -221,6 +225,8 @@ class Task(BaseModel):
|
|
|
221
225
|
)
|
|
222
226
|
response = agent.step(msg)
|
|
223
227
|
tasks = task_parser(response.msg.content, self.id)
|
|
228
|
+
for task in tasks:
|
|
229
|
+
task.additional_info = self.additional_info
|
|
224
230
|
return tasks
|
|
225
231
|
|
|
226
232
|
def compose(
|
|
@@ -248,6 +254,7 @@ class Task(BaseModel):
|
|
|
248
254
|
content = template.format(
|
|
249
255
|
role_name=role_name,
|
|
250
256
|
content=self.content,
|
|
257
|
+
additional_info=self.additional_info,
|
|
251
258
|
other_results=sub_tasks_result,
|
|
252
259
|
)
|
|
253
260
|
msg = BaseMessage.make_user_message(
|
camel/tasks/task_prompt.py
CHANGED
camel/utils/commons.py
CHANGED
|
@@ -381,10 +381,17 @@ def json_to_function_code(json_obj: Dict) -> str:
|
|
|
381
381
|
docstring_args = []
|
|
382
382
|
return_keys = []
|
|
383
383
|
|
|
384
|
+
prop_to_python = {
|
|
385
|
+
'string': 'str',
|
|
386
|
+
'number': 'float',
|
|
387
|
+
'integer': 'int',
|
|
388
|
+
'boolean': 'bool',
|
|
389
|
+
}
|
|
390
|
+
|
|
384
391
|
for prop in required:
|
|
385
392
|
description = properties[prop]['description']
|
|
386
393
|
prop_type = properties[prop]['type']
|
|
387
|
-
python_type =
|
|
394
|
+
python_type = prop_to_python.get(prop_type, prop_type)
|
|
388
395
|
args.append(f"{prop}: {python_type}")
|
|
389
396
|
docstring_args.append(
|
|
390
397
|
f" {prop} ({python_type}): {description}."
|
camel/workforce/__init__.py
CHANGED
|
@@ -12,12 +12,12 @@
|
|
|
12
12
|
# limitations under the License.
|
|
13
13
|
# =========== Copyright 2023 @ CAMEL-AI.org. All Rights Reserved. ===========
|
|
14
14
|
|
|
15
|
-
from .
|
|
16
|
-
from .
|
|
17
|
-
from .
|
|
15
|
+
from .role_playing_worker import RolePlayingWorker
|
|
16
|
+
from .single_agent_worker import SingleAgentWorker
|
|
17
|
+
from .workforce import Workforce
|
|
18
18
|
|
|
19
19
|
__all__ = [
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
20
|
+
"Workforce",
|
|
21
|
+
"SingleAgentWorker",
|
|
22
|
+
"RolePlayingWorker",
|
|
23
23
|
]
|
camel/workforce/base.py
CHANGED
|
@@ -15,36 +15,40 @@ from abc import ABC, abstractmethod
|
|
|
15
15
|
from typing import Any
|
|
16
16
|
|
|
17
17
|
from camel.workforce.task_channel import TaskChannel
|
|
18
|
+
from camel.workforce.utils import check_if_running
|
|
18
19
|
|
|
19
20
|
|
|
20
21
|
class BaseNode(ABC):
|
|
21
22
|
def __init__(self, description: str) -> None:
|
|
22
23
|
self.node_id = str(id(self))
|
|
23
24
|
self.description = description
|
|
24
|
-
# every node is initialized to use its own channel
|
|
25
25
|
self._channel: TaskChannel = TaskChannel()
|
|
26
26
|
self._running = False
|
|
27
27
|
|
|
28
|
+
@check_if_running(False)
|
|
28
29
|
def reset(self, *args: Any, **kwargs: Any) -> Any:
|
|
29
30
|
"""Resets the node to its initial state."""
|
|
30
|
-
|
|
31
|
+
self._channel = TaskChannel()
|
|
32
|
+
self._running = False
|
|
31
33
|
|
|
32
34
|
@abstractmethod
|
|
33
35
|
def set_channel(self, channel: TaskChannel):
|
|
34
36
|
r"""Sets the channel for the node."""
|
|
37
|
+
pass
|
|
35
38
|
|
|
36
39
|
@abstractmethod
|
|
37
40
|
async def _listen_to_channel(self):
|
|
38
41
|
r"""Listens to the channel and handle tasks. This method should be
|
|
39
42
|
the main loop for the node.
|
|
40
43
|
"""
|
|
44
|
+
pass
|
|
41
45
|
|
|
42
46
|
@abstractmethod
|
|
43
47
|
async def start(self):
|
|
44
48
|
r"""Start the node."""
|
|
49
|
+
pass
|
|
45
50
|
|
|
46
51
|
@abstractmethod
|
|
47
52
|
def stop(self):
|
|
48
|
-
r"""
|
|
49
|
-
|
|
50
|
-
"""
|
|
53
|
+
r"""Stop the node."""
|
|
54
|
+
pass
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# =========== Copyright 2023 @ 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 @ CAMEL-AI.org. All Rights Reserved. ===========
|
|
14
|
+
from camel.prompts import TextPrompt
|
|
15
|
+
|
|
16
|
+
# ruff: noqa: E501
|
|
17
|
+
CREATE_NODE_PROMPT = TextPrompt(
|
|
18
|
+
"""You need to use the given information to create a new worker node that contains a single agent for solving the category of tasks of the given one.
|
|
19
|
+
The content of the given task is:
|
|
20
|
+
|
|
21
|
+
==============================
|
|
22
|
+
{content}
|
|
23
|
+
==============================
|
|
24
|
+
|
|
25
|
+
Here are some additional information about the task:
|
|
26
|
+
|
|
27
|
+
==============================
|
|
28
|
+
{additional_info}
|
|
29
|
+
==============================
|
|
30
|
+
|
|
31
|
+
Following is the information of the existing worker nodes. The format is <ID>:<description>:<additional_info>.
|
|
32
|
+
|
|
33
|
+
==============================
|
|
34
|
+
{child_nodes_info}
|
|
35
|
+
==============================
|
|
36
|
+
|
|
37
|
+
You must return the following information:
|
|
38
|
+
1. The role of the agent working in the worker node, e.g. "programmer", "researcher", "product owner".
|
|
39
|
+
2. The system message that will be sent to the agent in the node.
|
|
40
|
+
3. The description of the new worker node itself.
|
|
41
|
+
|
|
42
|
+
You should ensure that the node created is capable of solving all the tasks in the same category as the given one, don't make it too specific.
|
|
43
|
+
Also, there should be no big overlap between the new work node and the existing ones.
|
|
44
|
+
The information returned should be concise and clear.
|
|
45
|
+
"""
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
ASSIGN_TASK_PROMPT = TextPrompt(
|
|
49
|
+
"""You need to assign the task to a worker node.
|
|
50
|
+
The content of the task is:
|
|
51
|
+
|
|
52
|
+
==============================
|
|
53
|
+
{content}
|
|
54
|
+
==============================
|
|
55
|
+
|
|
56
|
+
Here are some additional information about the task:
|
|
57
|
+
|
|
58
|
+
==============================
|
|
59
|
+
{additional_info}
|
|
60
|
+
==============================
|
|
61
|
+
|
|
62
|
+
Following is the information of the existing worker nodes. The format is <ID>:<description>:<additional_info>.
|
|
63
|
+
|
|
64
|
+
==============================
|
|
65
|
+
{child_nodes_info}
|
|
66
|
+
==============================
|
|
67
|
+
|
|
68
|
+
You must return the ID of the worker node that you think is most capable of doing the task.
|
|
69
|
+
"""
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
PROCESS_TASK_PROMPT = TextPrompt(
|
|
73
|
+
"""You need to process one given task.
|
|
74
|
+
Here are results of some prerequisite tasks that you can refer to:
|
|
75
|
+
|
|
76
|
+
==============================
|
|
77
|
+
{dependency_tasks_info}
|
|
78
|
+
==============================
|
|
79
|
+
|
|
80
|
+
The content of the task that you need to do is:
|
|
81
|
+
|
|
82
|
+
==============================
|
|
83
|
+
{content}
|
|
84
|
+
==============================
|
|
85
|
+
|
|
86
|
+
Here are some additional information about the task:
|
|
87
|
+
|
|
88
|
+
==============================
|
|
89
|
+
{additional_info}
|
|
90
|
+
==============================
|
|
91
|
+
|
|
92
|
+
You are asked to return the result of the given task.
|
|
93
|
+
However, if you think you can't finish the task, you MUST set the fail flag and leave the result empty.
|
|
94
|
+
"""
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
ROLEPLAY_PROCESS_TASK_PROMPT = TextPrompt(
|
|
99
|
+
"""You need to process the task. It is recommended that tools be actively called when needed.
|
|
100
|
+
Here are results of some prerequisite tasks that you can refer to:
|
|
101
|
+
|
|
102
|
+
==============================
|
|
103
|
+
{dependency_task_info}
|
|
104
|
+
==============================
|
|
105
|
+
|
|
106
|
+
The content of the task that you need to do is:
|
|
107
|
+
|
|
108
|
+
==============================
|
|
109
|
+
{content}
|
|
110
|
+
==============================
|
|
111
|
+
|
|
112
|
+
Here are some additional information about the task:
|
|
113
|
+
|
|
114
|
+
==============================
|
|
115
|
+
{additional_info}
|
|
116
|
+
==============================
|
|
117
|
+
|
|
118
|
+
You must return the result of the given task.
|
|
119
|
+
"""
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
ROLEPLAY_SUMMARIZE_PROMPT = TextPrompt(
|
|
123
|
+
"""For this scenario, the roles of the user is {user_role} and role of the assistant is {assistant_role}.
|
|
124
|
+
Here is the content of the task they are trying to solve:
|
|
125
|
+
|
|
126
|
+
==============================
|
|
127
|
+
{task_content}
|
|
128
|
+
==============================
|
|
129
|
+
|
|
130
|
+
Here are some additional information about the task:
|
|
131
|
+
|
|
132
|
+
==============================
|
|
133
|
+
{additional_info}
|
|
134
|
+
==============================
|
|
135
|
+
|
|
136
|
+
Here is their chat history on the task:
|
|
137
|
+
|
|
138
|
+
==============================
|
|
139
|
+
{chat_history}
|
|
140
|
+
==============================
|
|
141
|
+
|
|
142
|
+
Now you should summarize the scenario and return the result of the task.
|
|
143
|
+
However, if you think they didn't finish the task, you MUST set the fail flag and leave the result empty.
|
|
144
|
+
"""
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
WF_TASK_DECOMPOSE_PROMPT = r"""You need to split the given task into
|
|
148
|
+
subtasks according to the workers available in the group.
|
|
149
|
+
The content of the task is:
|
|
150
|
+
|
|
151
|
+
==============================
|
|
152
|
+
{content}
|
|
153
|
+
==============================
|
|
154
|
+
|
|
155
|
+
There are some additional information about the task:
|
|
156
|
+
|
|
157
|
+
==============================
|
|
158
|
+
{additional_info}
|
|
159
|
+
==============================
|
|
160
|
+
|
|
161
|
+
Following are the available workers, given in the format <ID>: <description>.
|
|
162
|
+
|
|
163
|
+
==============================
|
|
164
|
+
{child_nodes_info}
|
|
165
|
+
==============================
|
|
166
|
+
|
|
167
|
+
You must return the subtasks in the format of a numbered list within <tasks> tags, as shown below:
|
|
168
|
+
|
|
169
|
+
<tasks>
|
|
170
|
+
<task>Subtask 1</task>
|
|
171
|
+
<task>Subtask 2</task>
|
|
172
|
+
</tasks>
|
|
173
|
+
|
|
174
|
+
Though it's not a must, you should try your best effort to make each subtask achievable for a worker. The tasks should be clear and concise.
|
|
175
|
+
"""
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# =========== Copyright 2023 @ 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 @ CAMEL-AI.org. All Rights Reserved. ===========
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import ast
|
|
17
|
+
from typing import Dict, List, Optional
|
|
18
|
+
|
|
19
|
+
from colorama import Fore
|
|
20
|
+
|
|
21
|
+
from camel.agents.chat_agent import ChatAgent
|
|
22
|
+
from camel.messages.base import BaseMessage
|
|
23
|
+
from camel.societies import RolePlaying
|
|
24
|
+
from camel.tasks.task import Task, TaskState
|
|
25
|
+
from camel.utils import print_text_animated
|
|
26
|
+
from camel.workforce.prompts import (
|
|
27
|
+
ROLEPLAY_PROCESS_TASK_PROMPT,
|
|
28
|
+
ROLEPLAY_SUMMARIZE_PROMPT,
|
|
29
|
+
)
|
|
30
|
+
from camel.workforce.utils import TaskResult
|
|
31
|
+
from camel.workforce.worker import Worker
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class RolePlayingWorker(Worker):
|
|
35
|
+
r"""A worker node that contains a role playing.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
description (str): Description of the node.
|
|
39
|
+
assistant_role_name (str): The role name of the assistant agent.
|
|
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
|
|
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.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
def __init__(
|
|
52
|
+
self,
|
|
53
|
+
description: str,
|
|
54
|
+
assistant_role_name: str,
|
|
55
|
+
user_role_name: str,
|
|
56
|
+
assistant_agent_kwargs: Optional[Dict] = None,
|
|
57
|
+
user_agent_kwargs: Optional[Dict] = None,
|
|
58
|
+
chat_turn_limit: int = 3,
|
|
59
|
+
) -> None:
|
|
60
|
+
super().__init__(description)
|
|
61
|
+
summ_sys_msg = BaseMessage.make_assistant_message(
|
|
62
|
+
role_name="Summarizer",
|
|
63
|
+
content="You are a good summarizer. You will be presented with "
|
|
64
|
+
"scenarios where an assistant and a user with specific roles "
|
|
65
|
+
"are trying to solve a task. Your job is summarizing the result "
|
|
66
|
+
"of the task based on the chat history.",
|
|
67
|
+
)
|
|
68
|
+
self.summarize_agent = ChatAgent(summ_sys_msg)
|
|
69
|
+
self.chat_turn_limit = chat_turn_limit
|
|
70
|
+
self.assistant_role_name = assistant_role_name
|
|
71
|
+
self.user_role_name = user_role_name
|
|
72
|
+
self.assistant_agent_kwargs = assistant_agent_kwargs
|
|
73
|
+
self.user_agent_kwargs = user_agent_kwargs
|
|
74
|
+
|
|
75
|
+
async def _process_task(
|
|
76
|
+
self, task: Task, dependencies: List[Task]
|
|
77
|
+
) -> TaskState:
|
|
78
|
+
r"""Processes a task leveraging its dependencies through role-playing.
|
|
79
|
+
|
|
80
|
+
This method orchestrates a role-playing session between an AI
|
|
81
|
+
assistant and an AI user to process a given task. It initiates with a
|
|
82
|
+
generated prompt based on the task and its dependencies, conducts a
|
|
83
|
+
dialogue up to a specified chat turn limit, and then summarizes the
|
|
84
|
+
dialogue to determine the task's outcome.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
task (Task): The task object to be processed, containing necessary
|
|
88
|
+
details like content and type.
|
|
89
|
+
dependencies (List[Task]): A list of task objects that the current
|
|
90
|
+
task depends on.
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
TaskState: `TaskState.DONE` if processed successfully, otherwise
|
|
94
|
+
`TaskState.FAILED`.
|
|
95
|
+
"""
|
|
96
|
+
dependency_tasks_info = self._get_dep_tasks_info(dependencies)
|
|
97
|
+
prompt = ROLEPLAY_PROCESS_TASK_PROMPT.format(
|
|
98
|
+
content=task.content,
|
|
99
|
+
dependency_task_info=dependency_tasks_info,
|
|
100
|
+
additional_info=task.additional_info,
|
|
101
|
+
)
|
|
102
|
+
role_play_session = RolePlaying(
|
|
103
|
+
assistant_role_name=self.assistant_role_name,
|
|
104
|
+
user_role_name=self.user_role_name,
|
|
105
|
+
assistant_agent_kwargs=self.assistant_agent_kwargs,
|
|
106
|
+
user_agent_kwargs=self.user_agent_kwargs,
|
|
107
|
+
task_prompt=prompt,
|
|
108
|
+
with_task_specify=False,
|
|
109
|
+
)
|
|
110
|
+
n = 0
|
|
111
|
+
input_msg = role_play_session.init_chat()
|
|
112
|
+
chat_history = []
|
|
113
|
+
while n < self.chat_turn_limit:
|
|
114
|
+
n += 1
|
|
115
|
+
assistant_response, user_response = role_play_session.step(
|
|
116
|
+
input_msg
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
if assistant_response.terminated:
|
|
120
|
+
reason = assistant_response.info['termination_reasons']
|
|
121
|
+
print(
|
|
122
|
+
f"{Fore.GREEN}AI Assistant terminated. Reason: "
|
|
123
|
+
f"{reason}.{Fore.RESET}"
|
|
124
|
+
)
|
|
125
|
+
break
|
|
126
|
+
|
|
127
|
+
if user_response.terminated:
|
|
128
|
+
reason = user_response.info['termination_reasons']
|
|
129
|
+
print(
|
|
130
|
+
f"{Fore.GREEN}AI User terminated. Reason: {reason}."
|
|
131
|
+
f"{Fore.RESET}"
|
|
132
|
+
)
|
|
133
|
+
break
|
|
134
|
+
|
|
135
|
+
print_text_animated(
|
|
136
|
+
f"{Fore.BLUE}AI User:\n\n{user_response.msg.content}"
|
|
137
|
+
f"{Fore.RESET}\n",
|
|
138
|
+
delay=0.005,
|
|
139
|
+
)
|
|
140
|
+
chat_history.append(f"AI User: {user_response.msg.content}")
|
|
141
|
+
|
|
142
|
+
print_text_animated(
|
|
143
|
+
f"{Fore.GREEN}AI Assistant:{Fore.RESET}", delay=0.005
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
for func_record in assistant_response.info['tool_calls']:
|
|
147
|
+
print(func_record)
|
|
148
|
+
|
|
149
|
+
print_text_animated(
|
|
150
|
+
f"\n{Fore.GREEN}{assistant_response.msg.content}"
|
|
151
|
+
f"{Fore.RESET}\n",
|
|
152
|
+
delay=0.005,
|
|
153
|
+
)
|
|
154
|
+
chat_history.append(
|
|
155
|
+
f"AI Assistant: {assistant_response.msg.content}"
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
if "CAMEL_TASK_DONE" in user_response.msg.content:
|
|
159
|
+
break
|
|
160
|
+
|
|
161
|
+
input_msg = assistant_response.msg
|
|
162
|
+
|
|
163
|
+
chat_history_str = "\n".join(chat_history)
|
|
164
|
+
prompt = ROLEPLAY_SUMMARIZE_PROMPT.format(
|
|
165
|
+
user_role=self.user_role_name,
|
|
166
|
+
assistant_role=self.assistant_role_name,
|
|
167
|
+
content=task.content,
|
|
168
|
+
chat_history=chat_history_str,
|
|
169
|
+
additional_info=task.additional_info,
|
|
170
|
+
)
|
|
171
|
+
req = BaseMessage.make_user_message(
|
|
172
|
+
role_name="User",
|
|
173
|
+
content=prompt,
|
|
174
|
+
)
|
|
175
|
+
response = self.summarize_agent.step(req, output_schema=TaskResult)
|
|
176
|
+
result_dict = ast.literal_eval(response.msg.content)
|
|
177
|
+
task_result = TaskResult(**result_dict)
|
|
178
|
+
task.result = task_result.content
|
|
179
|
+
|
|
180
|
+
print(f"Task result: {task.result}\n")
|
|
181
|
+
return TaskState.DONE
|