camel-ai 0.2.73a4__py3-none-any.whl → 0.2.80a2__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.
Files changed (173) hide show
  1. camel/__init__.py +1 -1
  2. camel/agents/_utils.py +38 -0
  3. camel/agents/chat_agent.py +2217 -519
  4. camel/agents/mcp_agent.py +30 -27
  5. camel/configs/__init__.py +15 -0
  6. camel/configs/aihubmix_config.py +88 -0
  7. camel/configs/amd_config.py +70 -0
  8. camel/configs/cometapi_config.py +104 -0
  9. camel/configs/minimax_config.py +93 -0
  10. camel/configs/nebius_config.py +103 -0
  11. camel/data_collectors/alpaca_collector.py +15 -6
  12. camel/datasets/base_generator.py +39 -10
  13. camel/environments/single_step.py +28 -3
  14. camel/environments/tic_tac_toe.py +1 -1
  15. camel/interpreters/__init__.py +2 -0
  16. camel/interpreters/docker/Dockerfile +3 -12
  17. camel/interpreters/e2b_interpreter.py +34 -1
  18. camel/interpreters/microsandbox_interpreter.py +395 -0
  19. camel/loaders/__init__.py +11 -2
  20. camel/loaders/chunkr_reader.py +9 -0
  21. camel/memories/agent_memories.py +48 -4
  22. camel/memories/base.py +26 -0
  23. camel/memories/blocks/chat_history_block.py +122 -4
  24. camel/memories/context_creators/score_based.py +25 -384
  25. camel/memories/records.py +88 -8
  26. camel/messages/base.py +153 -34
  27. camel/models/__init__.py +10 -0
  28. camel/models/aihubmix_model.py +83 -0
  29. camel/models/aiml_model.py +1 -16
  30. camel/models/amd_model.py +101 -0
  31. camel/models/anthropic_model.py +6 -19
  32. camel/models/aws_bedrock_model.py +2 -33
  33. camel/models/azure_openai_model.py +114 -89
  34. camel/models/base_audio_model.py +3 -1
  35. camel/models/base_model.py +32 -14
  36. camel/models/cohere_model.py +1 -16
  37. camel/models/cometapi_model.py +83 -0
  38. camel/models/crynux_model.py +1 -16
  39. camel/models/deepseek_model.py +1 -16
  40. camel/models/fish_audio_model.py +6 -0
  41. camel/models/gemini_model.py +36 -18
  42. camel/models/groq_model.py +1 -17
  43. camel/models/internlm_model.py +1 -16
  44. camel/models/litellm_model.py +1 -16
  45. camel/models/lmstudio_model.py +1 -17
  46. camel/models/minimax_model.py +83 -0
  47. camel/models/mistral_model.py +1 -16
  48. camel/models/model_factory.py +27 -1
  49. camel/models/modelscope_model.py +1 -16
  50. camel/models/moonshot_model.py +105 -24
  51. camel/models/nebius_model.py +83 -0
  52. camel/models/nemotron_model.py +0 -5
  53. camel/models/netmind_model.py +1 -16
  54. camel/models/novita_model.py +1 -16
  55. camel/models/nvidia_model.py +1 -16
  56. camel/models/ollama_model.py +4 -19
  57. camel/models/openai_compatible_model.py +62 -41
  58. camel/models/openai_model.py +62 -57
  59. camel/models/openrouter_model.py +1 -17
  60. camel/models/ppio_model.py +1 -16
  61. camel/models/qianfan_model.py +1 -16
  62. camel/models/qwen_model.py +1 -16
  63. camel/models/reka_model.py +1 -16
  64. camel/models/samba_model.py +34 -47
  65. camel/models/sglang_model.py +64 -31
  66. camel/models/siliconflow_model.py +1 -16
  67. camel/models/stub_model.py +0 -4
  68. camel/models/togetherai_model.py +1 -16
  69. camel/models/vllm_model.py +1 -16
  70. camel/models/volcano_model.py +0 -17
  71. camel/models/watsonx_model.py +1 -16
  72. camel/models/yi_model.py +1 -16
  73. camel/models/zhipuai_model.py +60 -16
  74. camel/parsers/__init__.py +18 -0
  75. camel/parsers/mcp_tool_call_parser.py +176 -0
  76. camel/retrievers/auto_retriever.py +1 -0
  77. camel/runtimes/daytona_runtime.py +11 -12
  78. camel/societies/__init__.py +2 -0
  79. camel/societies/workforce/__init__.py +2 -0
  80. camel/societies/workforce/events.py +122 -0
  81. camel/societies/workforce/prompts.py +146 -66
  82. camel/societies/workforce/role_playing_worker.py +15 -11
  83. camel/societies/workforce/single_agent_worker.py +302 -65
  84. camel/societies/workforce/structured_output_handler.py +30 -18
  85. camel/societies/workforce/task_channel.py +163 -27
  86. camel/societies/workforce/utils.py +107 -13
  87. camel/societies/workforce/workflow_memory_manager.py +772 -0
  88. camel/societies/workforce/workforce.py +1949 -579
  89. camel/societies/workforce/workforce_callback.py +74 -0
  90. camel/societies/workforce/workforce_logger.py +168 -145
  91. camel/societies/workforce/workforce_metrics.py +33 -0
  92. camel/storages/key_value_storages/json.py +15 -2
  93. camel/storages/key_value_storages/mem0_cloud.py +48 -47
  94. camel/storages/object_storages/google_cloud.py +1 -1
  95. camel/storages/vectordb_storages/oceanbase.py +13 -13
  96. camel/storages/vectordb_storages/qdrant.py +3 -3
  97. camel/storages/vectordb_storages/tidb.py +8 -6
  98. camel/tasks/task.py +4 -3
  99. camel/toolkits/__init__.py +20 -7
  100. camel/toolkits/aci_toolkit.py +45 -0
  101. camel/toolkits/base.py +6 -4
  102. camel/toolkits/code_execution.py +28 -1
  103. camel/toolkits/context_summarizer_toolkit.py +684 -0
  104. camel/toolkits/dappier_toolkit.py +5 -1
  105. camel/toolkits/dingtalk.py +1135 -0
  106. camel/toolkits/edgeone_pages_mcp_toolkit.py +11 -31
  107. camel/toolkits/excel_toolkit.py +1 -1
  108. camel/toolkits/{file_write_toolkit.py → file_toolkit.py} +430 -36
  109. camel/toolkits/function_tool.py +13 -3
  110. camel/toolkits/github_toolkit.py +104 -17
  111. camel/toolkits/gmail_toolkit.py +1839 -0
  112. camel/toolkits/google_calendar_toolkit.py +38 -4
  113. camel/toolkits/google_drive_mcp_toolkit.py +12 -31
  114. camel/toolkits/hybrid_browser_toolkit/config_loader.py +15 -0
  115. camel/toolkits/hybrid_browser_toolkit/hybrid_browser_toolkit.py +77 -8
  116. camel/toolkits/hybrid_browser_toolkit/hybrid_browser_toolkit_ts.py +884 -88
  117. camel/toolkits/hybrid_browser_toolkit/installer.py +203 -0
  118. camel/toolkits/hybrid_browser_toolkit/ts/package-lock.json +5 -612
  119. camel/toolkits/hybrid_browser_toolkit/ts/package.json +0 -1
  120. camel/toolkits/hybrid_browser_toolkit/ts/src/browser-session.ts +959 -89
  121. camel/toolkits/hybrid_browser_toolkit/ts/src/config-loader.ts +9 -2
  122. camel/toolkits/hybrid_browser_toolkit/ts/src/hybrid-browser-toolkit.ts +281 -213
  123. camel/toolkits/hybrid_browser_toolkit/ts/src/parent-child-filter.ts +226 -0
  124. camel/toolkits/hybrid_browser_toolkit/ts/src/snapshot-parser.ts +219 -0
  125. camel/toolkits/hybrid_browser_toolkit/ts/src/som-screenshot-injected.ts +543 -0
  126. camel/toolkits/hybrid_browser_toolkit/ts/src/types.ts +23 -3
  127. camel/toolkits/hybrid_browser_toolkit/ts/websocket-server.js +72 -7
  128. camel/toolkits/hybrid_browser_toolkit/ws_wrapper.py +582 -132
  129. camel/toolkits/hybrid_browser_toolkit_py/actions.py +158 -0
  130. camel/toolkits/hybrid_browser_toolkit_py/browser_session.py +55 -8
  131. camel/toolkits/hybrid_browser_toolkit_py/config_loader.py +43 -0
  132. camel/toolkits/hybrid_browser_toolkit_py/hybrid_browser_toolkit.py +321 -8
  133. camel/toolkits/hybrid_browser_toolkit_py/snapshot.py +10 -4
  134. camel/toolkits/hybrid_browser_toolkit_py/unified_analyzer.js +45 -4
  135. camel/toolkits/{openai_image_toolkit.py → image_generation_toolkit.py} +151 -53
  136. camel/toolkits/klavis_toolkit.py +5 -1
  137. camel/toolkits/markitdown_toolkit.py +27 -1
  138. camel/toolkits/math_toolkit.py +64 -10
  139. camel/toolkits/mcp_toolkit.py +366 -71
  140. camel/toolkits/memory_toolkit.py +5 -1
  141. camel/toolkits/message_integration.py +18 -13
  142. camel/toolkits/minimax_mcp_toolkit.py +195 -0
  143. camel/toolkits/note_taking_toolkit.py +19 -10
  144. camel/toolkits/notion_mcp_toolkit.py +16 -26
  145. camel/toolkits/openbb_toolkit.py +5 -1
  146. camel/toolkits/origene_mcp_toolkit.py +8 -49
  147. camel/toolkits/playwright_mcp_toolkit.py +12 -31
  148. camel/toolkits/resend_toolkit.py +168 -0
  149. camel/toolkits/search_toolkit.py +264 -91
  150. camel/toolkits/slack_toolkit.py +64 -10
  151. camel/toolkits/terminal_toolkit/__init__.py +18 -0
  152. camel/toolkits/terminal_toolkit/terminal_toolkit.py +957 -0
  153. camel/toolkits/terminal_toolkit/utils.py +532 -0
  154. camel/toolkits/vertex_ai_veo_toolkit.py +590 -0
  155. camel/toolkits/video_analysis_toolkit.py +17 -11
  156. camel/toolkits/wechat_official_toolkit.py +483 -0
  157. camel/toolkits/zapier_toolkit.py +5 -1
  158. camel/types/__init__.py +2 -2
  159. camel/types/enums.py +274 -7
  160. camel/types/openai_types.py +2 -2
  161. camel/types/unified_model_type.py +15 -0
  162. camel/utils/commons.py +36 -5
  163. camel/utils/constants.py +3 -0
  164. camel/utils/context_utils.py +1003 -0
  165. camel/utils/mcp.py +138 -4
  166. camel/utils/token_counting.py +43 -20
  167. {camel_ai-0.2.73a4.dist-info → camel_ai-0.2.80a2.dist-info}/METADATA +223 -83
  168. {camel_ai-0.2.73a4.dist-info → camel_ai-0.2.80a2.dist-info}/RECORD +170 -141
  169. camel/loaders/pandas_reader.py +0 -368
  170. camel/toolkits/openai_agent_toolkit.py +0 -135
  171. camel/toolkits/terminal_toolkit.py +0 -1550
  172. {camel_ai-0.2.73a4.dist-info → camel_ai-0.2.80a2.dist-info}/WHEEL +0 -0
  173. {camel_ai-0.2.73a4.dist-info → camel_ai-0.2.80a2.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,74 @@
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
+ from __future__ import annotations
15
+
16
+ from abc import ABC, abstractmethod
17
+
18
+ from .events import (
19
+ AllTasksCompletedEvent,
20
+ TaskAssignedEvent,
21
+ TaskCompletedEvent,
22
+ TaskCreatedEvent,
23
+ TaskDecomposedEvent,
24
+ TaskFailedEvent,
25
+ TaskStartedEvent,
26
+ WorkerCreatedEvent,
27
+ WorkerDeletedEvent,
28
+ )
29
+
30
+
31
+ class WorkforceCallback(ABC):
32
+ r"""Interface for recording workforce lifecycle events.
33
+
34
+ Implementations should persist or stream events as appropriate.
35
+ """
36
+
37
+ @abstractmethod
38
+ def log_task_created(
39
+ self,
40
+ event: TaskCreatedEvent,
41
+ ) -> None:
42
+ pass
43
+
44
+ @abstractmethod
45
+ def log_task_decomposed(self, event: TaskDecomposedEvent) -> None:
46
+ pass
47
+
48
+ @abstractmethod
49
+ def log_task_assigned(self, event: TaskAssignedEvent) -> None:
50
+ pass
51
+
52
+ @abstractmethod
53
+ def log_task_started(self, event: TaskStartedEvent) -> None:
54
+ pass
55
+
56
+ @abstractmethod
57
+ def log_task_completed(self, event: TaskCompletedEvent) -> None:
58
+ pass
59
+
60
+ @abstractmethod
61
+ def log_task_failed(self, event: TaskFailedEvent) -> None:
62
+ pass
63
+
64
+ @abstractmethod
65
+ def log_worker_created(self, event: WorkerCreatedEvent) -> None:
66
+ pass
67
+
68
+ @abstractmethod
69
+ def log_worker_deleted(self, event: WorkerDeletedEvent) -> None:
70
+ pass
71
+
72
+ @abstractmethod
73
+ def log_all_tasks_completed(self, event: AllTasksCompletedEvent) -> None:
74
+ pass
@@ -16,12 +16,26 @@ from datetime import datetime, timezone
16
16
  from typing import Any, Dict, List, Optional
17
17
 
18
18
  from camel.logger import get_logger
19
+ from camel.societies.workforce.events import (
20
+ AllTasksCompletedEvent,
21
+ QueueStatusEvent,
22
+ TaskAssignedEvent,
23
+ TaskCompletedEvent,
24
+ TaskCreatedEvent,
25
+ TaskDecomposedEvent,
26
+ TaskFailedEvent,
27
+ TaskStartedEvent,
28
+ WorkerCreatedEvent,
29
+ WorkerDeletedEvent,
30
+ )
31
+ from camel.societies.workforce.workforce_callback import WorkforceCallback
32
+ from camel.societies.workforce.workforce_metrics import WorkforceMetrics
19
33
  from camel.types.agents import ToolCallingRecord
20
34
 
21
35
  logger = get_logger(__name__)
22
36
 
23
37
 
24
- class WorkforceLogger:
38
+ class WorkforceLogger(WorkforceCallback, WorkforceMetrics):
25
39
  r"""Logs events and metrics for a Workforce instance."""
26
40
 
27
41
  def __init__(self, workforce_id: str):
@@ -55,195 +69,201 @@ class WorkforceLogger:
55
69
 
56
70
  def log_task_created(
57
71
  self,
58
- task_id: str,
59
- description: str,
60
- parent_task_id: Optional[str] = None,
61
- task_type: Optional[str] = None,
62
- metadata: Optional[Dict[str, Any]] = None,
72
+ event: TaskCreatedEvent,
63
73
  ) -> None:
64
74
  r"""Logs the creation of a new task."""
65
75
  self._log_event(
66
- 'task_created',
67
- task_id=task_id,
68
- description=description,
69
- parent_task_id=parent_task_id,
70
- task_type=task_type,
71
- metadata=metadata or {},
76
+ event_type=event.event_type,
77
+ task_id=event.task_id,
78
+ description=event.description,
79
+ parent_task_id=event.parent_task_id,
80
+ task_type=event.task_type,
81
+ metadata=event.metadata or {},
72
82
  )
73
- self._task_hierarchy[task_id] = {
74
- 'parent': parent_task_id,
83
+ self._task_hierarchy[event.task_id] = {
84
+ 'parent': event.parent_task_id,
75
85
  'children': [],
76
86
  'status': 'created',
77
- 'description': description,
87
+ 'description': event.description,
78
88
  'assigned_to': None,
79
- **(metadata or {}),
89
+ **(event.metadata or {}),
80
90
  }
81
- if parent_task_id and parent_task_id in self._task_hierarchy:
82
- self._task_hierarchy[parent_task_id]['children'].append(task_id)
91
+ if (
92
+ event.parent_task_id
93
+ and event.parent_task_id in self._task_hierarchy
94
+ ):
95
+ self._task_hierarchy[event.parent_task_id]['children'].append(
96
+ event.task_id
97
+ )
83
98
 
84
99
  def log_task_decomposed(
85
100
  self,
86
- parent_task_id: str,
87
- subtask_ids: List[str],
88
- metadata: Optional[Dict[str, Any]] = None,
101
+ event: TaskDecomposedEvent,
89
102
  ) -> None:
90
103
  r"""Logs the decomposition of a task into subtasks."""
91
104
  self._log_event(
92
- 'task_decomposed',
93
- parent_task_id=parent_task_id,
94
- subtask_ids=subtask_ids,
95
- metadata=metadata or {},
105
+ event_type=event.event_type,
106
+ parent_task_id=event.parent_task_id,
107
+ subtask_ids=event.subtask_ids,
108
+ metadata=event.metadata or {},
96
109
  )
97
- if parent_task_id in self._task_hierarchy:
98
- self._task_hierarchy[parent_task_id]['status'] = "decomposed"
110
+ if event.parent_task_id in self._task_hierarchy:
111
+ self._task_hierarchy[event.parent_task_id]['status'] = "decomposed"
99
112
 
100
113
  def log_task_assigned(
101
114
  self,
102
- task_id: str,
103
- worker_id: str,
104
- queue_time_seconds: Optional[float] = None,
105
- dependencies: Optional[List[str]] = None,
106
- metadata: Optional[Dict[str, Any]] = None,
115
+ event: TaskAssignedEvent,
107
116
  ) -> None:
108
117
  r"""Logs the assignment of a task to a worker."""
109
118
  self._log_event(
110
- 'task_assigned',
111
- task_id=task_id,
112
- worker_id=worker_id,
113
- queue_time_seconds=queue_time_seconds,
114
- dependencies=dependencies or [],
115
- metadata=metadata or {},
119
+ event_type=event.event_type,
120
+ task_id=event.task_id,
121
+ worker_id=event.worker_id,
122
+ queue_time_seconds=event.queue_time_seconds,
123
+ dependencies=event.dependencies or [],
124
+ metadata=event.metadata or {},
116
125
  )
117
- if task_id in self._task_hierarchy:
118
- self._task_hierarchy[task_id]['status'] = 'assigned'
119
- self._task_hierarchy[task_id]['assigned_to'] = worker_id
120
- self._task_hierarchy[task_id]['dependencies'] = dependencies or []
121
- if worker_id in self._worker_information:
122
- self._worker_information[worker_id]['current_task_id'] = task_id
123
- self._worker_information[worker_id]['status'] = 'busy'
126
+ if event.task_id in self._task_hierarchy:
127
+ self._task_hierarchy[event.task_id]['status'] = 'assigned'
128
+ self._task_hierarchy[event.task_id]['assigned_to'] = (
129
+ event.worker_id
130
+ )
131
+ self._task_hierarchy[event.task_id]['dependencies'] = (
132
+ event.dependencies or []
133
+ )
134
+ if event.worker_id in self._worker_information:
135
+ self._worker_information[event.worker_id]['current_task_id'] = (
136
+ event.task_id
137
+ )
138
+ self._worker_information[event.worker_id]['status'] = 'busy'
124
139
 
125
140
  def log_task_started(
126
141
  self,
127
- task_id: str,
128
- worker_id: str,
129
- metadata: Optional[Dict[str, Any]] = None,
142
+ event: TaskStartedEvent,
130
143
  ) -> None:
131
144
  r"""Logs when a worker starts processing a task."""
132
145
  self._log_event(
133
- 'task_started',
134
- task_id=task_id,
135
- worker_id=worker_id,
136
- metadata=metadata or {},
146
+ event_type=event.event_type,
147
+ task_id=event.task_id,
148
+ worker_id=event.worker_id,
149
+ metadata=event.metadata or {},
137
150
  )
138
- if task_id in self._task_hierarchy:
139
- self._task_hierarchy[task_id]['status'] = 'processing'
151
+ if event.task_id in self._task_hierarchy:
152
+ self._task_hierarchy[event.task_id]['status'] = 'processing'
140
153
 
141
- def log_task_completed(
142
- self,
143
- task_id: str,
144
- worker_id: str,
145
- result_summary: Optional[str] = None,
146
- processing_time_seconds: Optional[float] = None,
147
- token_usage: Optional[Dict[str, int]] = None,
148
- metadata: Optional[Dict[str, Any]] = None,
149
- ) -> None:
154
+ def log_task_completed(self, event: TaskCompletedEvent) -> None:
150
155
  r"""Logs the successful completion of a task."""
151
156
  self._log_event(
152
- 'task_completed',
153
- task_id=task_id,
154
- worker_id=worker_id,
155
- result_summary=result_summary,
156
- processing_time_seconds=processing_time_seconds,
157
- token_usage=token_usage or {},
158
- metadata=metadata or {},
157
+ event_type=event.event_type,
158
+ task_id=event.task_id,
159
+ worker_id=event.worker_id,
160
+ result_summary=event.result_summary,
161
+ processing_time_seconds=event.processing_time_seconds,
162
+ token_usage=event.token_usage or {},
163
+ metadata=event.metadata or {},
159
164
  )
160
- if task_id in self._task_hierarchy:
161
- self._task_hierarchy[task_id]['status'] = 'completed'
162
- self._task_hierarchy[task_id]['assigned_to'] = None
165
+ if event.task_id in self._task_hierarchy:
166
+ self._task_hierarchy[event.task_id]['status'] = 'completed'
167
+ self._task_hierarchy[event.task_id]['assigned_to'] = None
163
168
  # Store processing time in task hierarchy for display in tree
164
- if processing_time_seconds is not None:
165
- self._task_hierarchy[task_id]['completion_time_seconds'] = (
166
- processing_time_seconds
167
- )
169
+ if event.processing_time_seconds is not None:
170
+ self._task_hierarchy[event.task_id][
171
+ 'completion_time_seconds'
172
+ ] = event.processing_time_seconds
168
173
  # Store token usage in task hierarchy for display in tree
169
- if token_usage is not None:
170
- self._task_hierarchy[task_id]['token_usage'] = token_usage
171
- if worker_id in self._worker_information:
172
- self._worker_information[worker_id]['current_task_id'] = None
173
- self._worker_information[worker_id]['status'] = 'idle'
174
- self._worker_information[worker_id]['tasks_completed'] = (
175
- self._worker_information[worker_id].get('tasks_completed', 0)
174
+ if event.token_usage is not None:
175
+ self._task_hierarchy[event.task_id]['token_usage'] = (
176
+ event.token_usage
177
+ )
178
+ if event.worker_id in self._worker_information:
179
+ self._worker_information[event.worker_id]['current_task_id'] = None
180
+ self._worker_information[event.worker_id]['status'] = 'idle'
181
+ self._worker_information[event.worker_id]['tasks_completed'] = (
182
+ self._worker_information[event.worker_id].get(
183
+ 'tasks_completed', 0
184
+ )
176
185
  + 1
177
186
  )
178
187
 
179
188
  def log_task_failed(
180
189
  self,
181
- task_id: str,
182
- error_message: str,
183
- worker_id: Optional[str] = None,
184
- metadata: Optional[Dict[str, Any]] = None,
190
+ event: TaskFailedEvent,
185
191
  ) -> None:
186
192
  r"""Logs the failure of a task."""
187
193
  self._log_event(
188
- 'task_failed',
189
- task_id=task_id,
190
- worker_id=worker_id,
191
- error_message=error_message,
192
- metadata=metadata or {},
194
+ event_type=event.event_type,
195
+ task_id=event.task_id,
196
+ worker_id=event.worker_id,
197
+ error_message=event.error_message,
198
+ metadata=event.metadata or {},
193
199
  )
194
- if task_id in self._task_hierarchy:
195
- self._task_hierarchy[task_id]['status'] = 'failed'
196
- self._task_hierarchy[task_id]['error'] = error_message
197
- self._task_hierarchy[task_id]['assigned_to'] = None
198
- if worker_id and worker_id in self._worker_information:
199
- self._worker_information[worker_id]['current_task_id'] = None
200
- self._worker_information[worker_id]['status'] = 'idle'
201
- self._worker_information[worker_id]['tasks_failed'] = (
202
- self._worker_information[worker_id].get('tasks_failed', 0) + 1
200
+ if event.task_id in self._task_hierarchy:
201
+ self._task_hierarchy[event.task_id]['status'] = 'failed'
202
+ self._task_hierarchy[event.task_id]['error'] = event.error_message
203
+ self._task_hierarchy[event.task_id]['assigned_to'] = None
204
+ if event.worker_id and event.worker_id in self._worker_information:
205
+ self._worker_information[event.worker_id]['current_task_id'] = None
206
+ self._worker_information[event.worker_id]['status'] = 'idle'
207
+ self._worker_information[event.worker_id]['tasks_failed'] = (
208
+ self._worker_information[event.worker_id].get(
209
+ 'tasks_failed', 0
210
+ )
211
+ + 1
203
212
  )
204
213
 
205
214
  def log_worker_created(
206
215
  self,
207
- worker_id: str,
208
- worker_type: str,
209
- role: str,
210
- metadata: Optional[Dict[str, Any]] = None,
216
+ event: WorkerCreatedEvent,
211
217
  ) -> None:
212
218
  r"""Logs the creation of a new worker."""
213
219
  self._log_event(
214
- 'worker_created',
215
- worker_id=worker_id,
216
- worker_type=worker_type,
217
- role=role,
218
- metadata=metadata or {},
220
+ event_type=event.event_type,
221
+ worker_id=event.worker_id,
222
+ worker_type=event.worker_type,
223
+ role=event.role,
224
+ metadata=event.metadata or {},
219
225
  )
220
- self._worker_information[worker_id] = {
221
- 'type': worker_type,
222
- 'role': role,
226
+ self._worker_information[event.worker_id] = {
227
+ 'type': event.worker_type,
228
+ 'role': event.role,
223
229
  'status': 'idle',
224
230
  'current_task_id': None,
225
231
  'tasks_completed': 0,
226
232
  'tasks_failed': 0,
227
- **(metadata or {}),
233
+ **(event.metadata or {}),
228
234
  }
229
235
 
230
236
  def log_worker_deleted(
231
237
  self,
232
- worker_id: str,
233
- reason: Optional[str] = None,
234
- metadata: Optional[Dict[str, Any]] = None,
238
+ event: WorkerDeletedEvent,
235
239
  ) -> None:
236
240
  r"""Logs the deletion of a worker."""
237
241
  self._log_event(
238
- 'worker_deleted',
239
- worker_id=worker_id,
240
- reason=reason,
241
- metadata=metadata or {},
242
+ event_type=event.event_type,
243
+ worker_id=event.worker_id,
244
+ reason=event.reason,
245
+ metadata=event.metadata or {},
242
246
  )
243
- if worker_id in self._worker_information:
244
- self._worker_information[worker_id]['status'] = 'deleted'
247
+ if event.worker_id in self._worker_information:
248
+ self._worker_information[event.worker_id]['status'] = 'deleted'
245
249
  # Or del self._worker_information[worker_id]
246
250
 
251
+ def log_queue_status(
252
+ self,
253
+ event: QueueStatusEvent,
254
+ ) -> None:
255
+ r"""Logs the status of a task queue."""
256
+ self._log_event(
257
+ event_type=event.event_type,
258
+ queue_name=event.queue_name,
259
+ length=event.length,
260
+ pending_task_ids=event.pending_task_ids or [],
261
+ metadata=event.metadata or {},
262
+ )
263
+
264
+ def log_all_tasks_completed(self, event: AllTasksCompletedEvent) -> None:
265
+ pass
266
+
247
267
  def reset_task_data(self) -> None:
248
268
  r"""Resets logs and data related to tasks, preserving worker
249
269
  information.
@@ -263,22 +283,6 @@ class WorkforceLogger:
263
283
  f"{self.workforce_id}"
264
284
  )
265
285
 
266
- def log_queue_status(
267
- self,
268
- queue_name: str,
269
- length: int,
270
- pending_task_ids: Optional[List[str]] = None,
271
- metadata: Optional[Dict[str, Any]] = None,
272
- ) -> None:
273
- r"""Logs the status of a task queue."""
274
- self._log_event(
275
- 'queue_status',
276
- queue_name=queue_name,
277
- length=length,
278
- pending_task_ids=pending_task_ids or [],
279
- metadata=metadata or {},
280
- )
281
-
282
286
  def dump_to_json(self, file_path: str) -> None:
283
287
  r"""Dumps all log entries to a JSON file.
284
288
 
@@ -495,6 +499,11 @@ class WorkforceLogger:
495
499
 
496
500
  tasks_handled_by_worker: Dict[str, int] = {}
497
501
 
502
+ # Track unique task final states to avoid double-counting
503
+ task_final_states: Dict[
504
+ str, str
505
+ ] = {} # task_id -> 'completed' or 'failed'
506
+
498
507
  # Helper function to check if a task is the main task (has no parent)
499
508
  def is_main_task(task_id: str) -> bool:
500
509
  return (
@@ -528,7 +537,9 @@ class WorkforceLogger:
528
537
  elif event_type == 'task_completed':
529
538
  # Exclude main task from total count
530
539
  if not is_main_task(task_id):
531
- kpis['total_tasks_completed'] += 1
540
+ # Track final state - a completed task overwrites any
541
+ # previous failed state
542
+ task_final_states[task_id] = 'completed'
532
543
  # Count tasks handled by worker (only for non-main tasks)
533
544
  if 'worker_id' in entry and entry['worker_id'] is not None:
534
545
  worker_id = entry['worker_id']
@@ -550,7 +561,11 @@ class WorkforceLogger:
550
561
  elif event_type == 'task_failed':
551
562
  # Exclude main task from total count
552
563
  if not is_main_task(task_id):
553
- kpis['total_tasks_failed'] += 1
564
+ # Only track as failed if not already completed
565
+ # (in case of retries, the final completion overwrites
566
+ # failed state)
567
+ if task_final_states.get(task_id) != 'completed':
568
+ task_final_states[task_id] = 'failed'
554
569
  # Count tasks handled by worker (only for non-main tasks)
555
570
  if 'worker_id' in entry and entry['worker_id'] is not None:
556
571
  worker_id = entry['worker_id']
@@ -565,6 +580,14 @@ class WorkforceLogger:
565
580
  kpis['total_workforce_running_time_seconds'] = (
566
581
  last_timestamp - first_timestamp
567
582
  ).total_seconds()
583
+
584
+ # Count unique tasks by final state
585
+ for _task_id, state in task_final_states.items():
586
+ if state == 'completed':
587
+ kpis['total_tasks_completed'] += 1
588
+ elif state == 'failed':
589
+ kpis['total_tasks_failed'] += 1
590
+
568
591
  # Calculate worker utilization based on proportion of tasks handled
569
592
  total_tasks_processed_for_utilization = (
570
593
  kpis['total_tasks_completed'] + kpis['total_tasks_failed']
@@ -610,9 +633,9 @@ class WorkforceLogger:
610
633
 
611
634
  kpis['total_workers_created'] = len(self._worker_information)
612
635
 
613
- # Current pending tasks (simplified)
614
- kpis['current_pending_tasks'] = kpis['total_tasks_created'] - (
615
- kpis['total_tasks_completed'] + kpis['total_tasks_failed']
636
+ # Current pending tasks - tasks created but not yet completed or failed
637
+ kpis['current_pending_tasks'] = kpis['total_tasks_created'] - len(
638
+ task_final_states
616
639
  )
617
640
 
618
641
  return kpis
@@ -0,0 +1,33 @@
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
+ from abc import ABC, abstractmethod
15
+ from typing import Any, Dict
16
+
17
+
18
+ class WorkforceMetrics(ABC):
19
+ @abstractmethod
20
+ def reset_task_data(self) -> None:
21
+ pass
22
+
23
+ @abstractmethod
24
+ def dump_to_json(self, file_path: str) -> None:
25
+ pass
26
+
27
+ @abstractmethod
28
+ def get_ascii_tree_representation(self) -> str:
29
+ pass
30
+
31
+ @abstractmethod
32
+ def get_kpis(self) -> Dict[str, Any]:
33
+ pass
@@ -17,6 +17,8 @@ from enum import EnumMeta
17
17
  from pathlib import Path
18
18
  from typing import Any, ClassVar, Dict, List, Optional
19
19
 
20
+ from pydantic import BaseModel
21
+
20
22
  from camel.storages.key_value_storages import BaseKeyValueStorage
21
23
  from camel.types import (
22
24
  ModelType,
@@ -27,8 +29,13 @@ from camel.types import (
27
29
 
28
30
 
29
31
  class CamelJSONEncoder(json.JSONEncoder):
30
- r"""A custom JSON encoder for serializing specifically enumerated types.
31
- Ensures enumerated types can be stored in and retrieved from JSON format.
32
+ r"""A custom JSON encoder for serializing CAMEL-specific types.
33
+
34
+ Handles serialization of:
35
+ - Enumerated types (RoleType, TaskType, ModelType, OpenAIBackendRole)
36
+ - Pydantic BaseModel objects (from structured outputs)
37
+
38
+ Ensures these types can be stored in and retrieved from JSON format.
32
39
  """
33
40
 
34
41
  CAMEL_ENUMS: ClassVar[Dict[str, EnumMeta]] = {
@@ -39,8 +46,14 @@ class CamelJSONEncoder(json.JSONEncoder):
39
46
  }
40
47
 
41
48
  def default(self, obj) -> Any:
49
+ # Handle CAMEL enum types
42
50
  if type(obj) in self.CAMEL_ENUMS.values():
43
51
  return {"__enum__": str(obj)}
52
+
53
+ # Handle Pydantic BaseModel objects (e.g., from structured outputs)
54
+ if isinstance(obj, BaseModel):
55
+ return obj.model_dump()
56
+
44
57
  # Let the base class default method raise the TypeError
45
58
  return json.JSONEncoder.default(self, obj)
46
59