camel-ai 0.2.71a11__py3-none-any.whl → 0.2.72__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 +261 -489
- camel/memories/agent_memories.py +39 -0
- camel/memories/base.py +8 -0
- camel/models/gemini_model.py +30 -2
- camel/models/moonshot_model.py +36 -4
- camel/models/openai_model.py +29 -15
- camel/societies/workforce/prompts.py +25 -15
- camel/societies/workforce/role_playing_worker.py +1 -1
- camel/societies/workforce/single_agent_worker.py +9 -7
- camel/societies/workforce/worker.py +1 -1
- camel/societies/workforce/workforce.py +97 -34
- camel/storages/vectordb_storages/__init__.py +1 -0
- camel/storages/vectordb_storages/surreal.py +415 -0
- camel/tasks/task.py +9 -5
- camel/toolkits/__init__.py +10 -1
- camel/toolkits/base.py +57 -1
- camel/toolkits/human_toolkit.py +5 -1
- camel/toolkits/hybrid_browser_toolkit/config_loader.py +127 -414
- camel/toolkits/hybrid_browser_toolkit/hybrid_browser_toolkit.py +783 -1626
- camel/toolkits/hybrid_browser_toolkit/ws_wrapper.py +489 -0
- camel/toolkits/markitdown_toolkit.py +2 -2
- camel/toolkits/message_integration.py +592 -0
- camel/toolkits/note_taking_toolkit.py +195 -26
- camel/toolkits/openai_image_toolkit.py +5 -5
- camel/toolkits/origene_mcp_toolkit.py +97 -0
- camel/toolkits/screenshot_toolkit.py +213 -0
- camel/toolkits/search_toolkit.py +161 -79
- camel/toolkits/terminal_toolkit.py +379 -165
- camel/toolkits/video_analysis_toolkit.py +13 -13
- camel/toolkits/video_download_toolkit.py +11 -11
- camel/toolkits/web_deploy_toolkit.py +1024 -0
- camel/types/enums.py +6 -3
- camel/types/unified_model_type.py +16 -4
- camel/utils/mcp_client.py +8 -0
- camel/utils/tool_result.py +1 -1
- {camel_ai-0.2.71a11.dist-info → camel_ai-0.2.72.dist-info}/METADATA +6 -3
- {camel_ai-0.2.71a11.dist-info → camel_ai-0.2.72.dist-info}/RECORD +40 -40
- camel/toolkits/hybrid_browser_toolkit/actions.py +0 -417
- camel/toolkits/hybrid_browser_toolkit/agent.py +0 -311
- camel/toolkits/hybrid_browser_toolkit/browser_session.py +0 -739
- camel/toolkits/hybrid_browser_toolkit/snapshot.py +0 -227
- camel/toolkits/hybrid_browser_toolkit/stealth_script.js +0 -0
- camel/toolkits/hybrid_browser_toolkit/unified_analyzer.js +0 -1002
- {camel_ai-0.2.71a11.dist-info → camel_ai-0.2.72.dist-info}/WHEEL +0 -0
- {camel_ai-0.2.71a11.dist-info → camel_ai-0.2.72.dist-info}/licenses/LICENSE +0 -0
camel/memories/agent_memories.py
CHANGED
|
@@ -88,6 +88,45 @@ class ChatHistoryMemory(AgentMemory):
|
|
|
88
88
|
def clear(self) -> None:
|
|
89
89
|
self._chat_history_block.clear()
|
|
90
90
|
|
|
91
|
+
def clean_tool_calls(self) -> None:
|
|
92
|
+
r"""Removes tool call messages from memory.
|
|
93
|
+
This method removes all FUNCTION/TOOL role messages and any ASSISTANT
|
|
94
|
+
messages that contain tool_calls in their meta_dict to save token
|
|
95
|
+
usage.
|
|
96
|
+
"""
|
|
97
|
+
from camel.types import OpenAIBackendRole
|
|
98
|
+
|
|
99
|
+
# Get all messages from storage
|
|
100
|
+
record_dicts = self._chat_history_block.storage.load()
|
|
101
|
+
if not record_dicts:
|
|
102
|
+
return
|
|
103
|
+
|
|
104
|
+
# Track indices to remove (reverse order for efficient deletion)
|
|
105
|
+
indices_to_remove = []
|
|
106
|
+
|
|
107
|
+
# Identify indices of tool-related messages
|
|
108
|
+
for i, record in enumerate(record_dicts):
|
|
109
|
+
role = record.get('role_at_backend')
|
|
110
|
+
|
|
111
|
+
# Mark FUNCTION messages for removal
|
|
112
|
+
if role == OpenAIBackendRole.FUNCTION.value:
|
|
113
|
+
indices_to_remove.append(i)
|
|
114
|
+
# Mark TOOL messages for removal
|
|
115
|
+
elif role == OpenAIBackendRole.TOOL.value:
|
|
116
|
+
indices_to_remove.append(i)
|
|
117
|
+
# Mark ASSISTANT messages with tool_calls for removal
|
|
118
|
+
elif role == OpenAIBackendRole.ASSISTANT.value:
|
|
119
|
+
meta_dict = record.get('meta_dict', {})
|
|
120
|
+
if meta_dict and 'tool_calls' in meta_dict:
|
|
121
|
+
indices_to_remove.append(i)
|
|
122
|
+
|
|
123
|
+
# Remove records in-place
|
|
124
|
+
for i in reversed(indices_to_remove):
|
|
125
|
+
del record_dicts[i]
|
|
126
|
+
|
|
127
|
+
# Save the modified records back to storage
|
|
128
|
+
self._chat_history_block.storage.save(record_dicts)
|
|
129
|
+
|
|
91
130
|
|
|
92
131
|
class VectorDBMemory(AgentMemory):
|
|
93
132
|
r"""An agent memory wrapper of :obj:`VectorDBBlock`. This memory queries
|
camel/memories/base.py
CHANGED
|
@@ -149,6 +149,14 @@ class AgentMemory(MemoryBlock, ABC):
|
|
|
149
149
|
"""
|
|
150
150
|
return self.get_context_creator().create_context(self.retrieve())
|
|
151
151
|
|
|
152
|
+
def clean_tool_calls(self) -> None:
|
|
153
|
+
r"""Removes tool call messages from memory.
|
|
154
|
+
This is an optional method that can be overridden by subclasses
|
|
155
|
+
to implement cleaning of tool-related messages. By default, it
|
|
156
|
+
does nothing, maintaining backward compatibility.
|
|
157
|
+
"""
|
|
158
|
+
pass
|
|
159
|
+
|
|
152
160
|
def __repr__(self) -> str:
|
|
153
161
|
r"""Returns a string representation of the AgentMemory.
|
|
154
162
|
|
camel/models/gemini_model.py
CHANGED
|
@@ -243,7 +243,7 @@ class GeminiModel(OpenAICompatibleModel):
|
|
|
243
243
|
function_dict = tool.get('function', {})
|
|
244
244
|
function_dict.pop("strict", None)
|
|
245
245
|
|
|
246
|
-
# Process parameters to remove anyOf
|
|
246
|
+
# Process parameters to remove anyOf and handle enum/format
|
|
247
247
|
if 'parameters' in function_dict:
|
|
248
248
|
params = function_dict['parameters']
|
|
249
249
|
if 'properties' in params:
|
|
@@ -260,6 +260,20 @@ class GeminiModel(OpenAICompatibleModel):
|
|
|
260
260
|
'description'
|
|
261
261
|
] = prop_value['description']
|
|
262
262
|
|
|
263
|
+
# Handle enum and format restrictions for Gemini
|
|
264
|
+
# API enum: only allowed for string type
|
|
265
|
+
if prop_value.get('type') != 'string':
|
|
266
|
+
prop_value.pop('enum', None)
|
|
267
|
+
|
|
268
|
+
# format: only allowed for string, integer, and
|
|
269
|
+
# number types
|
|
270
|
+
if prop_value.get('type') not in [
|
|
271
|
+
'string',
|
|
272
|
+
'integer',
|
|
273
|
+
'number',
|
|
274
|
+
]:
|
|
275
|
+
prop_value.pop('format', None)
|
|
276
|
+
|
|
263
277
|
request_config["tools"] = tools
|
|
264
278
|
|
|
265
279
|
return self._client.chat.completions.create(
|
|
@@ -283,7 +297,7 @@ class GeminiModel(OpenAICompatibleModel):
|
|
|
283
297
|
function_dict = tool.get('function', {})
|
|
284
298
|
function_dict.pop("strict", None)
|
|
285
299
|
|
|
286
|
-
# Process parameters to remove anyOf
|
|
300
|
+
# Process parameters to remove anyOf and handle enum/format
|
|
287
301
|
if 'parameters' in function_dict:
|
|
288
302
|
params = function_dict['parameters']
|
|
289
303
|
if 'properties' in params:
|
|
@@ -300,6 +314,20 @@ class GeminiModel(OpenAICompatibleModel):
|
|
|
300
314
|
'description'
|
|
301
315
|
] = prop_value['description']
|
|
302
316
|
|
|
317
|
+
# Handle enum and format restrictions for Gemini
|
|
318
|
+
# API enum: only allowed for string type
|
|
319
|
+
if prop_value.get('type') != 'string':
|
|
320
|
+
prop_value.pop('enum', None)
|
|
321
|
+
|
|
322
|
+
# format: only allowed for string, integer, and
|
|
323
|
+
# number types
|
|
324
|
+
if prop_value.get('type') not in [
|
|
325
|
+
'string',
|
|
326
|
+
'integer',
|
|
327
|
+
'number',
|
|
328
|
+
]:
|
|
329
|
+
prop_value.pop('format', None)
|
|
330
|
+
|
|
303
331
|
request_config["tools"] = tools
|
|
304
332
|
|
|
305
333
|
return await self._async_client.chat.completions.create(
|
camel/models/moonshot_model.py
CHANGED
|
@@ -20,6 +20,7 @@ from pydantic import BaseModel
|
|
|
20
20
|
|
|
21
21
|
from camel.configs import MOONSHOT_API_PARAMS, MoonshotConfig
|
|
22
22
|
from camel.messages import OpenAIMessage
|
|
23
|
+
from camel.models._utils import try_modify_message_with_format
|
|
23
24
|
from camel.models.openai_compatible_model import OpenAICompatibleModel
|
|
24
25
|
from camel.types import (
|
|
25
26
|
ChatCompletion,
|
|
@@ -106,6 +107,38 @@ class MoonshotModel(OpenAICompatibleModel):
|
|
|
106
107
|
**kwargs,
|
|
107
108
|
)
|
|
108
109
|
|
|
110
|
+
def _prepare_request(
|
|
111
|
+
self,
|
|
112
|
+
messages: List[OpenAIMessage],
|
|
113
|
+
response_format: Optional[Type[BaseModel]] = None,
|
|
114
|
+
tools: Optional[List[Dict[str, Any]]] = None,
|
|
115
|
+
) -> Dict[str, Any]:
|
|
116
|
+
r"""Prepare the request configuration for Moonshot API.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
messages (List[OpenAIMessage]): Message list with the chat history
|
|
120
|
+
in OpenAI API format.
|
|
121
|
+
response_format (Optional[Type[BaseModel]]): The format of the
|
|
122
|
+
response.
|
|
123
|
+
tools (Optional[List[Dict[str, Any]]]): The schema of the tools to
|
|
124
|
+
use for the request.
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
Dict[str, Any]: The prepared request configuration.
|
|
128
|
+
"""
|
|
129
|
+
import copy
|
|
130
|
+
|
|
131
|
+
request_config = copy.deepcopy(self.model_config_dict)
|
|
132
|
+
|
|
133
|
+
if tools:
|
|
134
|
+
request_config["tools"] = tools
|
|
135
|
+
elif response_format:
|
|
136
|
+
# Use the same approach as DeepSeek for structured output
|
|
137
|
+
try_modify_message_with_format(messages[-1], response_format)
|
|
138
|
+
request_config["response_format"] = {"type": "json_object"}
|
|
139
|
+
|
|
140
|
+
return request_config
|
|
141
|
+
|
|
109
142
|
@observe()
|
|
110
143
|
async def _arun(
|
|
111
144
|
self,
|
|
@@ -141,10 +174,9 @@ class MoonshotModel(OpenAICompatibleModel):
|
|
|
141
174
|
tags=["CAMEL-AI", str(self.model_type)],
|
|
142
175
|
)
|
|
143
176
|
|
|
144
|
-
request_config = self.
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
request_config["tools"] = tools
|
|
177
|
+
request_config = self._prepare_request(
|
|
178
|
+
messages, response_format, tools
|
|
179
|
+
)
|
|
148
180
|
|
|
149
181
|
return await self._async_client.chat.completions.create(
|
|
150
182
|
messages=messages,
|
camel/models/openai_model.py
CHANGED
|
@@ -23,6 +23,7 @@ from openai.lib.streaming.chat import (
|
|
|
23
23
|
from pydantic import BaseModel
|
|
24
24
|
|
|
25
25
|
from camel.configs import OPENAI_API_PARAMS, ChatGPTConfig
|
|
26
|
+
from camel.logger import get_logger
|
|
26
27
|
from camel.messages import OpenAIMessage
|
|
27
28
|
from camel.models import BaseModelBackend
|
|
28
29
|
from camel.types import (
|
|
@@ -39,6 +40,8 @@ from camel.utils import (
|
|
|
39
40
|
update_langfuse_trace,
|
|
40
41
|
)
|
|
41
42
|
|
|
43
|
+
logger = get_logger(__name__)
|
|
44
|
+
|
|
42
45
|
if os.environ.get("LANGFUSE_ENABLED", "False").lower() == "true":
|
|
43
46
|
try:
|
|
44
47
|
from langfuse.decorators import observe
|
|
@@ -273,17 +276,23 @@ class OpenAIModel(BaseModelBackend):
|
|
|
273
276
|
|
|
274
277
|
# Update Langfuse trace with current agent session and metadata
|
|
275
278
|
agent_session_id = get_current_agent_session_id()
|
|
279
|
+
model_type_str = str(self.model_type)
|
|
280
|
+
if not agent_session_id:
|
|
281
|
+
agent_session_id = "no-session-id"
|
|
282
|
+
metadata = {
|
|
283
|
+
"source": "camel",
|
|
284
|
+
"agent_id": agent_session_id,
|
|
285
|
+
"agent_type": "camel_chat_agent",
|
|
286
|
+
"model_type": model_type_str,
|
|
287
|
+
}
|
|
288
|
+
metadata = {k: str(v) for k, v in metadata.items()}
|
|
276
289
|
if agent_session_id:
|
|
277
290
|
update_langfuse_trace(
|
|
278
291
|
session_id=agent_session_id,
|
|
279
|
-
metadata=
|
|
280
|
-
|
|
281
|
-
"agent_id": agent_session_id,
|
|
282
|
-
"agent_type": "camel_chat_agent",
|
|
283
|
-
"model_type": str(self.model_type),
|
|
284
|
-
},
|
|
285
|
-
tags=["CAMEL-AI", str(self.model_type)],
|
|
292
|
+
metadata=metadata,
|
|
293
|
+
tags=["CAMEL-AI", model_type_str],
|
|
286
294
|
)
|
|
295
|
+
logger.info(f"metadata: {metadata}")
|
|
287
296
|
|
|
288
297
|
messages = self._adapt_messages_for_o1_models(messages)
|
|
289
298
|
response_format = response_format or self.model_config_dict.get(
|
|
@@ -342,17 +351,22 @@ class OpenAIModel(BaseModelBackend):
|
|
|
342
351
|
|
|
343
352
|
# Update Langfuse trace with current agent session and metadata
|
|
344
353
|
agent_session_id = get_current_agent_session_id()
|
|
345
|
-
|
|
354
|
+
model_type_str = str(self.model_type)
|
|
355
|
+
if not agent_session_id:
|
|
356
|
+
agent_session_id = "no-session-id"
|
|
357
|
+
metadata = {
|
|
358
|
+
"source": "camel",
|
|
359
|
+
"agent_id": agent_session_id,
|
|
360
|
+
"agent_type": "camel_chat_agent",
|
|
361
|
+
"model_type": model_type_str,
|
|
362
|
+
}
|
|
363
|
+
metadata = {k: str(v) for k, v in metadata.items()}
|
|
346
364
|
update_langfuse_trace(
|
|
347
365
|
session_id=agent_session_id,
|
|
348
|
-
metadata=
|
|
349
|
-
|
|
350
|
-
"agent_id": agent_session_id,
|
|
351
|
-
"agent_type": "camel_chat_agent",
|
|
352
|
-
"model_type": str(self.model_type),
|
|
353
|
-
},
|
|
354
|
-
tags=["CAMEL-AI", str(self.model_type)],
|
|
366
|
+
metadata=metadata,
|
|
367
|
+
tags=["CAMEL-AI", model_type_str],
|
|
355
368
|
)
|
|
369
|
+
logger.info(f"metadata: {metadata}")
|
|
356
370
|
|
|
357
371
|
messages = self._adapt_messages_for_o1_models(messages)
|
|
358
372
|
response_format = response_format or self.model_config_dict.get(
|
|
@@ -147,7 +147,7 @@ Here is the content of the parent task for you to refer to:
|
|
|
147
147
|
Here are results of some prerequisite tasks that you can refer to:
|
|
148
148
|
|
|
149
149
|
==============================
|
|
150
|
-
{
|
|
150
|
+
{dependency_tasks_info}
|
|
151
151
|
==============================
|
|
152
152
|
|
|
153
153
|
Here are some additional information about the task:
|
|
@@ -202,16 +202,21 @@ TASK_DECOMPOSE_PROMPT = r"""You need to decompose the given task into subtasks a
|
|
|
202
202
|
* **DO NOT** use relative references like "the first task," "the paper mentioned above," or "the result from the previous step."
|
|
203
203
|
* **DO** write explicit instructions. For example, instead of "Analyze the document," write "Analyze the document titled 'The Future of AI'." The system will automatically provide the necessary inputs (like the document itself) from previous steps.
|
|
204
204
|
|
|
205
|
-
|
|
206
|
-
*
|
|
205
|
+
2. **Define Clear Deliverables**: Each subtask must specify a clear, concrete deliverable. This tells the agent exactly what to produce and provides a clear "definition of done."
|
|
206
|
+
* **DO NOT** use vague verbs like "analyze," "look into," or "research" without defining the output.
|
|
207
|
+
* **DO** specify the format and content of the output. For example, instead of "Analyze the attached report," write "Summarize the key findings of the attached report in a 3-bullet-point list." Instead of "Find contacts," write "Extract all names and email addresses from the document and return them as a JSON list of objects, where each object has a 'name' and 'email' key."
|
|
208
|
+
|
|
209
|
+
3. **Full Workflow Completion & Strategic Grouping**:
|
|
210
|
+
* **Preserve the Entire Goal**: Ensure the decomposed subtasks collectively achieve the *entire* original task. Do not drop or ignore final steps like sending a message, submitting a form, or creating a file.
|
|
211
|
+
* **Group Sequential Actions**: If a series of steps must be done in order *and* can be handled by the same worker type (e.g., read, think, reply), group them into a single, comprehensive subtask. This maintains workflow and ensures the final goal is met.
|
|
207
212
|
|
|
208
|
-
|
|
213
|
+
4. **Aggressive Parallelization**:
|
|
209
214
|
* **Across Different Worker Specializations**: If distinct phases of the overall task require different types of workers (e.g., research by a 'SearchAgent', then content creation by a 'DocumentAgent'), define these as separate subtasks.
|
|
210
215
|
* **Within a Single Phase (Data/Task Parallelism)**: If a phase involves repetitive operations on multiple items (e.g., processing 10 documents, fetching 5 web pages, analyzing 3 datasets):
|
|
211
216
|
* Decompose this into parallel subtasks, one for each item or a small batch of items.
|
|
212
217
|
* This applies even if the same type of worker handles these parallel subtasks. The goal is to leverage multiple available workers or allow concurrent processing.
|
|
213
218
|
|
|
214
|
-
|
|
219
|
+
5. **Subtask Design for Efficiency**:
|
|
215
220
|
* **Actionable and Well-Defined**: Each subtask should have a clear, achievable goal.
|
|
216
221
|
* **Balanced Granularity**: Make subtasks large enough to be meaningful but small enough to enable parallelism and quick feedback. Avoid overly large subtasks that hide parallel opportunities.
|
|
217
222
|
* **Consider Dependencies**: While you list tasks sequentially, think about the true dependencies. The workforce manager will handle execution based on these implied dependencies and worker availability.
|
|
@@ -229,10 +234,10 @@ These principles aim to reduce overall completion time by maximizing concurrent
|
|
|
229
234
|
* **Correct Decomposition**:
|
|
230
235
|
```xml
|
|
231
236
|
<tasks>
|
|
232
|
-
<task>Create a short blog post about the benefits of Python by researching key benefits, writing a 300-word article, and finding a suitable image.</task>
|
|
237
|
+
<task>Create a short blog post about the benefits of Python by researching key benefits, writing a 300-word article, and finding a suitable image. The final output should be a single string containing the 300-word article followed by the image URL.</task>
|
|
233
238
|
</tasks>
|
|
234
239
|
```
|
|
235
|
-
* **Reasoning**: All steps are sequential and can be handled by the same worker type (`Document Agent`). Grouping them into one subtask is efficient and maintains the workflow, following the "Strategic Grouping" principle.
|
|
240
|
+
* **Reasoning**: All steps are sequential and can be handled by the same worker type (`Document Agent`). Grouping them into one subtask is efficient and maintains the workflow, following the "Strategic Grouping" principle. **The deliverable is clearly defined as a single string.**
|
|
236
241
|
|
|
237
242
|
***
|
|
238
243
|
**Example 2: Parallel Task Across Different Workers**
|
|
@@ -245,14 +250,14 @@ These principles aim to reduce overall completion time by maximizing concurrent
|
|
|
245
250
|
* **Correct Decomposition**:
|
|
246
251
|
```xml
|
|
247
252
|
<tasks>
|
|
248
|
-
<task>Create a financial summary for Apple (AAPL) for Q2.</task>
|
|
249
|
-
<task>Create a financial summary for Google (GOOGL) for Q2.</task>
|
|
250
|
-
<task>Perform market sentiment analysis for Apple (AAPL) for Q2.</task>
|
|
251
|
-
<task>Perform market sentiment analysis for Google (GOOGL) for Q2.</task>
|
|
252
|
-
<task>Compile the provided financial summaries and market sentiment
|
|
253
|
+
<task>Create a 1-paragraph financial summary for Apple (AAPL) for Q2, covering revenue, net income, and EPS. The output must be a plain text paragraph.</task>
|
|
254
|
+
<task>Create a 1-paragraph financial summary for Google (GOOGL) for Q2, covering revenue, net income, and EPS. The output must be a plain text paragraph.</task>
|
|
255
|
+
<task>Perform a market sentiment analysis for Apple (AAPL) for Q2, returning a single sentiment score from -1 (very negative) to 1 (very positive). The output must be a single floating-point number.</task>
|
|
256
|
+
<task>Perform a market sentiment analysis for Google (GOOGL) for Q2, returning a single sentiment score from -1 (very negative) to 1 (very positive). The output must be a single floating-point number.</task>
|
|
257
|
+
<task>Compile the provided financial summaries and market sentiment scores for Apple (AAPL) and Google (GOOGL) into a single Q2 performance report. The report should be a markdown-formatted document.</task>
|
|
253
258
|
</tasks>
|
|
254
259
|
```
|
|
255
|
-
* **Reasoning**: The financial analysis and market research can be done in parallel for both companies. The final report depends on all previous steps. This decomposition leverages worker specialization and parallelism, following the "Aggressive Parallelization" principle.
|
|
260
|
+
* **Reasoning**: The financial analysis and market research can be done in parallel for both companies. The final report depends on all previous steps. This decomposition leverages worker specialization and parallelism, following the "Aggressive Parallelization" principle. **Each subtask has a clearly defined deliverable.**
|
|
256
261
|
***
|
|
257
262
|
|
|
258
263
|
**END OF EXAMPLES** - Now, apply these principles and examples to decompose the following task.
|
|
@@ -312,6 +317,8 @@ Additional Info: {additional_info}
|
|
|
312
317
|
2. **REPLAN**: Modify the task content to address the underlying issue
|
|
313
318
|
- Use for: Unclear requirements, insufficient context, correctable errors
|
|
314
319
|
- Provide: Modified task content that addresses the failure cause
|
|
320
|
+
- **CRITICAL**: The replanned task MUST be a clear, actionable
|
|
321
|
+
instruction for an AI agent, not a question or request for a human.
|
|
315
322
|
|
|
316
323
|
3. **DECOMPOSE**: Break the task into smaller, more manageable subtasks
|
|
317
324
|
- Use for: Complex tasks, capability mismatches, persistent failures
|
|
@@ -324,10 +331,13 @@ Additional Info: {additional_info}
|
|
|
324
331
|
|
|
325
332
|
- **Connection/Network Errors**: Almost always choose RETRY
|
|
326
333
|
- **Model Processing Errors**: Consider REPLAN if the task can be clarified, otherwise DECOMPOSE
|
|
327
|
-
- **Capability Gaps**: Choose DECOMPOSE to break into simpler parts
|
|
334
|
+
- **Capability Gaps**: Choose DECOMPOSE to break into simpler parts. If a
|
|
335
|
+
replan can work, ensure the new task is a command for an agent, not a
|
|
336
|
+
request to a user.
|
|
328
337
|
- **Ambiguous Requirements**: Choose REPLAN with clearer instructions
|
|
329
338
|
- **High Failure Count**: Lean towards DECOMPOSE rather than repeated retries
|
|
330
|
-
- **Deep Tasks (depth > 2)**: Prefer RETRY or REPLAN over further
|
|
339
|
+
- **Deep Tasks (depth > 2)**: Prefer RETRY or REPLAN over further
|
|
340
|
+
decomposition
|
|
331
341
|
|
|
332
342
|
**RESPONSE FORMAT:**
|
|
333
343
|
You must return a valid JSON object with these fields:
|
|
@@ -122,7 +122,7 @@ class RolePlayingWorker(Worker):
|
|
|
122
122
|
prompt = ROLEPLAY_PROCESS_TASK_PROMPT.format(
|
|
123
123
|
content=task.content,
|
|
124
124
|
parent_task_content=task.parent.content if task.parent else "",
|
|
125
|
-
|
|
125
|
+
dependency_tasks_info=dependency_tasks_info,
|
|
126
126
|
additional_info=task.additional_info,
|
|
127
127
|
)
|
|
128
128
|
role_play_session = RolePlaying(
|
|
@@ -410,11 +410,13 @@ class SingleAgentWorker(Worker):
|
|
|
410
410
|
f"{getattr(worker_agent, 'agent_id', worker_agent.role_name)} "
|
|
411
411
|
f"(from pool/clone of "
|
|
412
412
|
f"{getattr(self.worker, 'agent_id', self.worker.role_name)}) "
|
|
413
|
-
f"to process task {task.content}",
|
|
414
|
-
"response_content": response_content,
|
|
415
|
-
"tool_calls":
|
|
416
|
-
|
|
417
|
-
|
|
413
|
+
f"to process task: {task.content}",
|
|
414
|
+
"response_content": response_content[:50],
|
|
415
|
+
"tool_calls": str(
|
|
416
|
+
final_response.info.get("tool_calls")
|
|
417
|
+
if isinstance(response, AsyncStreamingChatAgentResponse)
|
|
418
|
+
else response.info.get("tool_calls")
|
|
419
|
+
)[:50],
|
|
418
420
|
"total_tokens": total_tokens,
|
|
419
421
|
}
|
|
420
422
|
|
|
@@ -445,11 +447,11 @@ class SingleAgentWorker(Worker):
|
|
|
445
447
|
f"\n{color}{task_result.content}{Fore.RESET}\n======", # type: ignore[union-attr]
|
|
446
448
|
)
|
|
447
449
|
|
|
450
|
+
task.result = task_result.content # type: ignore[union-attr]
|
|
451
|
+
|
|
448
452
|
if task_result.failed: # type: ignore[union-attr]
|
|
449
453
|
return TaskState.FAILED
|
|
450
454
|
|
|
451
|
-
task.result = task_result.content # type: ignore[union-attr]
|
|
452
|
-
|
|
453
455
|
if is_task_result_insufficient(task):
|
|
454
456
|
print(
|
|
455
457
|
f"{Fore.RED}Task {task.id}: Content validation failed - "
|
|
@@ -1008,7 +1008,7 @@ class Workforce(BaseNode):
|
|
|
1008
1008
|
if not validate_task_content(new_content, task_id):
|
|
1009
1009
|
logger.warning(
|
|
1010
1010
|
f"Task {task_id} content modification rejected: "
|
|
1011
|
-
f"Invalid content. Content preview: '{new_content
|
|
1011
|
+
f"Invalid content. Content preview: '{new_content}'"
|
|
1012
1012
|
)
|
|
1013
1013
|
return False
|
|
1014
1014
|
|
|
@@ -1194,7 +1194,7 @@ class Workforce(BaseNode):
|
|
|
1194
1194
|
task.result = "Task failed: Invalid or empty content provided"
|
|
1195
1195
|
logger.warning(
|
|
1196
1196
|
f"Task {task.id} rejected: Invalid or empty content. "
|
|
1197
|
-
f"Content preview: '{task.content
|
|
1197
|
+
f"Content preview: '{task.content}'"
|
|
1198
1198
|
)
|
|
1199
1199
|
return task
|
|
1200
1200
|
|
|
@@ -1327,7 +1327,7 @@ class Workforce(BaseNode):
|
|
|
1327
1327
|
task.result = "Task failed: Invalid or empty content provided"
|
|
1328
1328
|
logger.warning(
|
|
1329
1329
|
f"Task {task.id} rejected: Invalid or empty content. "
|
|
1330
|
-
f"Content preview: '{task.content
|
|
1330
|
+
f"Content preview: '{task.content}'"
|
|
1331
1331
|
)
|
|
1332
1332
|
return task
|
|
1333
1333
|
|
|
@@ -1721,23 +1721,51 @@ class Workforce(BaseNode):
|
|
|
1721
1721
|
|
|
1722
1722
|
def _get_child_nodes_info(self) -> str:
|
|
1723
1723
|
r"""Get the information of all the child nodes under this node."""
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1724
|
+
return "".join(
|
|
1725
|
+
f"<{child.node_id}>:<{child.description}>:<{self._get_node_info(child)}>\n"
|
|
1726
|
+
for child in self._children
|
|
1727
|
+
)
|
|
1728
|
+
|
|
1729
|
+
def _get_node_info(self, node) -> str:
|
|
1730
|
+
r"""Get descriptive information for a specific node type."""
|
|
1731
|
+
if isinstance(node, Workforce):
|
|
1732
|
+
return "A Workforce node"
|
|
1733
|
+
elif isinstance(node, SingleAgentWorker):
|
|
1734
|
+
return self._get_single_agent_info(node)
|
|
1735
|
+
elif isinstance(node, RolePlayingWorker):
|
|
1736
|
+
return "A Role playing node"
|
|
1737
|
+
else:
|
|
1738
|
+
return "Unknown node"
|
|
1739
|
+
|
|
1740
|
+
def _get_single_agent_info(self, worker: 'SingleAgentWorker') -> str:
|
|
1741
|
+
r"""Get formatted information for a SingleAgentWorker node."""
|
|
1742
|
+
toolkit_tools = self._group_tools_by_toolkit(worker.worker.tool_dict)
|
|
1743
|
+
|
|
1744
|
+
if not toolkit_tools:
|
|
1745
|
+
return "no tools available"
|
|
1746
|
+
|
|
1747
|
+
toolkit_info = []
|
|
1748
|
+
for toolkit_name, tools in sorted(toolkit_tools.items()):
|
|
1749
|
+
tools_str = ', '.join(sorted(tools))
|
|
1750
|
+
toolkit_info.append(f"{toolkit_name}({tools_str})")
|
|
1751
|
+
|
|
1752
|
+
return " | ".join(toolkit_info)
|
|
1753
|
+
|
|
1754
|
+
def _group_tools_by_toolkit(self, tool_dict: dict) -> dict[str, list[str]]:
|
|
1755
|
+
r"""Group tools by their parent toolkit class names."""
|
|
1756
|
+
toolkit_tools: dict[str, list[str]] = {}
|
|
1757
|
+
|
|
1758
|
+
for tool_name, tool in tool_dict.items():
|
|
1759
|
+
if hasattr(tool.func, '__self__'):
|
|
1760
|
+
toolkit_name = tool.func.__self__.__class__.__name__
|
|
1734
1761
|
else:
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
)
|
|
1740
|
-
|
|
1762
|
+
toolkit_name = "Standalone"
|
|
1763
|
+
|
|
1764
|
+
if toolkit_name not in toolkit_tools:
|
|
1765
|
+
toolkit_tools[toolkit_name] = []
|
|
1766
|
+
toolkit_tools[toolkit_name].append(tool_name)
|
|
1767
|
+
|
|
1768
|
+
return toolkit_tools
|
|
1741
1769
|
|
|
1742
1770
|
def _get_valid_worker_ids(self) -> set:
|
|
1743
1771
|
r"""Get all valid worker IDs from child nodes.
|
|
@@ -1853,7 +1881,7 @@ class Workforce(BaseNode):
|
|
|
1853
1881
|
logger.error(
|
|
1854
1882
|
f"JSON parsing error in task assignment: Invalid response "
|
|
1855
1883
|
f"format - {e}. Response content: "
|
|
1856
|
-
f"{response.msg.content
|
|
1884
|
+
f"{response.msg.content}"
|
|
1857
1885
|
)
|
|
1858
1886
|
return TaskAssignResult(assignments=[])
|
|
1859
1887
|
|
|
@@ -1985,6 +2013,37 @@ class Workforce(BaseNode):
|
|
|
1985
2013
|
|
|
1986
2014
|
return final_assignments
|
|
1987
2015
|
|
|
2016
|
+
def _update_task_dependencies_from_assignments(
|
|
2017
|
+
self, assignments: List[TaskAssignment], tasks: List[Task]
|
|
2018
|
+
) -> None:
|
|
2019
|
+
r"""Update Task.dependencies with actual Task objects based on
|
|
2020
|
+
assignments.
|
|
2021
|
+
|
|
2022
|
+
Args:
|
|
2023
|
+
assignments (List[TaskAssignment]): The task assignments
|
|
2024
|
+
containing dependency IDs.
|
|
2025
|
+
tasks (List[Task]): The tasks that were assigned.
|
|
2026
|
+
"""
|
|
2027
|
+
# Create a lookup map for all available tasks
|
|
2028
|
+
all_tasks = {}
|
|
2029
|
+
for task_list in [self._completed_tasks, self._pending_tasks, tasks]:
|
|
2030
|
+
for task in task_list:
|
|
2031
|
+
all_tasks[task.id] = task
|
|
2032
|
+
|
|
2033
|
+
# Update dependencies for each assigned task
|
|
2034
|
+
for assignment in assignments:
|
|
2035
|
+
if not assignment.dependencies:
|
|
2036
|
+
continue
|
|
2037
|
+
|
|
2038
|
+
matching_tasks = [t for t in tasks if t.id == assignment.task_id]
|
|
2039
|
+
if matching_tasks:
|
|
2040
|
+
task = matching_tasks[0]
|
|
2041
|
+
task.dependencies = [
|
|
2042
|
+
all_tasks[dep_id]
|
|
2043
|
+
for dep_id in assignment.dependencies
|
|
2044
|
+
if dep_id in all_tasks
|
|
2045
|
+
]
|
|
2046
|
+
|
|
1988
2047
|
async def _find_assignee(
|
|
1989
2048
|
self,
|
|
1990
2049
|
tasks: List[Task],
|
|
@@ -2021,19 +2080,24 @@ class Workforce(BaseNode):
|
|
|
2021
2080
|
|
|
2022
2081
|
# if all assignments are valid and all tasks are assigned, return early
|
|
2023
2082
|
if not invalid_assignments and not unassigned_tasks:
|
|
2083
|
+
self._update_task_dependencies_from_assignments(
|
|
2084
|
+
valid_assignments, tasks
|
|
2085
|
+
)
|
|
2024
2086
|
return TaskAssignResult(assignments=valid_assignments)
|
|
2025
2087
|
|
|
2026
|
-
# handle retry and fallback for
|
|
2027
|
-
#
|
|
2028
|
-
all_problem_assignments = invalid_assignments
|
|
2088
|
+
# handle retry and fallback for invalid assignments and unassigned
|
|
2089
|
+
# tasks
|
|
2029
2090
|
retry_and_fallback_assignments = (
|
|
2030
2091
|
await self._handle_assignment_retry_and_fallback(
|
|
2031
|
-
|
|
2092
|
+
invalid_assignments, tasks, valid_worker_ids
|
|
2032
2093
|
)
|
|
2033
2094
|
)
|
|
2034
|
-
valid_assignments
|
|
2095
|
+
all_assignments = valid_assignments + retry_and_fallback_assignments
|
|
2096
|
+
|
|
2097
|
+
# Update Task.dependencies for all final assignments
|
|
2098
|
+
self._update_task_dependencies_from_assignments(all_assignments, tasks)
|
|
2035
2099
|
|
|
2036
|
-
return TaskAssignResult(assignments=
|
|
2100
|
+
return TaskAssignResult(assignments=all_assignments)
|
|
2037
2101
|
|
|
2038
2102
|
async def _post_task(self, task: Task, assignee_id: str) -> None:
|
|
2039
2103
|
# Record the start time when a task is posted
|
|
@@ -2107,7 +2171,7 @@ class Workforce(BaseNode):
|
|
|
2107
2171
|
)
|
|
2108
2172
|
new_node_conf = WorkerConf(
|
|
2109
2173
|
description=f"Fallback worker for task: "
|
|
2110
|
-
f"{task.content
|
|
2174
|
+
f"{task.content}",
|
|
2111
2175
|
role="General Assistant",
|
|
2112
2176
|
sys_msg="You are a general assistant that can help "
|
|
2113
2177
|
"with various tasks.",
|
|
@@ -2117,8 +2181,7 @@ class Workforce(BaseNode):
|
|
|
2117
2181
|
response.msg.content,
|
|
2118
2182
|
schema=WorkerConf,
|
|
2119
2183
|
fallback_values={
|
|
2120
|
-
"description": f"Worker for task: "
|
|
2121
|
-
f"{task.content[:50]}...",
|
|
2184
|
+
"description": f"Worker for task: " f"{task.content}",
|
|
2122
2185
|
"role": "Task Specialist",
|
|
2123
2186
|
"sys_msg": f"You are a specialist for: {task.content}",
|
|
2124
2187
|
},
|
|
@@ -2130,7 +2193,7 @@ class Workforce(BaseNode):
|
|
|
2130
2193
|
new_node_conf = WorkerConf(**result)
|
|
2131
2194
|
else:
|
|
2132
2195
|
new_node_conf = WorkerConf(
|
|
2133
|
-
description=f"Worker for task: {task.content
|
|
2196
|
+
description=f"Worker for task: {task.content}",
|
|
2134
2197
|
role="Task Specialist",
|
|
2135
2198
|
sys_msg=f"You are a specialist for: {task.content}",
|
|
2136
2199
|
)
|
|
@@ -2147,7 +2210,7 @@ class Workforce(BaseNode):
|
|
|
2147
2210
|
# Create a fallback worker configuration
|
|
2148
2211
|
new_node_conf = WorkerConf(
|
|
2149
2212
|
description=f"Fallback worker for "
|
|
2150
|
-
f"task: {task.content
|
|
2213
|
+
f"task: {task.content}",
|
|
2151
2214
|
role="General Assistant",
|
|
2152
2215
|
sys_msg="You are a general assistant that can help "
|
|
2153
2216
|
"with various tasks.",
|
|
@@ -2160,7 +2223,7 @@ class Workforce(BaseNode):
|
|
|
2160
2223
|
logger.error(
|
|
2161
2224
|
f"JSON parsing error in worker creation: Invalid "
|
|
2162
2225
|
f"response format - {e}. Response content: "
|
|
2163
|
-
f"{response.msg.content
|
|
2226
|
+
f"{response.msg.content}"
|
|
2164
2227
|
)
|
|
2165
2228
|
raise RuntimeError(
|
|
2166
2229
|
f"Failed to create worker for task {task.id}: "
|
|
@@ -2364,7 +2427,7 @@ class Workforce(BaseNode):
|
|
|
2364
2427
|
f"Task {task.id} has exceeded maximum retry attempts "
|
|
2365
2428
|
f"({MAX_TASK_RETRIES}). Final failure "
|
|
2366
2429
|
f"reason: {detailed_error}. "
|
|
2367
|
-
f"Task content: '{task.content
|
|
2430
|
+
f"Task content: '{task.content}'"
|
|
2368
2431
|
)
|
|
2369
2432
|
self._cleanup_task_tracking(task.id)
|
|
2370
2433
|
# Mark task as completed for dependency tracking before halting
|
|
@@ -2793,7 +2856,7 @@ class Workforce(BaseNode):
|
|
|
2793
2856
|
# useful results
|
|
2794
2857
|
if is_task_result_insufficient(returned_task):
|
|
2795
2858
|
result_preview = (
|
|
2796
|
-
returned_task.result
|
|
2859
|
+
returned_task.result
|
|
2797
2860
|
if returned_task.result
|
|
2798
2861
|
else "No result"
|
|
2799
2862
|
)
|