camel-ai 0.2.79a0__py3-none-any.whl → 0.2.79a1__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/_utils.py +38 -0
- camel/agents/chat_agent.py +788 -255
- camel/memories/agent_memories.py +34 -0
- camel/memories/base.py +26 -0
- camel/memories/blocks/chat_history_block.py +115 -0
- camel/memories/context_creators/score_based.py +25 -384
- camel/messages/base.py +26 -0
- camel/models/azure_openai_model.py +113 -67
- camel/models/model_factory.py +17 -1
- camel/models/openai_compatible_model.py +62 -32
- camel/models/openai_model.py +61 -35
- camel/models/samba_model.py +34 -15
- camel/models/sglang_model.py +41 -11
- camel/societies/workforce/__init__.py +2 -0
- camel/societies/workforce/role_playing_worker.py +15 -11
- camel/societies/workforce/single_agent_worker.py +86 -364
- camel/societies/workforce/utils.py +2 -1
- camel/societies/workforce/workflow_memory_manager.py +772 -0
- camel/societies/workforce/workforce.py +96 -32
- camel/storages/vectordb_storages/oceanbase.py +5 -4
- camel/toolkits/file_toolkit.py +166 -0
- camel/toolkits/message_integration.py +15 -13
- camel/toolkits/terminal_toolkit/terminal_toolkit.py +112 -79
- camel/types/enums.py +1 -0
- camel/utils/context_utils.py +148 -2
- {camel_ai-0.2.79a0.dist-info → camel_ai-0.2.79a1.dist-info}/METADATA +1 -1
- {camel_ai-0.2.79a0.dist-info → camel_ai-0.2.79a1.dist-info}/RECORD +30 -29
- {camel_ai-0.2.79a0.dist-info → camel_ai-0.2.79a1.dist-info}/WHEEL +0 -0
- {camel_ai-0.2.79a0.dist-info → camel_ai-0.2.79a1.dist-info}/licenses/LICENSE +0 -0
camel/models/sglang_model.py
CHANGED
|
@@ -72,8 +72,16 @@ class SGLangModel(BaseModelBackend):
|
|
|
72
72
|
(default: :obj:`None`)
|
|
73
73
|
max_retries (int, optional): Maximum number of retries for API calls.
|
|
74
74
|
(default: :obj:`3`)
|
|
75
|
+
client (Optional[Any], optional): A custom synchronous
|
|
76
|
+
OpenAI-compatible client instance. If provided, this client will
|
|
77
|
+
be used instead of creating a new one. Note: When using custom
|
|
78
|
+
clients with SGLang, server auto-start features will be disabled.
|
|
79
|
+
(default: :obj:`None`)
|
|
80
|
+
async_client (Optional[Any], optional): A custom asynchronous
|
|
81
|
+
OpenAI-compatible client instance. If provided, this client will
|
|
82
|
+
be used instead of creating a new one. (default: :obj:`None`)
|
|
75
83
|
**kwargs (Any): Additional arguments to pass to the client
|
|
76
|
-
initialization.
|
|
84
|
+
initialization. Ignored if custom clients are provided.
|
|
77
85
|
|
|
78
86
|
Reference: https://sgl-project.github.io/backend/openai_api_completions.
|
|
79
87
|
html
|
|
@@ -88,6 +96,8 @@ class SGLangModel(BaseModelBackend):
|
|
|
88
96
|
token_counter: Optional[BaseTokenCounter] = None,
|
|
89
97
|
timeout: Optional[float] = None,
|
|
90
98
|
max_retries: int = 3,
|
|
99
|
+
client: Optional[Any] = None,
|
|
100
|
+
async_client: Optional[Any] = None,
|
|
91
101
|
**kwargs: Any,
|
|
92
102
|
) -> None:
|
|
93
103
|
if model_config_dict is None:
|
|
@@ -111,9 +121,10 @@ class SGLangModel(BaseModelBackend):
|
|
|
111
121
|
max_retries,
|
|
112
122
|
)
|
|
113
123
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
124
|
+
# Use custom clients if provided, otherwise create new ones
|
|
125
|
+
if client is not None:
|
|
126
|
+
self._client = client
|
|
127
|
+
elif self._url:
|
|
117
128
|
# Initialize the client if an existing URL is provided
|
|
118
129
|
self._client = OpenAI(
|
|
119
130
|
timeout=self._timeout,
|
|
@@ -122,6 +133,12 @@ class SGLangModel(BaseModelBackend):
|
|
|
122
133
|
base_url=self._url,
|
|
123
134
|
**kwargs,
|
|
124
135
|
)
|
|
136
|
+
else:
|
|
137
|
+
self._client = None
|
|
138
|
+
|
|
139
|
+
if async_client is not None:
|
|
140
|
+
self._async_client = async_client
|
|
141
|
+
elif self._url:
|
|
125
142
|
self._async_client = AsyncOpenAI(
|
|
126
143
|
timeout=self._timeout,
|
|
127
144
|
max_retries=self._max_retries,
|
|
@@ -129,6 +146,8 @@ class SGLangModel(BaseModelBackend):
|
|
|
129
146
|
base_url=self._url,
|
|
130
147
|
**kwargs,
|
|
131
148
|
)
|
|
149
|
+
else:
|
|
150
|
+
self._async_client = None
|
|
132
151
|
|
|
133
152
|
def _start_server(self) -> None:
|
|
134
153
|
try:
|
|
@@ -159,13 +178,24 @@ class SGLangModel(BaseModelBackend):
|
|
|
159
178
|
)
|
|
160
179
|
self._inactivity_thread.start()
|
|
161
180
|
self.last_run_time = time.time()
|
|
162
|
-
# Initialize
|
|
163
|
-
self._client
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
181
|
+
# Initialize client after server starts if not already set
|
|
182
|
+
if self._client is None:
|
|
183
|
+
self._client = OpenAI(
|
|
184
|
+
timeout=self._timeout,
|
|
185
|
+
max_retries=self._max_retries,
|
|
186
|
+
api_key="Set-but-ignored", # required but ignored
|
|
187
|
+
base_url=self._url,
|
|
188
|
+
)
|
|
189
|
+
if (
|
|
190
|
+
not hasattr(self, '_async_client')
|
|
191
|
+
or self._async_client is None
|
|
192
|
+
):
|
|
193
|
+
self._async_client = AsyncOpenAI(
|
|
194
|
+
timeout=self._timeout,
|
|
195
|
+
max_retries=self._max_retries,
|
|
196
|
+
api_key="Set-but-ignored", # required but ignored
|
|
197
|
+
base_url=self._url,
|
|
198
|
+
)
|
|
169
199
|
except Exception as e:
|
|
170
200
|
raise RuntimeError(f"Failed to start SGLang server: {e}") from e
|
|
171
201
|
|
|
@@ -14,10 +14,12 @@
|
|
|
14
14
|
|
|
15
15
|
from .role_playing_worker import RolePlayingWorker
|
|
16
16
|
from .single_agent_worker import SingleAgentWorker
|
|
17
|
+
from .workflow_memory_manager import WorkflowSelectionMethod
|
|
17
18
|
from .workforce import Workforce
|
|
18
19
|
|
|
19
20
|
__all__ = [
|
|
20
21
|
"Workforce",
|
|
21
22
|
"SingleAgentWorker",
|
|
22
23
|
"RolePlayingWorker",
|
|
24
|
+
"WorkflowSelectionMethod",
|
|
23
25
|
]
|
|
@@ -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
|
|
@@ -15,12 +15,8 @@ from __future__ import annotations
|
|
|
15
15
|
|
|
16
16
|
import asyncio
|
|
17
17
|
import datetime
|
|
18
|
-
import glob
|
|
19
|
-
import os
|
|
20
|
-
import re
|
|
21
18
|
import time
|
|
22
19
|
from collections import deque
|
|
23
|
-
from pathlib import Path
|
|
24
20
|
from typing import Any, Dict, List, Optional
|
|
25
21
|
|
|
26
22
|
from colorama import Fore
|
|
@@ -34,8 +30,11 @@ from camel.societies.workforce.structured_output_handler import (
|
|
|
34
30
|
)
|
|
35
31
|
from camel.societies.workforce.utils import TaskResult
|
|
36
32
|
from camel.societies.workforce.worker import Worker
|
|
33
|
+
from camel.societies.workforce.workflow_memory_manager import (
|
|
34
|
+
WorkflowMemoryManager,
|
|
35
|
+
)
|
|
37
36
|
from camel.tasks.task import Task, TaskState, is_task_result_insufficient
|
|
38
|
-
from camel.utils.context_utils import ContextUtility
|
|
37
|
+
from camel.utils.context_utils import ContextUtility
|
|
39
38
|
|
|
40
39
|
logger = get_logger(__name__)
|
|
41
40
|
|
|
@@ -254,6 +253,9 @@ class SingleAgentWorker(Worker):
|
|
|
254
253
|
# from all task processing
|
|
255
254
|
self._conversation_accumulator: Optional[ChatAgent] = None
|
|
256
255
|
|
|
256
|
+
# workflow memory manager for handling workflow operations
|
|
257
|
+
self._workflow_manager: Optional[WorkflowMemoryManager] = None
|
|
258
|
+
|
|
257
259
|
# note: context utility is set on the worker agent during save/load
|
|
258
260
|
# operations to avoid creating session folders during initialization
|
|
259
261
|
|
|
@@ -316,6 +318,21 @@ class SingleAgentWorker(Worker):
|
|
|
316
318
|
)
|
|
317
319
|
return self._conversation_accumulator
|
|
318
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
|
+
|
|
319
336
|
async def _process_task(
|
|
320
337
|
self, task: Task, dependencies: List[Task]
|
|
321
338
|
) -> TaskState:
|
|
@@ -342,11 +359,15 @@ class SingleAgentWorker(Worker):
|
|
|
342
359
|
|
|
343
360
|
try:
|
|
344
361
|
dependency_tasks_info = self._get_dep_tasks_info(dependencies)
|
|
345
|
-
prompt =
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
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
|
+
)
|
|
350
371
|
)
|
|
351
372
|
|
|
352
373
|
if self.use_structured_output_handler and self.structured_handler:
|
|
@@ -590,6 +611,8 @@ class SingleAgentWorker(Worker):
|
|
|
590
611
|
is based on either the worker's explicit role_name or the generated
|
|
591
612
|
task_title from the summary.
|
|
592
613
|
|
|
614
|
+
Delegates to WorkflowMemoryManager for all workflow operations.
|
|
615
|
+
|
|
593
616
|
Returns:
|
|
594
617
|
Dict[str, Any]: Result dictionary with keys:
|
|
595
618
|
- status (str): "success" or "error"
|
|
@@ -609,67 +632,24 @@ class SingleAgentWorker(Worker):
|
|
|
609
632
|
DeprecationWarning,
|
|
610
633
|
stacklevel=2,
|
|
611
634
|
)
|
|
612
|
-
try:
|
|
613
|
-
# validate requirements
|
|
614
|
-
validation_error = self._validate_workflow_save_requirements()
|
|
615
|
-
if validation_error:
|
|
616
|
-
return validation_error
|
|
617
|
-
|
|
618
|
-
# setup context utility and agent
|
|
619
|
-
context_util = self._get_context_utility()
|
|
620
|
-
self.worker.set_context_utility(context_util)
|
|
621
|
-
|
|
622
|
-
# prepare workflow summarization components
|
|
623
|
-
structured_prompt = self._prepare_workflow_prompt()
|
|
624
|
-
agent_to_summarize = self._select_agent_for_summarization(
|
|
625
|
-
context_util
|
|
626
|
-
)
|
|
627
635
|
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
'assistant',
|
|
633
|
-
'agent',
|
|
634
|
-
'user',
|
|
635
|
-
'system',
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
# generate and save workflow summary
|
|
639
|
-
# if role_name is explicit, use it for filename
|
|
640
|
-
# if role_name is generic, pass none to let summarize use
|
|
641
|
-
# task_title
|
|
642
|
-
filename = (
|
|
643
|
-
self._generate_workflow_filename()
|
|
644
|
-
if use_role_name_for_filename
|
|
645
|
-
else None
|
|
646
|
-
)
|
|
636
|
+
manager = self._get_workflow_manager()
|
|
637
|
+
result = manager.save_workflow(
|
|
638
|
+
conversation_accumulator=self._conversation_accumulator
|
|
639
|
+
)
|
|
647
640
|
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
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"
|
|
652
649
|
)
|
|
650
|
+
self._conversation_accumulator = None
|
|
653
651
|
|
|
654
|
-
|
|
655
|
-
result["worker_description"] = self.description
|
|
656
|
-
if self._conversation_accumulator is not None:
|
|
657
|
-
logger.info(
|
|
658
|
-
"Cleaning up conversation accumulator after workflow "
|
|
659
|
-
"summarization"
|
|
660
|
-
)
|
|
661
|
-
self._conversation_accumulator = None
|
|
662
|
-
|
|
663
|
-
return result
|
|
664
|
-
|
|
665
|
-
except Exception as e:
|
|
666
|
-
return {
|
|
667
|
-
"status": "error",
|
|
668
|
-
"summary": "",
|
|
669
|
-
"file_path": None,
|
|
670
|
-
"worker_description": self.description,
|
|
671
|
-
"message": f"Failed to save workflow memories: {e!s}",
|
|
672
|
-
}
|
|
652
|
+
return result
|
|
673
653
|
|
|
674
654
|
async def save_workflow_memories_async(self) -> Dict[str, Any]:
|
|
675
655
|
r"""Asynchronously save the worker's current workflow memories using
|
|
@@ -679,6 +659,8 @@ class SingleAgentWorker(Worker):
|
|
|
679
659
|
asummarize() for non-blocking LLM calls, enabling parallel
|
|
680
660
|
summarization of multiple workers.
|
|
681
661
|
|
|
662
|
+
Delegates to WorkflowMemoryManager for all workflow operations.
|
|
663
|
+
|
|
682
664
|
Returns:
|
|
683
665
|
Dict[str, Any]: Result dictionary with keys:
|
|
684
666
|
- status (str): "success" or "error"
|
|
@@ -686,322 +668,62 @@ class SingleAgentWorker(Worker):
|
|
|
686
668
|
- file_path (str): Path to saved file
|
|
687
669
|
- worker_description (str): Worker description used
|
|
688
670
|
"""
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
return validation_error
|
|
694
|
-
|
|
695
|
-
# setup context utility and agent
|
|
696
|
-
context_util = self._get_context_utility()
|
|
697
|
-
self.worker.set_context_utility(context_util)
|
|
698
|
-
|
|
699
|
-
# prepare workflow summarization components
|
|
700
|
-
structured_prompt = self._prepare_workflow_prompt()
|
|
701
|
-
agent_to_summarize = self._select_agent_for_summarization(
|
|
702
|
-
context_util
|
|
703
|
-
)
|
|
704
|
-
|
|
705
|
-
# check if we should use role_name or let summarize extract
|
|
706
|
-
# task_title
|
|
707
|
-
role_name = getattr(self.worker, 'role_name', 'assistant')
|
|
708
|
-
use_role_name_for_filename = role_name.lower() not in {
|
|
709
|
-
'assistant',
|
|
710
|
-
'agent',
|
|
711
|
-
'user',
|
|
712
|
-
'system',
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
# generate and save workflow summary
|
|
716
|
-
# if role_name is explicit, use it for filename
|
|
717
|
-
# if role_name is generic, pass none to let summarize use
|
|
718
|
-
# task_title
|
|
719
|
-
filename = (
|
|
720
|
-
self._generate_workflow_filename()
|
|
721
|
-
if use_role_name_for_filename
|
|
722
|
-
else None
|
|
723
|
-
)
|
|
671
|
+
manager = self._get_workflow_manager()
|
|
672
|
+
result = await manager.save_workflow_async(
|
|
673
|
+
conversation_accumulator=self._conversation_accumulator
|
|
674
|
+
)
|
|
724
675
|
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
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"
|
|
730
684
|
)
|
|
685
|
+
self._conversation_accumulator = None
|
|
731
686
|
|
|
732
|
-
|
|
733
|
-
result["worker_description"] = self.description
|
|
734
|
-
if self._conversation_accumulator is not None:
|
|
735
|
-
logger.info(
|
|
736
|
-
"Cleaning up conversation accumulator after workflow "
|
|
737
|
-
"summarization"
|
|
738
|
-
)
|
|
739
|
-
self._conversation_accumulator = None
|
|
740
|
-
|
|
741
|
-
return result
|
|
742
|
-
|
|
743
|
-
except Exception as e:
|
|
744
|
-
return {
|
|
745
|
-
"status": "error",
|
|
746
|
-
"summary": "",
|
|
747
|
-
"file_path": None,
|
|
748
|
-
"worker_description": self.description,
|
|
749
|
-
"message": f"Failed to save workflow memories: {e!s}",
|
|
750
|
-
}
|
|
687
|
+
return result
|
|
751
688
|
|
|
752
689
|
def load_workflow_memories(
|
|
753
690
|
self,
|
|
754
691
|
pattern: Optional[str] = None,
|
|
755
|
-
|
|
692
|
+
max_workflows: int = 3,
|
|
756
693
|
session_id: Optional[str] = None,
|
|
694
|
+
use_smart_selection: bool = True,
|
|
757
695
|
) -> bool:
|
|
758
|
-
r"""Load workflow memories
|
|
759
|
-
from saved files.
|
|
696
|
+
r"""Load workflow memories using intelligent agent-based selection.
|
|
760
697
|
|
|
761
|
-
This method
|
|
762
|
-
|
|
763
|
-
|
|
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.
|
|
764
703
|
|
|
765
704
|
Args:
|
|
766
|
-
pattern (Optional[str]):
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
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.
|
|
770
710
|
(default: :obj:`3`)
|
|
771
711
|
session_id (Optional[str]): Specific workforce session ID to load
|
|
772
712
|
from. If None, searches across all sessions.
|
|
773
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`)
|
|
774
718
|
|
|
775
719
|
Returns:
|
|
776
720
|
bool: True if workflow memories were successfully loaded, False
|
|
777
721
|
otherwise.
|
|
778
722
|
"""
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
# Find workflow memory files matching the pattern
|
|
786
|
-
workflow_files = self._find_workflow_files(pattern, session_id)
|
|
787
|
-
if not workflow_files:
|
|
788
|
-
return False
|
|
789
|
-
|
|
790
|
-
# Load the workflow memory files
|
|
791
|
-
loaded_count = self._load_workflow_files(
|
|
792
|
-
workflow_files, max_files_to_load
|
|
793
|
-
)
|
|
794
|
-
|
|
795
|
-
# Report results
|
|
796
|
-
logger.info(
|
|
797
|
-
f"Successfully loaded {loaded_count} workflow file(s) for "
|
|
798
|
-
f"{self.description}"
|
|
799
|
-
)
|
|
800
|
-
return loaded_count > 0
|
|
801
|
-
|
|
802
|
-
except Exception as e:
|
|
803
|
-
logger.warning(
|
|
804
|
-
f"Error loading workflow memories for {self.description}: "
|
|
805
|
-
f"{e!s}"
|
|
806
|
-
)
|
|
807
|
-
return False
|
|
808
|
-
|
|
809
|
-
def _find_workflow_files(
|
|
810
|
-
self, pattern: Optional[str], session_id: Optional[str] = None
|
|
811
|
-
) -> List[str]:
|
|
812
|
-
r"""Find and return sorted workflow files matching the pattern.
|
|
813
|
-
|
|
814
|
-
Args:
|
|
815
|
-
pattern (Optional[str]): Custom search pattern for workflow files.
|
|
816
|
-
If None, uses worker description to generate pattern.
|
|
817
|
-
session_id (Optional[str]): Specific session ID to search in.
|
|
818
|
-
If None, searches across all sessions.
|
|
819
|
-
|
|
820
|
-
Returns:
|
|
821
|
-
List[str]: Sorted list of workflow file paths (empty if
|
|
822
|
-
validation fails).
|
|
823
|
-
"""
|
|
824
|
-
# Ensure we have a ChatAgent worker
|
|
825
|
-
if not isinstance(self.worker, ChatAgent):
|
|
826
|
-
logger.warning(
|
|
827
|
-
f"Cannot load workflow: {self.description} worker is not "
|
|
828
|
-
"a ChatAgent"
|
|
829
|
-
)
|
|
830
|
-
return []
|
|
831
|
-
|
|
832
|
-
# generate filename-safe search pattern from worker role name
|
|
833
|
-
if pattern is None:
|
|
834
|
-
from camel.utils.context_utils import ContextUtility
|
|
835
|
-
|
|
836
|
-
# get role_name (always available, defaults to "assistant")
|
|
837
|
-
role_name = getattr(self.worker, 'role_name', 'assistant')
|
|
838
|
-
clean_name = ContextUtility.sanitize_workflow_filename(role_name)
|
|
839
|
-
|
|
840
|
-
# check if role_name is generic
|
|
841
|
-
generic_names = {'assistant', 'agent', 'user', 'system'}
|
|
842
|
-
if clean_name in generic_names:
|
|
843
|
-
# for generic role names, search for all workflow files
|
|
844
|
-
# since filename is based on task_title
|
|
845
|
-
pattern = "*_workflow*.md"
|
|
846
|
-
else:
|
|
847
|
-
# for explicit role names, search for role-specific files
|
|
848
|
-
pattern = f"{clean_name}_workflow*.md"
|
|
849
|
-
|
|
850
|
-
# Get the base workforce_workflows directory
|
|
851
|
-
camel_workdir = os.environ.get("CAMEL_WORKDIR")
|
|
852
|
-
if camel_workdir:
|
|
853
|
-
base_dir = os.path.join(camel_workdir, "workforce_workflows")
|
|
854
|
-
else:
|
|
855
|
-
base_dir = "workforce_workflows"
|
|
856
|
-
|
|
857
|
-
# search for workflow files in specified or all session directories
|
|
858
|
-
if session_id:
|
|
859
|
-
search_path = str(Path(base_dir) / session_id / pattern)
|
|
860
|
-
else:
|
|
861
|
-
# search across all session directories using wildcard pattern
|
|
862
|
-
search_path = str(Path(base_dir) / "*" / pattern)
|
|
863
|
-
workflow_files = glob.glob(search_path)
|
|
864
|
-
|
|
865
|
-
if not workflow_files:
|
|
866
|
-
logger.info(f"No workflow files found for pattern: {pattern}")
|
|
867
|
-
return []
|
|
868
|
-
|
|
869
|
-
# prioritize most recent sessions by session timestamp in
|
|
870
|
-
# directory name
|
|
871
|
-
def extract_session_timestamp(filepath: str) -> str:
|
|
872
|
-
match = re.search(r'session_(\d{8}_\d{6}_\d{6})', filepath)
|
|
873
|
-
return match.group(1) if match else ""
|
|
874
|
-
|
|
875
|
-
workflow_files.sort(key=extract_session_timestamp, reverse=True)
|
|
876
|
-
return workflow_files
|
|
877
|
-
|
|
878
|
-
def _load_workflow_files(
|
|
879
|
-
self, workflow_files: List[str], max_files_to_load: int
|
|
880
|
-
) -> int:
|
|
881
|
-
r"""Load workflow files and return count of successful loads.
|
|
882
|
-
|
|
883
|
-
Args:
|
|
884
|
-
workflow_files (List[str]): List of workflow file paths to load.
|
|
885
|
-
|
|
886
|
-
Returns:
|
|
887
|
-
int: Number of successfully loaded workflow files.
|
|
888
|
-
"""
|
|
889
|
-
loaded_count = 0
|
|
890
|
-
# limit loading to prevent context overflow
|
|
891
|
-
for file_path in workflow_files[:max_files_to_load]:
|
|
892
|
-
try:
|
|
893
|
-
# extract file and session info from full path
|
|
894
|
-
filename = os.path.basename(file_path).replace('.md', '')
|
|
895
|
-
session_dir = os.path.dirname(file_path)
|
|
896
|
-
session_id = os.path.basename(session_dir)
|
|
897
|
-
|
|
898
|
-
# create context utility for the specific session
|
|
899
|
-
# where file exists
|
|
900
|
-
temp_utility = ContextUtility.get_workforce_shared(session_id)
|
|
901
|
-
|
|
902
|
-
status = temp_utility.load_markdown_context_to_memory(
|
|
903
|
-
self.worker, filename
|
|
904
|
-
)
|
|
905
|
-
|
|
906
|
-
if "Context appended" in status:
|
|
907
|
-
loaded_count += 1
|
|
908
|
-
logger.info(f"Loaded workflow: {filename}")
|
|
909
|
-
else:
|
|
910
|
-
logger.warning(
|
|
911
|
-
f"Failed to load workflow {filename}: {status}"
|
|
912
|
-
)
|
|
913
|
-
|
|
914
|
-
except Exception as e:
|
|
915
|
-
logger.warning(
|
|
916
|
-
f"Failed to load workflow file {file_path}: {e!s}"
|
|
917
|
-
)
|
|
918
|
-
continue
|
|
919
|
-
|
|
920
|
-
return loaded_count
|
|
921
|
-
|
|
922
|
-
def _validate_workflow_save_requirements(self) -> Optional[Dict[str, Any]]:
|
|
923
|
-
r"""Validate requirements for workflow saving.
|
|
924
|
-
|
|
925
|
-
Returns:
|
|
926
|
-
Optional[Dict[str, Any]]: Error result dict if validation fails,
|
|
927
|
-
None if validation passes.
|
|
928
|
-
"""
|
|
929
|
-
if not isinstance(self.worker, ChatAgent):
|
|
930
|
-
return {
|
|
931
|
-
"status": "error",
|
|
932
|
-
"summary": "",
|
|
933
|
-
"file_path": None,
|
|
934
|
-
"worker_description": self.description,
|
|
935
|
-
"message": (
|
|
936
|
-
"Worker must be a ChatAgent instance to save workflow "
|
|
937
|
-
"memories"
|
|
938
|
-
),
|
|
939
|
-
}
|
|
940
|
-
return None
|
|
941
|
-
|
|
942
|
-
def _generate_workflow_filename(self) -> str:
|
|
943
|
-
r"""Generate a filename for the workflow based on worker role name.
|
|
944
|
-
|
|
945
|
-
Uses the worker's explicit role_name when available.
|
|
946
|
-
|
|
947
|
-
Returns:
|
|
948
|
-
str: Sanitized filename without timestamp and without .md
|
|
949
|
-
extension. Format: {role_name}_workflow
|
|
950
|
-
"""
|
|
951
|
-
from camel.utils.context_utils import ContextUtility
|
|
952
|
-
|
|
953
|
-
# get role_name (always available, defaults to "assistant"/"Assistant")
|
|
954
|
-
role_name = getattr(self.worker, 'role_name', 'assistant')
|
|
955
|
-
clean_name = ContextUtility.sanitize_workflow_filename(role_name)
|
|
956
|
-
|
|
957
|
-
return f"{clean_name}_workflow"
|
|
958
|
-
|
|
959
|
-
def _prepare_workflow_prompt(self) -> str:
|
|
960
|
-
r"""Prepare the structured prompt for workflow summarization.
|
|
961
|
-
|
|
962
|
-
Returns:
|
|
963
|
-
str: Structured prompt for workflow summary.
|
|
964
|
-
"""
|
|
965
|
-
workflow_prompt = WorkflowSummary.get_instruction_prompt()
|
|
966
|
-
return StructuredOutputHandler.generate_structured_prompt(
|
|
967
|
-
base_prompt=workflow_prompt, schema=WorkflowSummary
|
|
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,
|
|
968
729
|
)
|
|
969
|
-
|
|
970
|
-
def _select_agent_for_summarization(
|
|
971
|
-
self, context_util: ContextUtility
|
|
972
|
-
) -> ChatAgent:
|
|
973
|
-
r"""Select the best agent for workflow summarization.
|
|
974
|
-
|
|
975
|
-
Args:
|
|
976
|
-
context_util: Context utility to set on selected agent.
|
|
977
|
-
|
|
978
|
-
Returns:
|
|
979
|
-
ChatAgent: Agent to use for summarization.
|
|
980
|
-
"""
|
|
981
|
-
agent_to_summarize = self.worker
|
|
982
|
-
|
|
983
|
-
if self._conversation_accumulator is not None:
|
|
984
|
-
accumulator_messages, _ = (
|
|
985
|
-
self._conversation_accumulator.memory.get_context()
|
|
986
|
-
)
|
|
987
|
-
if accumulator_messages:
|
|
988
|
-
self._conversation_accumulator.set_context_utility(
|
|
989
|
-
context_util
|
|
990
|
-
)
|
|
991
|
-
agent_to_summarize = self._conversation_accumulator
|
|
992
|
-
logger.info(
|
|
993
|
-
f"Using conversation accumulator with "
|
|
994
|
-
f"{len(accumulator_messages)} messages for workflow "
|
|
995
|
-
f"summary"
|
|
996
|
-
)
|
|
997
|
-
else:
|
|
998
|
-
logger.info(
|
|
999
|
-
"Using original worker for workflow summary (no "
|
|
1000
|
-
"accumulated conversations)"
|
|
1001
|
-
)
|
|
1002
|
-
else:
|
|
1003
|
-
logger.info(
|
|
1004
|
-
"Using original worker for workflow summary (no accumulator)"
|
|
1005
|
-
)
|
|
1006
|
-
|
|
1007
|
-
return agent_to_summarize
|