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
|
@@ -13,32 +13,41 @@
|
|
|
13
13
|
# =========== Copyright 2023 @ CAMEL-AI.org. All Rights Reserved. ===========
|
|
14
14
|
from __future__ import annotations
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
import ast
|
|
17
|
+
from typing import Any, List
|
|
17
18
|
|
|
18
|
-
from
|
|
19
|
+
from colorama import Fore
|
|
20
|
+
|
|
21
|
+
from camel.agents import ChatAgent
|
|
19
22
|
from camel.messages.base import BaseMessage
|
|
20
23
|
from camel.tasks.task import Task, TaskState
|
|
21
|
-
from camel.
|
|
22
|
-
from camel.workforce.
|
|
23
|
-
from camel.workforce.
|
|
24
|
+
from camel.utils import print_text_animated
|
|
25
|
+
from camel.workforce.prompts import PROCESS_TASK_PROMPT
|
|
26
|
+
from camel.workforce.utils import TaskResult
|
|
27
|
+
from camel.workforce.worker import Worker
|
|
24
28
|
|
|
25
29
|
|
|
26
|
-
class
|
|
30
|
+
class SingleAgentWorker(Worker):
|
|
27
31
|
r"""A worker node that consists of a single agent.
|
|
28
32
|
|
|
29
33
|
Args:
|
|
30
34
|
description (str): Description of the node.
|
|
31
|
-
worker (
|
|
35
|
+
worker (ChatAgent): Worker of the node. A single agent.
|
|
32
36
|
"""
|
|
33
37
|
|
|
34
38
|
def __init__(
|
|
35
39
|
self,
|
|
36
40
|
description: str,
|
|
37
|
-
worker:
|
|
41
|
+
worker: ChatAgent,
|
|
38
42
|
) -> None:
|
|
39
43
|
super().__init__(description)
|
|
40
44
|
self.worker = worker
|
|
41
45
|
|
|
46
|
+
def reset(self) -> Any:
|
|
47
|
+
r"""Resets the worker to its initial state."""
|
|
48
|
+
super().reset()
|
|
49
|
+
self.worker.reset()
|
|
50
|
+
|
|
42
51
|
async def _process_task(
|
|
43
52
|
self, task: Task, dependencies: List[Task]
|
|
44
53
|
) -> TaskState:
|
|
@@ -57,21 +66,39 @@ class SingleAgentNode(WorkerNode):
|
|
|
57
66
|
TaskState: `TaskState.DONE` if processed successfully, otherwise
|
|
58
67
|
`TaskState.FAILED`.
|
|
59
68
|
"""
|
|
69
|
+
dependency_tasks_info = self._get_dep_tasks_info(dependencies)
|
|
70
|
+
prompt = PROCESS_TASK_PROMPT.format(
|
|
71
|
+
content=task.content,
|
|
72
|
+
dependency_tasks_info=dependency_tasks_info,
|
|
73
|
+
additional_info=task.additional_info,
|
|
74
|
+
)
|
|
75
|
+
req = BaseMessage.make_user_message(
|
|
76
|
+
role_name="User",
|
|
77
|
+
content=prompt,
|
|
78
|
+
)
|
|
60
79
|
try:
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
80
|
+
response = self.worker.step(req, output_schema=TaskResult)
|
|
81
|
+
except Exception as e:
|
|
82
|
+
print(
|
|
83
|
+
f"{Fore.RED}Error occurred while processing task {task.id}:"
|
|
84
|
+
f"\n{e}{Fore.RESET}"
|
|
66
85
|
)
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
86
|
+
return TaskState.FAILED
|
|
87
|
+
|
|
88
|
+
print(f"======\n{Fore.GREEN}Reply from {self}:{Fore.RESET}")
|
|
89
|
+
|
|
90
|
+
result_dict = ast.literal_eval(response.msg.content)
|
|
91
|
+
task_result = TaskResult(**result_dict)
|
|
92
|
+
|
|
93
|
+
if task_result.failed:
|
|
94
|
+
print(
|
|
95
|
+
f"{Fore.RED}{self} failed to process task {task.id}.\n======"
|
|
70
96
|
)
|
|
71
|
-
response = self.worker.step(req)
|
|
72
|
-
# print("info['tool_calls']:", response.info['tool_calls'])
|
|
73
|
-
task.result = parse_task_result_resp(response.msg.content)
|
|
74
|
-
print('Task result:', task.result, '\n')
|
|
75
|
-
return TaskState.DONE
|
|
76
|
-
except Exception:
|
|
77
97
|
return TaskState.FAILED
|
|
98
|
+
|
|
99
|
+
task.result = task_result.content
|
|
100
|
+
print_text_animated(
|
|
101
|
+
f'\n{Fore.GREEN}{task.result}{Fore.RESET}\n======',
|
|
102
|
+
delay=0.005,
|
|
103
|
+
)
|
|
104
|
+
return TaskState.DONE
|
camel/workforce/task_channel.py
CHANGED
|
@@ -61,7 +61,7 @@ class Packet:
|
|
|
61
61
|
publisher_id: str,
|
|
62
62
|
assignee_id: Optional[str] = None,
|
|
63
63
|
status: PacketStatus = PacketStatus.SENT,
|
|
64
|
-
):
|
|
64
|
+
) -> None:
|
|
65
65
|
self.task = task
|
|
66
66
|
self.publisher_id = publisher_id
|
|
67
67
|
self.assignee_id = assignee_id
|
|
@@ -83,7 +83,6 @@ class TaskChannel:
|
|
|
83
83
|
self._task_dict: Dict[str, Packet] = {}
|
|
84
84
|
|
|
85
85
|
async def get_returned_task_by_publisher(self, publisher_id: str) -> Task:
|
|
86
|
-
await self.print_channel()
|
|
87
86
|
async with self._condition:
|
|
88
87
|
while True:
|
|
89
88
|
for task_id in self._task_id_list:
|
|
@@ -167,7 +166,6 @@ class TaskChannel:
|
|
|
167
166
|
raise ValueError(f"Task {task_id} not found.")
|
|
168
167
|
return self._task_dict[task_id].task
|
|
169
168
|
|
|
170
|
-
async def
|
|
169
|
+
async def get_channel_debug_info(self) -> str:
|
|
171
170
|
async with self._condition:
|
|
172
|
-
|
|
173
|
-
print(self._task_id_list)
|
|
171
|
+
return str(self._task_dict) + '\n' + str(self._task_id_list)
|
camel/workforce/utils.py
CHANGED
|
@@ -11,66 +11,36 @@
|
|
|
11
11
|
# See the License for the specific language governing permissions and
|
|
12
12
|
# limitations under the License.
|
|
13
13
|
# =========== Copyright 2023 @ CAMEL-AI.org. All Rights Reserved. ===========
|
|
14
|
-
import re
|
|
15
14
|
from functools import wraps
|
|
16
15
|
from typing import Callable
|
|
17
16
|
|
|
17
|
+
from pydantic import BaseModel, Field
|
|
18
18
|
|
|
19
|
-
class NodeConf:
|
|
20
|
-
def __init__(self, role: str, system: str, description: str):
|
|
21
|
-
self.role = role
|
|
22
|
-
self.system = system
|
|
23
|
-
self.description = description
|
|
24
19
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
try:
|
|
36
|
-
import xml.etree.ElementTree as ET
|
|
37
|
-
|
|
38
|
-
root = ET.fromstring(config_raw)
|
|
39
|
-
workforce_info = {child.tag: child.text for child in root}
|
|
40
|
-
except Exception as e:
|
|
41
|
-
raise ValueError(f"Failed to parse workforce configuration: {e}")
|
|
42
|
-
|
|
43
|
-
if (
|
|
44
|
-
"role" not in workforce_info
|
|
45
|
-
or "system" not in workforce_info
|
|
46
|
-
or "description" not in workforce_info
|
|
47
|
-
):
|
|
48
|
-
raise ValueError("Missing required fields in workforce configuration.")
|
|
49
|
-
|
|
50
|
-
return NodeConf(
|
|
51
|
-
role=workforce_info["role"] or "",
|
|
52
|
-
system=workforce_info["system"] or "",
|
|
53
|
-
description=workforce_info["description"] or "",
|
|
20
|
+
class WorkerConf(BaseModel):
|
|
21
|
+
role: str = Field(
|
|
22
|
+
description="The role of the agent working in the work node."
|
|
23
|
+
)
|
|
24
|
+
sys_msg: str = Field(
|
|
25
|
+
description="The system message that will be sent to the agent in "
|
|
26
|
+
"the node."
|
|
27
|
+
)
|
|
28
|
+
description: str = Field(
|
|
29
|
+
description="The description of the new work node itself."
|
|
54
30
|
)
|
|
55
31
|
|
|
56
32
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
return assignee_id.group(1)
|
|
33
|
+
class TaskResult(BaseModel):
|
|
34
|
+
content: str = Field(description="The result of the task.")
|
|
35
|
+
failed: bool = Field(
|
|
36
|
+
description="Flag indicating whether the task processing failed."
|
|
37
|
+
)
|
|
63
38
|
|
|
64
39
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
if failed_tag:
|
|
70
|
-
task_result = None
|
|
71
|
-
if task_result is None:
|
|
72
|
-
raise ValueError("No result found in the response.")
|
|
73
|
-
return task_result.group(1)
|
|
40
|
+
class TaskAssignResult(BaseModel):
|
|
41
|
+
assignee_id: str = Field(
|
|
42
|
+
description="The ID of the workforce that is assigned to the task."
|
|
43
|
+
)
|
|
74
44
|
|
|
75
45
|
|
|
76
46
|
def check_if_running(running: bool) -> Callable:
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
# =========== Copyright 2023 @ CAMEL-AI.org. All Rights Reserved. ===========
|
|
14
14
|
from __future__ import annotations
|
|
15
15
|
|
|
16
|
+
import logging
|
|
16
17
|
from abc import ABC, abstractmethod
|
|
17
18
|
from typing import List
|
|
18
19
|
|
|
@@ -23,8 +24,10 @@ from camel.workforce.base import BaseNode
|
|
|
23
24
|
from camel.workforce.task_channel import TaskChannel
|
|
24
25
|
from camel.workforce.utils import check_if_running
|
|
25
26
|
|
|
27
|
+
logger = logging.getLogger(__name__)
|
|
26
28
|
|
|
27
|
-
|
|
29
|
+
|
|
30
|
+
class Worker(BaseNode, ABC):
|
|
28
31
|
r"""A worker node that works on tasks. It is the basic unit of task
|
|
29
32
|
processing in the workforce system.
|
|
30
33
|
|
|
@@ -33,15 +36,15 @@ class WorkerNode(BaseNode, ABC):
|
|
|
33
36
|
|
|
34
37
|
"""
|
|
35
38
|
|
|
36
|
-
# TODO: Make RolePlaying and Agent scceed from one parent class, so that
|
|
37
|
-
# we don't need two different classes for the worker node.
|
|
38
|
-
|
|
39
39
|
def __init__(
|
|
40
40
|
self,
|
|
41
41
|
description: str,
|
|
42
42
|
) -> None:
|
|
43
43
|
super().__init__(description)
|
|
44
44
|
|
|
45
|
+
def __repr__(self):
|
|
46
|
+
return f"Worker node {self.node_id} ({self.description})"
|
|
47
|
+
|
|
45
48
|
@abstractmethod
|
|
46
49
|
async def _process_task(
|
|
47
50
|
self, task: Task, dependencies: List[Task]
|
|
@@ -52,6 +55,7 @@ class WorkerNode(BaseNode, ABC):
|
|
|
52
55
|
'DONE' if the task is successfully processed,
|
|
53
56
|
'FAILED' if the processing fails.
|
|
54
57
|
"""
|
|
58
|
+
pass
|
|
55
59
|
|
|
56
60
|
async def _get_assigned_task(self) -> Task:
|
|
57
61
|
r"""Get the task assigned to this node from the channel."""
|
|
@@ -80,27 +84,26 @@ class WorkerNode(BaseNode, ABC):
|
|
|
80
84
|
indefinitely.
|
|
81
85
|
"""
|
|
82
86
|
self._running = True
|
|
83
|
-
|
|
87
|
+
logger.info(f"{self} started.")
|
|
84
88
|
|
|
85
89
|
while True:
|
|
86
|
-
#
|
|
90
|
+
# Get the earliest task assigned to this node
|
|
87
91
|
task = await self._get_assigned_task()
|
|
88
92
|
print(
|
|
89
|
-
f
|
|
90
|
-
|
|
91
|
-
task.content,
|
|
93
|
+
f"{Fore.YELLOW}{self} get task {task.id}: {task.content}"
|
|
94
|
+
f"{Fore.RESET}"
|
|
92
95
|
)
|
|
93
|
-
#
|
|
96
|
+
# Get the Task instance of dependencies
|
|
94
97
|
dependency_ids = await self._channel.get_dependency_ids()
|
|
95
98
|
task_dependencies = [
|
|
96
99
|
await self._channel.get_task_by_id(dep_id)
|
|
97
100
|
for dep_id in dependency_ids
|
|
98
101
|
]
|
|
99
102
|
|
|
100
|
-
#
|
|
103
|
+
# Process the task
|
|
101
104
|
task_state = await self._process_task(task, task_dependencies)
|
|
102
105
|
|
|
103
|
-
#
|
|
106
|
+
# Update the result and status of the task
|
|
104
107
|
task.set_state(task_state)
|
|
105
108
|
|
|
106
109
|
await self._channel.return_task(task.id)
|