camel-ai 0.1.5.6__py3-none-any.whl → 0.1.6.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 +249 -36
- camel/agents/critic_agent.py +18 -2
- camel/agents/deductive_reasoner_agent.py +16 -4
- camel/agents/embodied_agent.py +20 -6
- camel/agents/knowledge_graph_agent.py +24 -5
- camel/agents/role_assignment_agent.py +13 -1
- camel/agents/search_agent.py +16 -5
- camel/agents/task_agent.py +20 -5
- camel/configs/__init__.py +11 -9
- camel/configs/anthropic_config.py +5 -6
- camel/configs/base_config.py +50 -4
- camel/configs/gemini_config.py +69 -17
- camel/configs/groq_config.py +105 -0
- camel/configs/litellm_config.py +2 -8
- camel/configs/mistral_config.py +78 -0
- camel/configs/ollama_config.py +5 -7
- camel/configs/openai_config.py +12 -23
- camel/configs/vllm_config.py +102 -0
- camel/configs/zhipuai_config.py +5 -11
- camel/embeddings/__init__.py +2 -0
- camel/embeddings/mistral_embedding.py +89 -0
- camel/human.py +1 -1
- camel/interpreters/__init__.py +2 -0
- camel/interpreters/ipython_interpreter.py +167 -0
- camel/loaders/__init__.py +2 -0
- camel/loaders/firecrawl_reader.py +213 -0
- camel/memories/agent_memories.py +1 -4
- camel/memories/blocks/chat_history_block.py +6 -2
- camel/memories/blocks/vectordb_block.py +3 -1
- camel/memories/context_creators/score_based.py +6 -6
- camel/memories/records.py +9 -7
- camel/messages/base.py +1 -0
- camel/models/__init__.py +8 -0
- camel/models/anthropic_model.py +7 -2
- camel/models/azure_openai_model.py +152 -0
- camel/models/base_model.py +9 -2
- camel/models/gemini_model.py +14 -2
- camel/models/groq_model.py +131 -0
- camel/models/litellm_model.py +26 -4
- camel/models/mistral_model.py +169 -0
- camel/models/model_factory.py +30 -3
- camel/models/ollama_model.py +21 -2
- camel/models/open_source_model.py +13 -5
- camel/models/openai_model.py +7 -2
- camel/models/stub_model.py +4 -4
- camel/models/vllm_model.py +138 -0
- camel/models/zhipuai_model.py +7 -4
- camel/prompts/__init__.py +8 -1
- camel/prompts/image_craft.py +34 -0
- camel/prompts/multi_condition_image_craft.py +34 -0
- camel/prompts/task_prompt_template.py +10 -4
- camel/prompts/{descripte_video_prompt.py → video_description_prompt.py} +1 -1
- camel/responses/agent_responses.py +4 -3
- camel/retrievers/auto_retriever.py +2 -2
- camel/societies/babyagi_playing.py +6 -4
- camel/societies/role_playing.py +16 -8
- camel/storages/graph_storages/graph_element.py +10 -14
- camel/storages/graph_storages/neo4j_graph.py +5 -0
- camel/storages/vectordb_storages/base.py +24 -13
- camel/storages/vectordb_storages/milvus.py +1 -1
- camel/storages/vectordb_storages/qdrant.py +2 -3
- camel/tasks/__init__.py +22 -0
- camel/tasks/task.py +408 -0
- camel/tasks/task_prompt.py +65 -0
- camel/toolkits/__init__.py +39 -0
- camel/toolkits/base.py +4 -2
- camel/toolkits/code_execution.py +1 -1
- camel/toolkits/dalle_toolkit.py +146 -0
- camel/toolkits/github_toolkit.py +19 -34
- camel/toolkits/google_maps_toolkit.py +368 -0
- camel/toolkits/math_toolkit.py +79 -0
- camel/toolkits/open_api_toolkit.py +547 -0
- camel/{functions → toolkits}/openai_function.py +2 -7
- camel/toolkits/retrieval_toolkit.py +76 -0
- camel/toolkits/search_toolkit.py +326 -0
- camel/toolkits/slack_toolkit.py +308 -0
- camel/toolkits/twitter_toolkit.py +522 -0
- camel/toolkits/weather_toolkit.py +173 -0
- camel/types/enums.py +154 -35
- camel/utils/__init__.py +14 -2
- camel/utils/async_func.py +1 -1
- camel/utils/commons.py +152 -2
- camel/utils/constants.py +3 -0
- camel/utils/token_counting.py +148 -40
- camel/workforce/__init__.py +23 -0
- camel/workforce/base.py +50 -0
- camel/workforce/manager_node.py +299 -0
- camel/workforce/role_playing_node.py +168 -0
- camel/workforce/single_agent_node.py +77 -0
- camel/workforce/task_channel.py +173 -0
- camel/workforce/utils.py +97 -0
- camel/workforce/worker_node.py +115 -0
- camel/workforce/workforce.py +49 -0
- camel/workforce/workforce_prompt.py +125 -0
- {camel_ai-0.1.5.6.dist-info → camel_ai-0.1.6.1.dist-info}/METADATA +45 -3
- camel_ai-0.1.6.1.dist-info/RECORD +182 -0
- camel/functions/__init__.py +0 -51
- camel/functions/google_maps_function.py +0 -335
- camel/functions/math_functions.py +0 -61
- camel/functions/open_api_function.py +0 -508
- camel/functions/retrieval_functions.py +0 -61
- camel/functions/search_functions.py +0 -298
- camel/functions/slack_functions.py +0 -286
- camel/functions/twitter_function.py +0 -479
- camel/functions/weather_functions.py +0 -144
- camel_ai-0.1.5.6.dist-info/RECORD +0 -157
- /camel/{functions → toolkits}/open_api_specs/biztoc/__init__.py +0 -0
- /camel/{functions → toolkits}/open_api_specs/biztoc/ai-plugin.json +0 -0
- /camel/{functions → toolkits}/open_api_specs/biztoc/openapi.yaml +0 -0
- /camel/{functions → toolkits}/open_api_specs/coursera/__init__.py +0 -0
- /camel/{functions → toolkits}/open_api_specs/coursera/openapi.yaml +0 -0
- /camel/{functions → toolkits}/open_api_specs/create_qr_code/__init__.py +0 -0
- /camel/{functions → toolkits}/open_api_specs/create_qr_code/openapi.yaml +0 -0
- /camel/{functions → toolkits}/open_api_specs/klarna/__init__.py +0 -0
- /camel/{functions → toolkits}/open_api_specs/klarna/openapi.yaml +0 -0
- /camel/{functions → toolkits}/open_api_specs/nasa_apod/__init__.py +0 -0
- /camel/{functions → toolkits}/open_api_specs/nasa_apod/openapi.yaml +0 -0
- /camel/{functions → toolkits}/open_api_specs/outschool/__init__.py +0 -0
- /camel/{functions → toolkits}/open_api_specs/outschool/ai-plugin.json +0 -0
- /camel/{functions → toolkits}/open_api_specs/outschool/openapi.yaml +0 -0
- /camel/{functions → toolkits}/open_api_specs/outschool/paths/__init__.py +0 -0
- /camel/{functions → toolkits}/open_api_specs/outschool/paths/get_classes.py +0 -0
- /camel/{functions → toolkits}/open_api_specs/outschool/paths/search_teachers.py +0 -0
- /camel/{functions → toolkits}/open_api_specs/security_config.py +0 -0
- /camel/{functions → toolkits}/open_api_specs/speak/__init__.py +0 -0
- /camel/{functions → toolkits}/open_api_specs/speak/openapi.yaml +0 -0
- /camel/{functions → toolkits}/open_api_specs/web_scraper/__init__.py +0 -0
- /camel/{functions → toolkits}/open_api_specs/web_scraper/ai-plugin.json +0 -0
- /camel/{functions → toolkits}/open_api_specs/web_scraper/openapi.yaml +0 -0
- /camel/{functions → toolkits}/open_api_specs/web_scraper/paths/__init__.py +0 -0
- /camel/{functions → toolkits}/open_api_specs/web_scraper/paths/scraper.py +0 -0
- {camel_ai-0.1.5.6.dist-info → camel_ai-0.1.6.1.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,168 @@
|
|
|
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
|
+
from typing import Dict, List, Optional
|
|
17
|
+
|
|
18
|
+
from colorama import Fore, Style
|
|
19
|
+
|
|
20
|
+
from camel.agents.chat_agent import ChatAgent, FunctionCallingRecord
|
|
21
|
+
from camel.messages.base import BaseMessage
|
|
22
|
+
from camel.societies import RolePlaying
|
|
23
|
+
from camel.tasks.task import Task, TaskState
|
|
24
|
+
from camel.utils import print_text_animated
|
|
25
|
+
from camel.workforce.utils import parse_task_result_resp
|
|
26
|
+
from camel.workforce.worker_node import WorkerNode
|
|
27
|
+
from camel.workforce.workforce_prompt import (
|
|
28
|
+
ROLEPLAY_PROCESS_TASK_PROMPT,
|
|
29
|
+
ROLEPLAY_SUMMERIZE_PROMPT,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class RolePlayingNode(WorkerNode):
|
|
34
|
+
r"""A worker node that contains a role playing.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
description (str): Description of the node.
|
|
38
|
+
assistant_role_name (str): The role name of the assistant agent.
|
|
39
|
+
user_role_name (str): The role name of the user agent.
|
|
40
|
+
assistant_agent_kwargs (Optional[Dict], optional): The keyword
|
|
41
|
+
arguments to initialize the assistant agent in the role playing,
|
|
42
|
+
like the model name, etc. Defaults to None.
|
|
43
|
+
user_agent_kwargs (Optional[Dict], optional): The keyword arguments to
|
|
44
|
+
initialize the user agent in the role playing, like the model name,
|
|
45
|
+
etc. Defaults to None.
|
|
46
|
+
chat_turn_limit (int, optional): The maximum number of chat turns in
|
|
47
|
+
the role playing. Defaults to 3.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
def __init__(
|
|
51
|
+
self,
|
|
52
|
+
description: str,
|
|
53
|
+
assistant_role_name: str,
|
|
54
|
+
user_role_name: str,
|
|
55
|
+
assistant_agent_kwargs: Optional[Dict] = None,
|
|
56
|
+
user_agent_kwargs: Optional[Dict] = None,
|
|
57
|
+
chat_turn_limit: int = 3,
|
|
58
|
+
) -> None:
|
|
59
|
+
super().__init__(description)
|
|
60
|
+
sys_message = BaseMessage.make_assistant_message(
|
|
61
|
+
role_name="Summarizer",
|
|
62
|
+
content="Good at summarizing the results of the chat logs",
|
|
63
|
+
)
|
|
64
|
+
self.summerize_agent = ChatAgent(sys_message)
|
|
65
|
+
self.chat_turn_limit = chat_turn_limit
|
|
66
|
+
self.assistant_role_name = assistant_role_name
|
|
67
|
+
self.user_role_name = user_role_name
|
|
68
|
+
self.assistant_agent_kwargs = assistant_agent_kwargs
|
|
69
|
+
self.user_agent_kwargs = user_agent_kwargs
|
|
70
|
+
|
|
71
|
+
async def _process_task(
|
|
72
|
+
self, task: Task, dependencies: List[Task]
|
|
73
|
+
) -> TaskState:
|
|
74
|
+
r"""Processes a task leveraging its dependencies through role-playing.
|
|
75
|
+
|
|
76
|
+
This method orchestrates a role-playing session between an AI
|
|
77
|
+
assistant and an AI user to process a given task. It initiates with a
|
|
78
|
+
generated prompt based on the task and its dependencies, conducts a
|
|
79
|
+
dialogue up to a specified chat turn limit, and then summarizes the
|
|
80
|
+
dialogue to determine the task's outcome.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
task (Task): The task object to be processed, containing necessary
|
|
84
|
+
details like content and type.
|
|
85
|
+
dependencies (List[Task]): A list of task objects that the current
|
|
86
|
+
task depends on.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
TaskState: `TaskState.DONE` if processed successfully, otherwise
|
|
90
|
+
`TaskState.FAILED`.
|
|
91
|
+
"""
|
|
92
|
+
try:
|
|
93
|
+
dependency_tasks_info = self._get_dep_tasks_info(dependencies)
|
|
94
|
+
prompt = ROLEPLAY_PROCESS_TASK_PROMPT.format(
|
|
95
|
+
content=task.content,
|
|
96
|
+
type=task.type,
|
|
97
|
+
dependency_task_info=dependency_tasks_info,
|
|
98
|
+
)
|
|
99
|
+
role_play_session = RolePlaying(
|
|
100
|
+
assistant_role_name=self.assistant_role_name,
|
|
101
|
+
user_role_name=self.user_role_name,
|
|
102
|
+
assistant_agent_kwargs=self.assistant_agent_kwargs,
|
|
103
|
+
user_agent_kwargs=self.user_agent_kwargs,
|
|
104
|
+
task_prompt=prompt,
|
|
105
|
+
with_task_specify=False,
|
|
106
|
+
)
|
|
107
|
+
n = 0
|
|
108
|
+
input_msg = role_play_session.init_chat()
|
|
109
|
+
chat_history = []
|
|
110
|
+
while n < self.chat_turn_limit:
|
|
111
|
+
n += 1
|
|
112
|
+
assistant_response, user_response = role_play_session.step(
|
|
113
|
+
input_msg
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
if assistant_response.terminated:
|
|
117
|
+
reason = assistant_response.info['termination_reasons']
|
|
118
|
+
print(
|
|
119
|
+
Fore.GREEN + f"AI Assistant terminated. Reason: "
|
|
120
|
+
f"{reason}. "
|
|
121
|
+
)
|
|
122
|
+
break
|
|
123
|
+
if user_response.terminated:
|
|
124
|
+
reason = user_response.info['termination_reasons']
|
|
125
|
+
print(
|
|
126
|
+
Fore.GREEN + f"AI User terminated. Reason: {reason}."
|
|
127
|
+
)
|
|
128
|
+
break
|
|
129
|
+
print_text_animated(
|
|
130
|
+
Fore.BLUE + f"AI User:\n\n{user_response.msg.content}\n",
|
|
131
|
+
delay=0.005,
|
|
132
|
+
)
|
|
133
|
+
chat_history.append(f"AI User: {user_response.msg.content}")
|
|
134
|
+
|
|
135
|
+
print_text_animated(Fore.GREEN + "AI Assistant:", delay=0.005)
|
|
136
|
+
tool_calls: List[FunctionCallingRecord] = (
|
|
137
|
+
assistant_response.info['tool_calls']
|
|
138
|
+
)
|
|
139
|
+
for func_record in tool_calls:
|
|
140
|
+
print_text_animated(f"{func_record}", delay=0.005)
|
|
141
|
+
print_text_animated(
|
|
142
|
+
f"{assistant_response.msg.content}\n", delay=0.005
|
|
143
|
+
)
|
|
144
|
+
chat_history.append(
|
|
145
|
+
f"AI Assistant: {assistant_response.msg.content}"
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
if "CAMEL_TASK_DONE" in user_response.msg.content:
|
|
149
|
+
break
|
|
150
|
+
|
|
151
|
+
input_msg = assistant_response.msg
|
|
152
|
+
|
|
153
|
+
chat_history_str = "\n".join(chat_history)
|
|
154
|
+
prompt = ROLEPLAY_SUMMERIZE_PROMPT.format(
|
|
155
|
+
content=task.content,
|
|
156
|
+
type=task.type,
|
|
157
|
+
chat_history=chat_history_str,
|
|
158
|
+
)
|
|
159
|
+
req = BaseMessage.make_user_message(
|
|
160
|
+
role_name="User",
|
|
161
|
+
content=prompt,
|
|
162
|
+
)
|
|
163
|
+
response = self.summerize_agent.step(req)
|
|
164
|
+
task.result = parse_task_result_resp(response.msg.content)
|
|
165
|
+
print(Style.RESET_ALL + 'Task result:', task.result, '\n')
|
|
166
|
+
return TaskState.DONE
|
|
167
|
+
except Exception:
|
|
168
|
+
return TaskState.FAILED
|
|
@@ -0,0 +1,77 @@
|
|
|
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
|
+
from typing import List
|
|
17
|
+
|
|
18
|
+
from camel.agents.base import BaseAgent
|
|
19
|
+
from camel.messages.base import BaseMessage
|
|
20
|
+
from camel.tasks.task import Task, TaskState
|
|
21
|
+
from camel.workforce.utils import parse_task_result_resp
|
|
22
|
+
from camel.workforce.worker_node import WorkerNode
|
|
23
|
+
from camel.workforce.workforce_prompt import PROCESS_TASK_PROMPT
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class SingleAgentNode(WorkerNode):
|
|
27
|
+
r"""A worker node that consists of a single agent.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
description (str): Description of the node.
|
|
31
|
+
worker (BaseAgent): Worker of the node. A single agent.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(
|
|
35
|
+
self,
|
|
36
|
+
description: str,
|
|
37
|
+
worker: BaseAgent,
|
|
38
|
+
) -> None:
|
|
39
|
+
super().__init__(description)
|
|
40
|
+
self.worker = worker
|
|
41
|
+
|
|
42
|
+
async def _process_task(
|
|
43
|
+
self, task: Task, dependencies: List[Task]
|
|
44
|
+
) -> TaskState:
|
|
45
|
+
r"""Processes a task with its dependencies.
|
|
46
|
+
|
|
47
|
+
This method asynchronously processes a given task, considering its
|
|
48
|
+
dependencies, by sending a generated prompt to a worker. It updates
|
|
49
|
+
the task's result based on the agent's response.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
task (Task): The task to process, which includes necessary details
|
|
53
|
+
like content and type.
|
|
54
|
+
dependencies (List[Task]): Tasks that the given task depends on.
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
TaskState: `TaskState.DONE` if processed successfully, otherwise
|
|
58
|
+
`TaskState.FAILED`.
|
|
59
|
+
"""
|
|
60
|
+
try:
|
|
61
|
+
dependency_tasks_info = self._get_dep_tasks_info(dependencies)
|
|
62
|
+
prompt = PROCESS_TASK_PROMPT.format(
|
|
63
|
+
content=task.content,
|
|
64
|
+
type=task.type,
|
|
65
|
+
dependency_task_info=dependency_tasks_info,
|
|
66
|
+
)
|
|
67
|
+
req = BaseMessage.make_user_message(
|
|
68
|
+
role_name="User",
|
|
69
|
+
content=prompt,
|
|
70
|
+
)
|
|
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
|
+
return TaskState.FAILED
|
|
@@ -0,0 +1,173 @@
|
|
|
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
|
+
import asyncio
|
|
15
|
+
from enum import Enum
|
|
16
|
+
from typing import Dict, List, Optional
|
|
17
|
+
|
|
18
|
+
from camel.tasks import Task
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class PacketStatus(Enum):
|
|
22
|
+
r"""The status of a packet. The packet can be in one of the following
|
|
23
|
+
states:
|
|
24
|
+
- ``SENT``: The packet has been sent to a worker.
|
|
25
|
+
- ``RETURNED``: The packet has been returned by the worker, meaning that
|
|
26
|
+
the status of the task inside has been updated.
|
|
27
|
+
- ``ARCHIVED``: The packet has been archived, meaning that the content of
|
|
28
|
+
the task inside will not be changed. The task is considered
|
|
29
|
+
as a dependency.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
SENT = "SENT"
|
|
33
|
+
RETURNED = "RETURNED"
|
|
34
|
+
ARCHIVED = "ARCHIVED"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class Packet:
|
|
38
|
+
r"""The basic element inside the channel. A task is wrapped inside a
|
|
39
|
+
packet. The packet will contain the task, along with the task's assignee,
|
|
40
|
+
and the task's status.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
task (Task): The task that is wrapped inside the packet.
|
|
44
|
+
publisher_id (str): The ID of the workforce that published the task.
|
|
45
|
+
assignee_id (str): The ID of the workforce that is assigned
|
|
46
|
+
to the task. Defaults to None, meaning that the task is posted as
|
|
47
|
+
a dependency in the channel.
|
|
48
|
+
|
|
49
|
+
Attributes:
|
|
50
|
+
task (Task): The task that is wrapped inside the packet.
|
|
51
|
+
publisher_id (str): The ID of the workforce that published the task.
|
|
52
|
+
assignee_id (Optional[str], optional): The ID of the workforce that is
|
|
53
|
+
assigned to the task. Would be None if the task is a dependency.
|
|
54
|
+
Defaults to None.
|
|
55
|
+
status (PacketStatus): The status of the task.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
def __init__(
|
|
59
|
+
self,
|
|
60
|
+
task: Task,
|
|
61
|
+
publisher_id: str,
|
|
62
|
+
assignee_id: Optional[str] = None,
|
|
63
|
+
status: PacketStatus = PacketStatus.SENT,
|
|
64
|
+
):
|
|
65
|
+
self.task = task
|
|
66
|
+
self.publisher_id = publisher_id
|
|
67
|
+
self.assignee_id = assignee_id
|
|
68
|
+
self.status = status
|
|
69
|
+
|
|
70
|
+
def __repr__(self):
|
|
71
|
+
return (
|
|
72
|
+
f"Packet(publisher_id={self.publisher_id}, assignee_id="
|
|
73
|
+
f"{self.assignee_id}, status={self.status})"
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class TaskChannel:
|
|
78
|
+
r"""An internal class used by Workforce to manage tasks."""
|
|
79
|
+
|
|
80
|
+
def __init__(self) -> None:
|
|
81
|
+
self._task_id_list: List[str] = []
|
|
82
|
+
self._condition = asyncio.Condition()
|
|
83
|
+
self._task_dict: Dict[str, Packet] = {}
|
|
84
|
+
|
|
85
|
+
async def get_returned_task_by_publisher(self, publisher_id: str) -> Task:
|
|
86
|
+
await self.print_channel()
|
|
87
|
+
async with self._condition:
|
|
88
|
+
while True:
|
|
89
|
+
for task_id in self._task_id_list:
|
|
90
|
+
packet = self._task_dict[task_id]
|
|
91
|
+
if packet.publisher_id != publisher_id:
|
|
92
|
+
continue
|
|
93
|
+
if packet.status != PacketStatus.RETURNED:
|
|
94
|
+
continue
|
|
95
|
+
return packet.task
|
|
96
|
+
await self._condition.wait()
|
|
97
|
+
|
|
98
|
+
async def get_assigned_task_by_assignee(self, assignee_id: str) -> Task:
|
|
99
|
+
async with self._condition:
|
|
100
|
+
while True:
|
|
101
|
+
for task_id in self._task_id_list:
|
|
102
|
+
packet = self._task_dict[task_id]
|
|
103
|
+
if (
|
|
104
|
+
packet.status == PacketStatus.SENT
|
|
105
|
+
and packet.assignee_id == assignee_id
|
|
106
|
+
):
|
|
107
|
+
return packet.task
|
|
108
|
+
await self._condition.wait()
|
|
109
|
+
|
|
110
|
+
async def post_task(
|
|
111
|
+
self, task: Task, publisher_id: str, assignee_id: str
|
|
112
|
+
) -> None:
|
|
113
|
+
r"""Send a task to the channel with specified publisher and assignee,
|
|
114
|
+
along with the dependency of the task."""
|
|
115
|
+
async with self._condition:
|
|
116
|
+
self._task_id_list.append(task.id)
|
|
117
|
+
packet = Packet(task, publisher_id, assignee_id)
|
|
118
|
+
self._task_dict[packet.task.id] = packet
|
|
119
|
+
self._condition.notify_all()
|
|
120
|
+
|
|
121
|
+
async def post_dependency(
|
|
122
|
+
self, dependency: Task, publisher_id: str
|
|
123
|
+
) -> None:
|
|
124
|
+
r"""Post a dependency to the channel. A dependency is a task that is
|
|
125
|
+
archived, and will be referenced by other tasks."""
|
|
126
|
+
async with self._condition:
|
|
127
|
+
self._task_id_list.append(dependency.id)
|
|
128
|
+
packet = Packet(
|
|
129
|
+
dependency, publisher_id, status=PacketStatus.ARCHIVED
|
|
130
|
+
)
|
|
131
|
+
self._task_dict[packet.task.id] = packet
|
|
132
|
+
self._condition.notify_all()
|
|
133
|
+
|
|
134
|
+
async def return_task(self, task_id: str) -> None:
|
|
135
|
+
r"""Return a task to the sender, indicating that the task has been
|
|
136
|
+
processed by the worker."""
|
|
137
|
+
async with self._condition:
|
|
138
|
+
packet = self._task_dict[task_id]
|
|
139
|
+
packet.status = PacketStatus.RETURNED
|
|
140
|
+
self._condition.notify_all()
|
|
141
|
+
|
|
142
|
+
async def archive_task(self, task_id: str) -> None:
|
|
143
|
+
r"""Archive a task in channel, making it to become a dependency."""
|
|
144
|
+
async with self._condition:
|
|
145
|
+
packet = self._task_dict[task_id]
|
|
146
|
+
packet.status = PacketStatus.ARCHIVED
|
|
147
|
+
self._condition.notify_all()
|
|
148
|
+
|
|
149
|
+
async def remove_task(self, task_id: str) -> None:
|
|
150
|
+
async with self._condition:
|
|
151
|
+
self._task_id_list.remove(task_id)
|
|
152
|
+
self._task_dict.pop(task_id)
|
|
153
|
+
self._condition.notify_all()
|
|
154
|
+
|
|
155
|
+
async def get_dependency_ids(self) -> List[str]:
|
|
156
|
+
async with self._condition:
|
|
157
|
+
dependency_ids = []
|
|
158
|
+
for task_id in self._task_id_list:
|
|
159
|
+
packet = self._task_dict[task_id]
|
|
160
|
+
if packet.status == PacketStatus.ARCHIVED:
|
|
161
|
+
dependency_ids.append(task_id)
|
|
162
|
+
return dependency_ids
|
|
163
|
+
|
|
164
|
+
async def get_task_by_id(self, task_id: str) -> Task:
|
|
165
|
+
async with self._condition:
|
|
166
|
+
if task_id not in self._task_id_list:
|
|
167
|
+
raise ValueError(f"Task {task_id} not found.")
|
|
168
|
+
return self._task_dict[task_id].task
|
|
169
|
+
|
|
170
|
+
async def print_channel(self):
|
|
171
|
+
async with self._condition:
|
|
172
|
+
print(self._task_dict)
|
|
173
|
+
print(self._task_id_list)
|
camel/workforce/utils.py
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
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
|
+
import re
|
|
15
|
+
from functools import wraps
|
|
16
|
+
from typing import Callable
|
|
17
|
+
|
|
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
|
+
|
|
25
|
+
|
|
26
|
+
# TODO: integrate structured response directly instead of parsing
|
|
27
|
+
def parse_create_node_resp(response: str) -> NodeConf:
|
|
28
|
+
r"""Parses the response of the new workforce creation from the manager
|
|
29
|
+
agent."""
|
|
30
|
+
config = re.search(r"(<workforce>.*</workforce>)", response, re.DOTALL)
|
|
31
|
+
if config is None:
|
|
32
|
+
raise ValueError("No workforce configuration found in the response.")
|
|
33
|
+
config_raw = config.group(1)
|
|
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 "",
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def parse_assign_task_resp(response: str) -> str:
|
|
58
|
+
r"""Parses the response of the task assignment from the manager agent."""
|
|
59
|
+
assignee_id = re.search(r"<id>(.*)</id>", response)
|
|
60
|
+
if assignee_id is None:
|
|
61
|
+
raise ValueError("No assignee found in the response.")
|
|
62
|
+
return assignee_id.group(1)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def parse_task_result_resp(response: str) -> str:
|
|
66
|
+
r"""Parses the result of the task from the signle agent workforce."""
|
|
67
|
+
task_result = re.search(r"<result>(.*)</result>", response, re.DOTALL)
|
|
68
|
+
failed_tag = re.search(r"<failed></failed>", response)
|
|
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)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def check_if_running(running: bool) -> Callable:
|
|
77
|
+
r"""Check if the workforce is (not) running, specified the boolean value.
|
|
78
|
+
If the workforce is not in the expected status, raise an exception.
|
|
79
|
+
|
|
80
|
+
Raises:
|
|
81
|
+
RuntimeError: If the workforce is not in the expected status.
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
def decorator(func):
|
|
85
|
+
@wraps(func)
|
|
86
|
+
def wrapper(self, *args, **kwargs):
|
|
87
|
+
if self._running != running:
|
|
88
|
+
status = "not running" if running else "running"
|
|
89
|
+
raise RuntimeError(
|
|
90
|
+
f"The workforce is {status}. Cannot perform the "
|
|
91
|
+
f"operation {func.__name__}."
|
|
92
|
+
)
|
|
93
|
+
return func(self, *args, **kwargs)
|
|
94
|
+
|
|
95
|
+
return wrapper
|
|
96
|
+
|
|
97
|
+
return decorator
|
|
@@ -0,0 +1,115 @@
|
|
|
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
|
+
from abc import ABC, abstractmethod
|
|
17
|
+
from typing import List
|
|
18
|
+
|
|
19
|
+
from colorama import Fore
|
|
20
|
+
|
|
21
|
+
from camel.tasks.task import Task, TaskState
|
|
22
|
+
from camel.workforce.base import BaseNode
|
|
23
|
+
from camel.workforce.task_channel import TaskChannel
|
|
24
|
+
from camel.workforce.utils import check_if_running
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class WorkerNode(BaseNode, ABC):
|
|
28
|
+
r"""A worker node that works on tasks. It is the basic unit of task
|
|
29
|
+
processing in the workforce system.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
description (str): Description of the node.
|
|
33
|
+
|
|
34
|
+
"""
|
|
35
|
+
|
|
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
|
+
def __init__(
|
|
40
|
+
self,
|
|
41
|
+
description: str,
|
|
42
|
+
) -> None:
|
|
43
|
+
super().__init__(description)
|
|
44
|
+
|
|
45
|
+
@abstractmethod
|
|
46
|
+
async def _process_task(
|
|
47
|
+
self, task: Task, dependencies: List[Task]
|
|
48
|
+
) -> TaskState:
|
|
49
|
+
r"""Processes a task based on its dependencies.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
'DONE' if the task is successfully processed,
|
|
53
|
+
'FAILED' if the processing fails.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
async def _get_assigned_task(self) -> Task:
|
|
57
|
+
r"""Get the task assigned to this node from the channel."""
|
|
58
|
+
return await self._channel.get_assigned_task_by_assignee(self.node_id)
|
|
59
|
+
|
|
60
|
+
@staticmethod
|
|
61
|
+
def _get_dep_tasks_info(dependencies: List[Task]) -> str:
|
|
62
|
+
result_lines = [
|
|
63
|
+
f"id: {dep_task.id}, content: {dep_task.content}. "
|
|
64
|
+
f"result: {dep_task.result}."
|
|
65
|
+
for dep_task in dependencies
|
|
66
|
+
]
|
|
67
|
+
result_str = "\n".join(result_lines)
|
|
68
|
+
return result_str
|
|
69
|
+
|
|
70
|
+
@check_if_running(False)
|
|
71
|
+
def set_channel(self, channel: TaskChannel):
|
|
72
|
+
self._channel = channel
|
|
73
|
+
|
|
74
|
+
@check_if_running(False)
|
|
75
|
+
async def _listen_to_channel(self):
|
|
76
|
+
"""Continuously listen to the channel, process the task that are
|
|
77
|
+
assigned to this node, and update the result and status of the task.
|
|
78
|
+
|
|
79
|
+
This method should be run in an event loop, as it will run
|
|
80
|
+
indefinitely.
|
|
81
|
+
"""
|
|
82
|
+
self._running = True
|
|
83
|
+
print(f"{Fore.GREEN}Worker node {self.node_id} started.{Fore.RESET}")
|
|
84
|
+
|
|
85
|
+
while True:
|
|
86
|
+
# get the earliest task assigned to this node
|
|
87
|
+
task = await self._get_assigned_task()
|
|
88
|
+
print(
|
|
89
|
+
f'worker node {self.node_id} get task:',
|
|
90
|
+
task.id,
|
|
91
|
+
task.content,
|
|
92
|
+
)
|
|
93
|
+
# get the Task instance of dependencies
|
|
94
|
+
dependency_ids = await self._channel.get_dependency_ids()
|
|
95
|
+
task_dependencies = [
|
|
96
|
+
await self._channel.get_task_by_id(dep_id)
|
|
97
|
+
for dep_id in dependency_ids
|
|
98
|
+
]
|
|
99
|
+
|
|
100
|
+
# process the task
|
|
101
|
+
task_state = await self._process_task(task, task_dependencies)
|
|
102
|
+
|
|
103
|
+
# update the result and status of the task
|
|
104
|
+
task.set_state(task_state)
|
|
105
|
+
|
|
106
|
+
await self._channel.return_task(task.id)
|
|
107
|
+
|
|
108
|
+
@check_if_running(False)
|
|
109
|
+
async def start(self):
|
|
110
|
+
await self._listen_to_channel()
|
|
111
|
+
|
|
112
|
+
@check_if_running(True)
|
|
113
|
+
def stop(self):
|
|
114
|
+
self._running = False
|
|
115
|
+
return
|
|
@@ -0,0 +1,49 @@
|
|
|
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
|
+
import asyncio
|
|
15
|
+
|
|
16
|
+
from camel.tasks import Task
|
|
17
|
+
from camel.workforce.manager_node import ManagerNode
|
|
18
|
+
from camel.workforce.task_channel import TaskChannel
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Workforce:
|
|
22
|
+
r"""A class representing a workforce system.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
name (str, optional): The name of the workforce system. Defaults to
|
|
26
|
+
`"CAMEL Workforce"`.
|
|
27
|
+
description (str, optional): A description of the workforce system.
|
|
28
|
+
Defaults to `"A workforce system for managing tasks."`.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
root_node: ManagerNode,
|
|
34
|
+
name: str = "CAMEL Workforce",
|
|
35
|
+
description: str = "A workforce system for managing tasks.",
|
|
36
|
+
) -> None:
|
|
37
|
+
self.name = name
|
|
38
|
+
self.description = description
|
|
39
|
+
self._root_node = root_node
|
|
40
|
+
|
|
41
|
+
def process_task(self, task: Task) -> Task:
|
|
42
|
+
self._root_node.set_main_task(task)
|
|
43
|
+
shared_channel = TaskChannel()
|
|
44
|
+
self._root_node.set_channel(shared_channel)
|
|
45
|
+
|
|
46
|
+
# start the root workforce
|
|
47
|
+
asyncio.run(self._root_node.start())
|
|
48
|
+
|
|
49
|
+
return task
|