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.
- camel/__init__.py +1 -1
- camel/agents/_utils.py +38 -0
- camel/agents/chat_agent.py +2217 -519
- camel/agents/mcp_agent.py +30 -27
- camel/configs/__init__.py +15 -0
- camel/configs/aihubmix_config.py +88 -0
- camel/configs/amd_config.py +70 -0
- camel/configs/cometapi_config.py +104 -0
- camel/configs/minimax_config.py +93 -0
- camel/configs/nebius_config.py +103 -0
- camel/data_collectors/alpaca_collector.py +15 -6
- camel/datasets/base_generator.py +39 -10
- camel/environments/single_step.py +28 -3
- camel/environments/tic_tac_toe.py +1 -1
- camel/interpreters/__init__.py +2 -0
- camel/interpreters/docker/Dockerfile +3 -12
- camel/interpreters/e2b_interpreter.py +34 -1
- camel/interpreters/microsandbox_interpreter.py +395 -0
- camel/loaders/__init__.py +11 -2
- camel/loaders/chunkr_reader.py +9 -0
- camel/memories/agent_memories.py +48 -4
- camel/memories/base.py +26 -0
- camel/memories/blocks/chat_history_block.py +122 -4
- camel/memories/context_creators/score_based.py +25 -384
- camel/memories/records.py +88 -8
- camel/messages/base.py +153 -34
- camel/models/__init__.py +10 -0
- camel/models/aihubmix_model.py +83 -0
- camel/models/aiml_model.py +1 -16
- camel/models/amd_model.py +101 -0
- camel/models/anthropic_model.py +6 -19
- camel/models/aws_bedrock_model.py +2 -33
- camel/models/azure_openai_model.py +114 -89
- camel/models/base_audio_model.py +3 -1
- camel/models/base_model.py +32 -14
- camel/models/cohere_model.py +1 -16
- camel/models/cometapi_model.py +83 -0
- camel/models/crynux_model.py +1 -16
- camel/models/deepseek_model.py +1 -16
- camel/models/fish_audio_model.py +6 -0
- camel/models/gemini_model.py +36 -18
- camel/models/groq_model.py +1 -17
- camel/models/internlm_model.py +1 -16
- camel/models/litellm_model.py +1 -16
- camel/models/lmstudio_model.py +1 -17
- camel/models/minimax_model.py +83 -0
- camel/models/mistral_model.py +1 -16
- camel/models/model_factory.py +27 -1
- camel/models/modelscope_model.py +1 -16
- camel/models/moonshot_model.py +105 -24
- camel/models/nebius_model.py +83 -0
- camel/models/nemotron_model.py +0 -5
- camel/models/netmind_model.py +1 -16
- camel/models/novita_model.py +1 -16
- camel/models/nvidia_model.py +1 -16
- camel/models/ollama_model.py +4 -19
- camel/models/openai_compatible_model.py +62 -41
- camel/models/openai_model.py +62 -57
- camel/models/openrouter_model.py +1 -17
- camel/models/ppio_model.py +1 -16
- camel/models/qianfan_model.py +1 -16
- camel/models/qwen_model.py +1 -16
- camel/models/reka_model.py +1 -16
- camel/models/samba_model.py +34 -47
- camel/models/sglang_model.py +64 -31
- camel/models/siliconflow_model.py +1 -16
- camel/models/stub_model.py +0 -4
- camel/models/togetherai_model.py +1 -16
- camel/models/vllm_model.py +1 -16
- camel/models/volcano_model.py +0 -17
- camel/models/watsonx_model.py +1 -16
- camel/models/yi_model.py +1 -16
- camel/models/zhipuai_model.py +60 -16
- camel/parsers/__init__.py +18 -0
- camel/parsers/mcp_tool_call_parser.py +176 -0
- camel/retrievers/auto_retriever.py +1 -0
- camel/runtimes/daytona_runtime.py +11 -12
- camel/societies/__init__.py +2 -0
- camel/societies/workforce/__init__.py +2 -0
- camel/societies/workforce/events.py +122 -0
- camel/societies/workforce/prompts.py +146 -66
- camel/societies/workforce/role_playing_worker.py +15 -11
- camel/societies/workforce/single_agent_worker.py +302 -65
- camel/societies/workforce/structured_output_handler.py +30 -18
- camel/societies/workforce/task_channel.py +163 -27
- camel/societies/workforce/utils.py +107 -13
- camel/societies/workforce/workflow_memory_manager.py +772 -0
- camel/societies/workforce/workforce.py +1949 -579
- camel/societies/workforce/workforce_callback.py +74 -0
- camel/societies/workforce/workforce_logger.py +168 -145
- camel/societies/workforce/workforce_metrics.py +33 -0
- camel/storages/key_value_storages/json.py +15 -2
- camel/storages/key_value_storages/mem0_cloud.py +48 -47
- camel/storages/object_storages/google_cloud.py +1 -1
- camel/storages/vectordb_storages/oceanbase.py +13 -13
- camel/storages/vectordb_storages/qdrant.py +3 -3
- camel/storages/vectordb_storages/tidb.py +8 -6
- camel/tasks/task.py +4 -3
- camel/toolkits/__init__.py +20 -7
- camel/toolkits/aci_toolkit.py +45 -0
- camel/toolkits/base.py +6 -4
- camel/toolkits/code_execution.py +28 -1
- camel/toolkits/context_summarizer_toolkit.py +684 -0
- camel/toolkits/dappier_toolkit.py +5 -1
- camel/toolkits/dingtalk.py +1135 -0
- camel/toolkits/edgeone_pages_mcp_toolkit.py +11 -31
- camel/toolkits/excel_toolkit.py +1 -1
- camel/toolkits/{file_write_toolkit.py → file_toolkit.py} +430 -36
- camel/toolkits/function_tool.py +13 -3
- camel/toolkits/github_toolkit.py +104 -17
- camel/toolkits/gmail_toolkit.py +1839 -0
- camel/toolkits/google_calendar_toolkit.py +38 -4
- camel/toolkits/google_drive_mcp_toolkit.py +12 -31
- camel/toolkits/hybrid_browser_toolkit/config_loader.py +15 -0
- camel/toolkits/hybrid_browser_toolkit/hybrid_browser_toolkit.py +77 -8
- camel/toolkits/hybrid_browser_toolkit/hybrid_browser_toolkit_ts.py +884 -88
- camel/toolkits/hybrid_browser_toolkit/installer.py +203 -0
- camel/toolkits/hybrid_browser_toolkit/ts/package-lock.json +5 -612
- camel/toolkits/hybrid_browser_toolkit/ts/package.json +0 -1
- camel/toolkits/hybrid_browser_toolkit/ts/src/browser-session.ts +959 -89
- camel/toolkits/hybrid_browser_toolkit/ts/src/config-loader.ts +9 -2
- camel/toolkits/hybrid_browser_toolkit/ts/src/hybrid-browser-toolkit.ts +281 -213
- camel/toolkits/hybrid_browser_toolkit/ts/src/parent-child-filter.ts +226 -0
- camel/toolkits/hybrid_browser_toolkit/ts/src/snapshot-parser.ts +219 -0
- camel/toolkits/hybrid_browser_toolkit/ts/src/som-screenshot-injected.ts +543 -0
- camel/toolkits/hybrid_browser_toolkit/ts/src/types.ts +23 -3
- camel/toolkits/hybrid_browser_toolkit/ts/websocket-server.js +72 -7
- camel/toolkits/hybrid_browser_toolkit/ws_wrapper.py +582 -132
- camel/toolkits/hybrid_browser_toolkit_py/actions.py +158 -0
- camel/toolkits/hybrid_browser_toolkit_py/browser_session.py +55 -8
- camel/toolkits/hybrid_browser_toolkit_py/config_loader.py +43 -0
- camel/toolkits/hybrid_browser_toolkit_py/hybrid_browser_toolkit.py +321 -8
- camel/toolkits/hybrid_browser_toolkit_py/snapshot.py +10 -4
- camel/toolkits/hybrid_browser_toolkit_py/unified_analyzer.js +45 -4
- camel/toolkits/{openai_image_toolkit.py → image_generation_toolkit.py} +151 -53
- camel/toolkits/klavis_toolkit.py +5 -1
- camel/toolkits/markitdown_toolkit.py +27 -1
- camel/toolkits/math_toolkit.py +64 -10
- camel/toolkits/mcp_toolkit.py +366 -71
- camel/toolkits/memory_toolkit.py +5 -1
- camel/toolkits/message_integration.py +18 -13
- camel/toolkits/minimax_mcp_toolkit.py +195 -0
- camel/toolkits/note_taking_toolkit.py +19 -10
- camel/toolkits/notion_mcp_toolkit.py +16 -26
- camel/toolkits/openbb_toolkit.py +5 -1
- camel/toolkits/origene_mcp_toolkit.py +8 -49
- camel/toolkits/playwright_mcp_toolkit.py +12 -31
- camel/toolkits/resend_toolkit.py +168 -0
- camel/toolkits/search_toolkit.py +264 -91
- camel/toolkits/slack_toolkit.py +64 -10
- camel/toolkits/terminal_toolkit/__init__.py +18 -0
- camel/toolkits/terminal_toolkit/terminal_toolkit.py +957 -0
- camel/toolkits/terminal_toolkit/utils.py +532 -0
- camel/toolkits/vertex_ai_veo_toolkit.py +590 -0
- camel/toolkits/video_analysis_toolkit.py +17 -11
- camel/toolkits/wechat_official_toolkit.py +483 -0
- camel/toolkits/zapier_toolkit.py +5 -1
- camel/types/__init__.py +2 -2
- camel/types/enums.py +274 -7
- camel/types/openai_types.py +2 -2
- camel/types/unified_model_type.py +15 -0
- camel/utils/commons.py +36 -5
- camel/utils/constants.py +3 -0
- camel/utils/context_utils.py +1003 -0
- camel/utils/mcp.py +138 -4
- camel/utils/token_counting.py +43 -20
- {camel_ai-0.2.73a4.dist-info → camel_ai-0.2.80a2.dist-info}/METADATA +223 -83
- {camel_ai-0.2.73a4.dist-info → camel_ai-0.2.80a2.dist-info}/RECORD +170 -141
- camel/loaders/pandas_reader.py +0 -368
- camel/toolkits/openai_agent_toolkit.py +0 -135
- camel/toolkits/terminal_toolkit.py +0 -1550
- {camel_ai-0.2.73a4.dist-info → camel_ai-0.2.80a2.dist-info}/WHEEL +0 -0
- {camel_ai-0.2.73a4.dist-info → camel_ai-0.2.80a2.dist-info}/licenses/LICENSE +0 -0
|
@@ -119,11 +119,13 @@ class RolePlayingWorker(Worker):
|
|
|
119
119
|
`TaskState.FAILED`.
|
|
120
120
|
"""
|
|
121
121
|
dependency_tasks_info = self._get_dep_tasks_info(dependencies)
|
|
122
|
-
prompt =
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
122
|
+
prompt = str(
|
|
123
|
+
ROLEPLAY_PROCESS_TASK_PROMPT.format(
|
|
124
|
+
content=task.content,
|
|
125
|
+
parent_task_content=task.parent.content if task.parent else "",
|
|
126
|
+
dependency_tasks_info=dependency_tasks_info,
|
|
127
|
+
additional_info=task.additional_info,
|
|
128
|
+
)
|
|
127
129
|
)
|
|
128
130
|
role_play_session = RolePlaying(
|
|
129
131
|
assistant_role_name=self.assistant_role_name,
|
|
@@ -183,12 +185,14 @@ class RolePlayingWorker(Worker):
|
|
|
183
185
|
input_msg = assistant_response.msg
|
|
184
186
|
|
|
185
187
|
chat_history_str = "\n".join(chat_history)
|
|
186
|
-
prompt =
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
188
|
+
prompt = str(
|
|
189
|
+
ROLEPLAY_SUMMARIZE_PROMPT.format(
|
|
190
|
+
user_role=self.user_role_name,
|
|
191
|
+
assistant_role=self.assistant_role_name,
|
|
192
|
+
content=task.content,
|
|
193
|
+
chat_history=chat_history_str,
|
|
194
|
+
additional_info=task.additional_info,
|
|
195
|
+
)
|
|
192
196
|
)
|
|
193
197
|
if self.use_structured_output_handler and self.structured_handler:
|
|
194
198
|
# Use structured output handler for prompt-based extraction
|
|
@@ -17,26 +17,33 @@ import asyncio
|
|
|
17
17
|
import datetime
|
|
18
18
|
import time
|
|
19
19
|
from collections import deque
|
|
20
|
-
from typing import Any, List, Optional
|
|
20
|
+
from typing import Any, Dict, List, Optional
|
|
21
21
|
|
|
22
22
|
from colorama import Fore
|
|
23
23
|
|
|
24
24
|
from camel.agents import ChatAgent
|
|
25
25
|
from camel.agents.chat_agent import AsyncStreamingChatAgentResponse
|
|
26
|
+
from camel.logger import get_logger
|
|
26
27
|
from camel.societies.workforce.prompts import PROCESS_TASK_PROMPT
|
|
27
28
|
from camel.societies.workforce.structured_output_handler import (
|
|
28
29
|
StructuredOutputHandler,
|
|
29
30
|
)
|
|
30
31
|
from camel.societies.workforce.utils import TaskResult
|
|
31
32
|
from camel.societies.workforce.worker import Worker
|
|
33
|
+
from camel.societies.workforce.workflow_memory_manager import (
|
|
34
|
+
WorkflowMemoryManager,
|
|
35
|
+
)
|
|
32
36
|
from camel.tasks.task import Task, TaskState, is_task_result_insufficient
|
|
37
|
+
from camel.utils.context_utils import ContextUtility
|
|
38
|
+
|
|
39
|
+
logger = get_logger(__name__)
|
|
33
40
|
|
|
34
41
|
|
|
35
42
|
class AgentPool:
|
|
36
43
|
r"""A pool of agent instances for efficient reuse.
|
|
37
44
|
|
|
38
|
-
This pool manages a collection of pre-cloned agents
|
|
39
|
-
|
|
45
|
+
This pool manages a collection of pre-cloned agents with automatic
|
|
46
|
+
scaling and idle timeout cleanup.
|
|
40
47
|
|
|
41
48
|
Args:
|
|
42
49
|
base_agent (ChatAgent): The base agent to clone from.
|
|
@@ -48,6 +55,8 @@ class AgentPool:
|
|
|
48
55
|
(default: :obj:`True`)
|
|
49
56
|
idle_timeout (float): Time in seconds after which idle agents are
|
|
50
57
|
removed. (default: :obj:`180.0`)
|
|
58
|
+
cleanup_interval (float): Fixed interval in seconds between cleanup
|
|
59
|
+
checks. (default: :obj:`60.0`)
|
|
51
60
|
"""
|
|
52
61
|
|
|
53
62
|
def __init__(
|
|
@@ -56,23 +65,27 @@ class AgentPool:
|
|
|
56
65
|
initial_size: int = 1,
|
|
57
66
|
max_size: int = 10,
|
|
58
67
|
auto_scale: bool = True,
|
|
59
|
-
idle_timeout: float = 180.0,
|
|
68
|
+
idle_timeout: float = 180.0,
|
|
69
|
+
cleanup_interval: float = 60.0,
|
|
60
70
|
):
|
|
61
71
|
self.base_agent = base_agent
|
|
62
72
|
self.max_size = max_size
|
|
63
73
|
self.auto_scale = auto_scale
|
|
64
74
|
self.idle_timeout = idle_timeout
|
|
75
|
+
self.cleanup_interval = cleanup_interval
|
|
65
76
|
|
|
66
77
|
# Pool management
|
|
67
78
|
self._available_agents: deque = deque()
|
|
68
79
|
self._in_use_agents: set = set()
|
|
69
80
|
self._agent_last_used: dict = {}
|
|
70
81
|
self._lock = asyncio.Lock()
|
|
82
|
+
self._condition = asyncio.Condition(self._lock)
|
|
71
83
|
|
|
72
84
|
# Statistics
|
|
73
85
|
self._total_borrows = 0
|
|
74
86
|
self._total_clones_created = 0
|
|
75
87
|
self._pool_hits = 0
|
|
88
|
+
self._agents_cleaned = 0
|
|
76
89
|
|
|
77
90
|
# Initialize pool
|
|
78
91
|
self._initialize_pool(initial_size)
|
|
@@ -82,6 +95,7 @@ class AgentPool:
|
|
|
82
95
|
for _ in range(min(size, self.max_size)):
|
|
83
96
|
agent = self._create_fresh_agent()
|
|
84
97
|
self._available_agents.append(agent)
|
|
98
|
+
self._agent_last_used[id(agent)] = time.time()
|
|
85
99
|
|
|
86
100
|
def _create_fresh_agent(self) -> ChatAgent:
|
|
87
101
|
r"""Create a fresh agent instance."""
|
|
@@ -91,87 +105,82 @@ class AgentPool:
|
|
|
91
105
|
|
|
92
106
|
async def get_agent(self) -> ChatAgent:
|
|
93
107
|
r"""Get an agent from the pool, creating one if necessary."""
|
|
94
|
-
async with self.
|
|
108
|
+
async with self._condition:
|
|
95
109
|
self._total_borrows += 1
|
|
96
110
|
|
|
97
|
-
# Try to get
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
111
|
+
# Try to get available agent or create new one
|
|
112
|
+
while True:
|
|
113
|
+
if self._available_agents:
|
|
114
|
+
agent = self._available_agents.popleft()
|
|
115
|
+
self._in_use_agents.add(id(agent))
|
|
116
|
+
self._pool_hits += 1
|
|
117
|
+
return agent
|
|
102
118
|
|
|
103
|
-
#
|
|
104
|
-
|
|
105
|
-
|
|
119
|
+
# Check if we can create a new agent
|
|
120
|
+
if len(self._in_use_agents) < self.max_size or self.auto_scale:
|
|
121
|
+
agent = self._create_fresh_agent()
|
|
122
|
+
self._in_use_agents.add(id(agent))
|
|
123
|
+
return agent
|
|
106
124
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
self._in_use_agents
|
|
110
|
-
)
|
|
111
|
-
if total_agents < self.max_size:
|
|
112
|
-
agent = self._create_fresh_agent()
|
|
113
|
-
self._in_use_agents.add(id(agent))
|
|
114
|
-
return agent
|
|
115
|
-
|
|
116
|
-
# Pool exhausted, wait and retry or create temporary agent
|
|
117
|
-
if self.auto_scale:
|
|
118
|
-
# Create a temporary agent that won't be returned to pool
|
|
119
|
-
return self._create_fresh_agent()
|
|
120
|
-
else:
|
|
121
|
-
# Wait for an agent to become available
|
|
122
|
-
while not self._available_agents:
|
|
123
|
-
await asyncio.sleep(0.1)
|
|
124
|
-
|
|
125
|
-
agent = self._available_agents.popleft()
|
|
126
|
-
self._in_use_agents.add(id(agent))
|
|
127
|
-
agent.reset()
|
|
128
|
-
return agent
|
|
125
|
+
# Wait for an agent to be returned
|
|
126
|
+
await self._condition.wait()
|
|
129
127
|
|
|
130
128
|
async def return_agent(self, agent: ChatAgent) -> None:
|
|
131
129
|
r"""Return an agent to the pool."""
|
|
132
|
-
|
|
133
|
-
agent_id = id(agent)
|
|
130
|
+
agent_id = id(agent)
|
|
134
131
|
|
|
135
|
-
|
|
136
|
-
|
|
132
|
+
async with self._condition:
|
|
133
|
+
if agent_id not in self._in_use_agents:
|
|
134
|
+
return
|
|
137
135
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
136
|
+
self._in_use_agents.discard(agent_id)
|
|
137
|
+
|
|
138
|
+
# Only add back to pool if under max size
|
|
139
|
+
if len(self._available_agents) < self.max_size:
|
|
140
|
+
agent.reset()
|
|
141
|
+
self._agent_last_used[agent_id] = time.time()
|
|
142
|
+
self._available_agents.append(agent)
|
|
143
|
+
# Notify one waiting coroutine that an agent is available
|
|
144
|
+
self._condition.notify()
|
|
145
|
+
else:
|
|
146
|
+
# Remove tracking for agents not returned to pool
|
|
147
|
+
self._agent_last_used.pop(agent_id, None)
|
|
144
148
|
|
|
145
149
|
async def cleanup_idle_agents(self) -> None:
|
|
146
150
|
r"""Remove idle agents from the pool to free memory."""
|
|
147
151
|
if not self.auto_scale:
|
|
148
152
|
return
|
|
149
153
|
|
|
150
|
-
async with self.
|
|
154
|
+
async with self._condition:
|
|
155
|
+
if not self._available_agents:
|
|
156
|
+
return
|
|
157
|
+
|
|
151
158
|
current_time = time.time()
|
|
152
159
|
agents_to_remove = []
|
|
153
160
|
|
|
154
161
|
for agent in list(self._available_agents):
|
|
155
162
|
agent_id = id(agent)
|
|
156
163
|
last_used = self._agent_last_used.get(agent_id, current_time)
|
|
157
|
-
|
|
158
164
|
if current_time - last_used > self.idle_timeout:
|
|
159
165
|
agents_to_remove.append(agent)
|
|
160
166
|
|
|
161
167
|
for agent in agents_to_remove:
|
|
162
168
|
self._available_agents.remove(agent)
|
|
163
|
-
|
|
164
|
-
self.
|
|
169
|
+
self._agent_last_used.pop(id(agent), None)
|
|
170
|
+
self._agents_cleaned += 1
|
|
165
171
|
|
|
166
172
|
def get_stats(self) -> dict:
|
|
167
173
|
r"""Get pool statistics."""
|
|
168
174
|
return {
|
|
169
175
|
"available_agents": len(self._available_agents),
|
|
170
176
|
"in_use_agents": len(self._in_use_agents),
|
|
177
|
+
"pool_size": len(self._available_agents)
|
|
178
|
+
+ len(self._in_use_agents),
|
|
171
179
|
"total_borrows": self._total_borrows,
|
|
172
180
|
"total_clones_created": self._total_clones_created,
|
|
173
181
|
"pool_hits": self._pool_hits,
|
|
174
182
|
"hit_rate": self._pool_hits / max(self._total_borrows, 1),
|
|
183
|
+
"agents_cleaned_up": self._agents_cleaned,
|
|
175
184
|
}
|
|
176
185
|
|
|
177
186
|
|
|
@@ -197,6 +206,16 @@ class SingleAgentWorker(Worker):
|
|
|
197
206
|
support native structured output. When disabled, the workforce
|
|
198
207
|
uses the native response_format parameter.
|
|
199
208
|
(default: :obj:`True`)
|
|
209
|
+
context_utility (ContextUtility, optional): Shared context utility
|
|
210
|
+
instance for workflow management. If provided, all workflow
|
|
211
|
+
operations will use this shared instance instead of creating
|
|
212
|
+
a new one. This ensures multiple workers share the same session
|
|
213
|
+
directory. (default: :obj:`None`)
|
|
214
|
+
enable_workflow_memory (bool, optional): Whether to enable workflow
|
|
215
|
+
memory accumulation during task execution. When enabled,
|
|
216
|
+
conversations from all task executions are accumulated for
|
|
217
|
+
potential workflow saving. Set to True if you plan to call
|
|
218
|
+
save_workflow_memories(). (default: :obj:`False`)
|
|
200
219
|
"""
|
|
201
220
|
|
|
202
221
|
def __init__(
|
|
@@ -208,6 +227,8 @@ class SingleAgentWorker(Worker):
|
|
|
208
227
|
pool_max_size: int = 10,
|
|
209
228
|
auto_scale_pool: bool = True,
|
|
210
229
|
use_structured_output_handler: bool = True,
|
|
230
|
+
context_utility: Optional[ContextUtility] = None,
|
|
231
|
+
enable_workflow_memory: bool = False,
|
|
211
232
|
) -> None:
|
|
212
233
|
node_id = worker.agent_id
|
|
213
234
|
super().__init__(
|
|
@@ -222,6 +243,21 @@ class SingleAgentWorker(Worker):
|
|
|
222
243
|
)
|
|
223
244
|
self.worker = worker
|
|
224
245
|
self.use_agent_pool = use_agent_pool
|
|
246
|
+
self.enable_workflow_memory = enable_workflow_memory
|
|
247
|
+
self._shared_context_utility = context_utility
|
|
248
|
+
self._context_utility: Optional[ContextUtility] = (
|
|
249
|
+
None # Will be initialized when needed
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
# accumulator agent for collecting conversations
|
|
253
|
+
# from all task processing
|
|
254
|
+
self._conversation_accumulator: Optional[ChatAgent] = None
|
|
255
|
+
|
|
256
|
+
# workflow memory manager for handling workflow operations
|
|
257
|
+
self._workflow_manager: Optional[WorkflowMemoryManager] = None
|
|
258
|
+
|
|
259
|
+
# note: context utility is set on the worker agent during save/load
|
|
260
|
+
# operations to avoid creating session folders during initialization
|
|
225
261
|
|
|
226
262
|
self.agent_pool: Optional[AgentPool] = None
|
|
227
263
|
self._cleanup_task: Optional[asyncio.Task] = None
|
|
@@ -264,6 +300,39 @@ class SingleAgentWorker(Worker):
|
|
|
264
300
|
await self.agent_pool.return_agent(agent)
|
|
265
301
|
# If not using pool, agent will be garbage collected
|
|
266
302
|
|
|
303
|
+
def _get_context_utility(self) -> ContextUtility:
|
|
304
|
+
r"""Get context utility with lazy initialization."""
|
|
305
|
+
if self._context_utility is None:
|
|
306
|
+
self._context_utility = (
|
|
307
|
+
self._shared_context_utility
|
|
308
|
+
or ContextUtility.get_workforce_shared()
|
|
309
|
+
)
|
|
310
|
+
return self._context_utility
|
|
311
|
+
|
|
312
|
+
def _get_conversation_accumulator(self) -> ChatAgent:
|
|
313
|
+
r"""Get or create the conversation accumulator agent."""
|
|
314
|
+
if self._conversation_accumulator is None:
|
|
315
|
+
# create a clone of the original worker to serve as accumulator
|
|
316
|
+
self._conversation_accumulator = self.worker.clone(
|
|
317
|
+
with_memory=False
|
|
318
|
+
)
|
|
319
|
+
return self._conversation_accumulator
|
|
320
|
+
|
|
321
|
+
def _get_workflow_manager(self) -> WorkflowMemoryManager:
|
|
322
|
+
r"""Get or create the workflow memory manager."""
|
|
323
|
+
if self._workflow_manager is None:
|
|
324
|
+
context_util = (
|
|
325
|
+
self._shared_context_utility
|
|
326
|
+
if self._shared_context_utility is not None
|
|
327
|
+
else None
|
|
328
|
+
)
|
|
329
|
+
self._workflow_manager = WorkflowMemoryManager(
|
|
330
|
+
worker=self.worker,
|
|
331
|
+
description=self.description,
|
|
332
|
+
context_utility=context_util,
|
|
333
|
+
)
|
|
334
|
+
return self._workflow_manager
|
|
335
|
+
|
|
267
336
|
async def _process_task(
|
|
268
337
|
self, task: Task, dependencies: List[Task]
|
|
269
338
|
) -> TaskState:
|
|
@@ -290,11 +359,15 @@ class SingleAgentWorker(Worker):
|
|
|
290
359
|
|
|
291
360
|
try:
|
|
292
361
|
dependency_tasks_info = self._get_dep_tasks_info(dependencies)
|
|
293
|
-
prompt =
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
362
|
+
prompt = str(
|
|
363
|
+
PROCESS_TASK_PROMPT.format(
|
|
364
|
+
content=task.content,
|
|
365
|
+
parent_task_content=task.parent.content
|
|
366
|
+
if task.parent
|
|
367
|
+
else "",
|
|
368
|
+
dependency_tasks_info=dependency_tasks_info,
|
|
369
|
+
additional_info=task.additional_info,
|
|
370
|
+
)
|
|
298
371
|
)
|
|
299
372
|
|
|
300
373
|
if self.use_structured_output_handler and self.structured_handler:
|
|
@@ -374,6 +447,7 @@ class SingleAgentWorker(Worker):
|
|
|
374
447
|
"usage"
|
|
375
448
|
) or final_response.info.get("token_usage")
|
|
376
449
|
else:
|
|
450
|
+
final_response = response
|
|
377
451
|
usage_info = response.info.get("usage") or response.info.get(
|
|
378
452
|
"token_usage"
|
|
379
453
|
)
|
|
@@ -381,10 +455,36 @@ class SingleAgentWorker(Worker):
|
|
|
381
455
|
usage_info.get("total_tokens", 0) if usage_info else 0
|
|
382
456
|
)
|
|
383
457
|
|
|
458
|
+
# collect conversation from working agent to
|
|
459
|
+
# accumulator for workflow memory
|
|
460
|
+
# Only transfer memory if workflow memory is enabled
|
|
461
|
+
if self.enable_workflow_memory:
|
|
462
|
+
accumulator = self._get_conversation_accumulator()
|
|
463
|
+
|
|
464
|
+
# transfer all memory records from working agent to accumulator
|
|
465
|
+
try:
|
|
466
|
+
# retrieve all context records from the working agent
|
|
467
|
+
work_records = worker_agent.memory.retrieve()
|
|
468
|
+
|
|
469
|
+
# write these records to the accumulator's memory
|
|
470
|
+
memory_records = [
|
|
471
|
+
record.memory_record for record in work_records
|
|
472
|
+
]
|
|
473
|
+
accumulator.memory.write_records(memory_records)
|
|
474
|
+
|
|
475
|
+
logger.debug(
|
|
476
|
+
f"Transferred {len(memory_records)} memory records to "
|
|
477
|
+
f"accumulator"
|
|
478
|
+
)
|
|
479
|
+
|
|
480
|
+
except Exception as e:
|
|
481
|
+
logger.warning(
|
|
482
|
+
f"Failed to transfer conversation to accumulator: {e}"
|
|
483
|
+
)
|
|
484
|
+
|
|
384
485
|
except Exception as e:
|
|
385
|
-
|
|
386
|
-
f"
|
|
387
|
-
f"{type(e).__name__}: {e}{Fore.RESET}"
|
|
486
|
+
logger.error(
|
|
487
|
+
f"Error processing task {task.id}: {type(e).__name__}: {e}"
|
|
388
488
|
)
|
|
389
489
|
# Store error information in task result
|
|
390
490
|
task.result = f"{type(e).__name__}: {e!s}"
|
|
@@ -429,13 +529,13 @@ class SingleAgentWorker(Worker):
|
|
|
429
529
|
task.additional_info["token_usage"] = {"total_tokens": total_tokens}
|
|
430
530
|
|
|
431
531
|
print(f"======\n{Fore.GREEN}Response from {self}:{Fore.RESET}")
|
|
532
|
+
logger.info(f"Response from {self}:")
|
|
432
533
|
|
|
433
534
|
if not self.use_structured_output_handler:
|
|
434
535
|
# Handle native structured output parsing
|
|
435
536
|
if task_result is None:
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
f"task result{Fore.RESET}"
|
|
537
|
+
logger.error(
|
|
538
|
+
"Error in worker step execution: Invalid task result"
|
|
439
539
|
)
|
|
440
540
|
task_result = TaskResult(
|
|
441
541
|
content="Failed to generate valid task result.",
|
|
@@ -446,6 +546,10 @@ class SingleAgentWorker(Worker):
|
|
|
446
546
|
print(
|
|
447
547
|
f"\n{color}{task_result.content}{Fore.RESET}\n======", # type: ignore[union-attr]
|
|
448
548
|
)
|
|
549
|
+
if task_result.failed: # type: ignore[union-attr]
|
|
550
|
+
logger.error(f"{task_result.content}") # type: ignore[union-attr]
|
|
551
|
+
else:
|
|
552
|
+
logger.info(f"{task_result.content}") # type: ignore[union-attr]
|
|
449
553
|
|
|
450
554
|
task.result = task_result.content # type: ignore[union-attr]
|
|
451
555
|
|
|
@@ -453,9 +557,9 @@ class SingleAgentWorker(Worker):
|
|
|
453
557
|
return TaskState.FAILED
|
|
454
558
|
|
|
455
559
|
if is_task_result_insufficient(task):
|
|
456
|
-
|
|
457
|
-
f"
|
|
458
|
-
f"task marked as failed
|
|
560
|
+
logger.warning(
|
|
561
|
+
f"Task {task.id}: Content validation failed - "
|
|
562
|
+
f"task marked as failed"
|
|
459
563
|
)
|
|
460
564
|
return TaskState.FAILED
|
|
461
565
|
return TaskState.DONE
|
|
@@ -477,16 +581,149 @@ class SingleAgentWorker(Worker):
|
|
|
477
581
|
r"""Periodically clean up idle agents from the pool."""
|
|
478
582
|
while True:
|
|
479
583
|
try:
|
|
480
|
-
|
|
584
|
+
# Fixed interval cleanup
|
|
481
585
|
if self.agent_pool:
|
|
586
|
+
await asyncio.sleep(self.agent_pool.cleanup_interval)
|
|
482
587
|
await self.agent_pool.cleanup_idle_agents()
|
|
588
|
+
else:
|
|
589
|
+
break
|
|
483
590
|
except asyncio.CancelledError:
|
|
484
591
|
break
|
|
485
592
|
except Exception as e:
|
|
486
|
-
|
|
593
|
+
logger.warning(f"Error in pool cleanup: {e}")
|
|
487
594
|
|
|
488
595
|
def get_pool_stats(self) -> Optional[dict]:
|
|
489
596
|
r"""Get agent pool statistics if pool is enabled."""
|
|
490
597
|
if self.use_agent_pool and self.agent_pool:
|
|
491
598
|
return self.agent_pool.get_stats()
|
|
492
599
|
return None
|
|
600
|
+
|
|
601
|
+
def save_workflow_memories(self) -> Dict[str, Any]:
|
|
602
|
+
r"""Save the worker's current workflow memories using agent
|
|
603
|
+
summarization.
|
|
604
|
+
|
|
605
|
+
.. deprecated:: 0.2.80
|
|
606
|
+
Use :meth:`save_workflow_memories_async` for async/await support
|
|
607
|
+
and better integration with parallel workflow saving.
|
|
608
|
+
|
|
609
|
+
This method generates a workflow summary from the worker agent's
|
|
610
|
+
conversation history and saves it to a markdown file. The filename
|
|
611
|
+
is based on either the worker's explicit role_name or the generated
|
|
612
|
+
task_title from the summary.
|
|
613
|
+
|
|
614
|
+
Delegates to WorkflowMemoryManager for all workflow operations.
|
|
615
|
+
|
|
616
|
+
Returns:
|
|
617
|
+
Dict[str, Any]: Result dictionary with keys:
|
|
618
|
+
- status (str): "success" or "error"
|
|
619
|
+
- summary (str): Generated workflow summary
|
|
620
|
+
- file_path (str): Path to saved file
|
|
621
|
+
- worker_description (str): Worker description used
|
|
622
|
+
|
|
623
|
+
See Also:
|
|
624
|
+
:meth:`save_workflow_memories_async`: Async version for better
|
|
625
|
+
performance in parallel workflows.
|
|
626
|
+
"""
|
|
627
|
+
import warnings
|
|
628
|
+
|
|
629
|
+
warnings.warn(
|
|
630
|
+
"save_workflow_memories() is synchronous. Consider using "
|
|
631
|
+
"save_workflow_memories_async() for async/await support.",
|
|
632
|
+
DeprecationWarning,
|
|
633
|
+
stacklevel=2,
|
|
634
|
+
)
|
|
635
|
+
|
|
636
|
+
manager = self._get_workflow_manager()
|
|
637
|
+
result = manager.save_workflow(
|
|
638
|
+
conversation_accumulator=self._conversation_accumulator
|
|
639
|
+
)
|
|
640
|
+
|
|
641
|
+
# clean up accumulator after successful save
|
|
642
|
+
if (
|
|
643
|
+
result.get("status") == "success"
|
|
644
|
+
and self._conversation_accumulator is not None
|
|
645
|
+
):
|
|
646
|
+
logger.info(
|
|
647
|
+
"Cleaning up conversation accumulator after workflow "
|
|
648
|
+
"summarization"
|
|
649
|
+
)
|
|
650
|
+
self._conversation_accumulator = None
|
|
651
|
+
|
|
652
|
+
return result
|
|
653
|
+
|
|
654
|
+
async def save_workflow_memories_async(self) -> Dict[str, Any]:
|
|
655
|
+
r"""Asynchronously save the worker's current workflow memories using
|
|
656
|
+
agent summarization.
|
|
657
|
+
|
|
658
|
+
This is the async version of save_workflow_memories() that uses
|
|
659
|
+
asummarize() for non-blocking LLM calls, enabling parallel
|
|
660
|
+
summarization of multiple workers.
|
|
661
|
+
|
|
662
|
+
Delegates to WorkflowMemoryManager for all workflow operations.
|
|
663
|
+
|
|
664
|
+
Returns:
|
|
665
|
+
Dict[str, Any]: Result dictionary with keys:
|
|
666
|
+
- status (str): "success" or "error"
|
|
667
|
+
- summary (str): Generated workflow summary
|
|
668
|
+
- file_path (str): Path to saved file
|
|
669
|
+
- worker_description (str): Worker description used
|
|
670
|
+
"""
|
|
671
|
+
manager = self._get_workflow_manager()
|
|
672
|
+
result = await manager.save_workflow_async(
|
|
673
|
+
conversation_accumulator=self._conversation_accumulator
|
|
674
|
+
)
|
|
675
|
+
|
|
676
|
+
# clean up accumulator after successful save
|
|
677
|
+
if (
|
|
678
|
+
result.get("status") == "success"
|
|
679
|
+
and self._conversation_accumulator is not None
|
|
680
|
+
):
|
|
681
|
+
logger.info(
|
|
682
|
+
"Cleaning up conversation accumulator after workflow "
|
|
683
|
+
"summarization"
|
|
684
|
+
)
|
|
685
|
+
self._conversation_accumulator = None
|
|
686
|
+
|
|
687
|
+
return result
|
|
688
|
+
|
|
689
|
+
def load_workflow_memories(
|
|
690
|
+
self,
|
|
691
|
+
pattern: Optional[str] = None,
|
|
692
|
+
max_workflows: int = 3,
|
|
693
|
+
session_id: Optional[str] = None,
|
|
694
|
+
use_smart_selection: bool = True,
|
|
695
|
+
) -> bool:
|
|
696
|
+
r"""Load workflow memories using intelligent agent-based selection.
|
|
697
|
+
|
|
698
|
+
This method uses the worker agent to intelligently select the most
|
|
699
|
+
relevant workflows based on metadata (title, description, tags)
|
|
700
|
+
rather than simple filename pattern matching.
|
|
701
|
+
|
|
702
|
+
Delegates to WorkflowMemoryManager for all workflow operations.
|
|
703
|
+
|
|
704
|
+
Args:
|
|
705
|
+
pattern (Optional[str]): Legacy parameter for backward
|
|
706
|
+
compatibility. When use_smart_selection=False, uses this
|
|
707
|
+
pattern for file matching. Ignored when smart selection
|
|
708
|
+
is enabled.
|
|
709
|
+
max_workflows (int): Maximum number of workflow files to load.
|
|
710
|
+
(default: :obj:`3`)
|
|
711
|
+
session_id (Optional[str]): Specific workforce session ID to load
|
|
712
|
+
from. If None, searches across all sessions.
|
|
713
|
+
(default: :obj:`None`)
|
|
714
|
+
use_smart_selection (bool): Whether to use agent-based intelligent
|
|
715
|
+
workflow selection. When True, uses metadata and LLM to select
|
|
716
|
+
most relevant workflows. When False, falls back to pattern
|
|
717
|
+
matching. (default: :obj:`True`)
|
|
718
|
+
|
|
719
|
+
Returns:
|
|
720
|
+
bool: True if workflow memories were successfully loaded, False
|
|
721
|
+
otherwise.
|
|
722
|
+
"""
|
|
723
|
+
manager = self._get_workflow_manager()
|
|
724
|
+
return manager.load_workflows(
|
|
725
|
+
pattern=pattern,
|
|
726
|
+
max_files_to_load=max_workflows,
|
|
727
|
+
session_id=session_id,
|
|
728
|
+
use_smart_selection=use_smart_selection,
|
|
729
|
+
)
|