|
|
|
@ -12,11 +12,7 @@ from logging.handlers import RotatingFileHandler
|
|
|
|
|
from typing import Any, Dict, List, Optional
|
|
|
|
|
|
|
|
|
|
from pydantic import BaseModel, Field
|
|
|
|
|
from swarm_models import OpenAIChat
|
|
|
|
|
|
|
|
|
|
from swarms.prompts.finance_agent_sys_prompt import (
|
|
|
|
|
FINANCIAL_AGENT_SYS_PROMPT,
|
|
|
|
|
)
|
|
|
|
|
from swarms.structs.agent import Agent
|
|
|
|
|
from swarms.structs.base_workflow import BaseWorkflow
|
|
|
|
|
from swarms.utils.loguru_logger import initialize_logger
|
|
|
|
@ -24,6 +20,7 @@ from swarms.utils.loguru_logger import initialize_logger
|
|
|
|
|
# Base logger initialization
|
|
|
|
|
logger = initialize_logger("async_workflow")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Pydantic models for structured data
|
|
|
|
|
class AgentOutput(BaseModel):
|
|
|
|
|
agent_id: str
|
|
|
|
@ -36,6 +33,7 @@ class AgentOutput(BaseModel):
|
|
|
|
|
status: str
|
|
|
|
|
error: Optional[str] = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class WorkflowOutput(BaseModel):
|
|
|
|
|
workflow_id: str
|
|
|
|
|
workflow_name: str
|
|
|
|
@ -47,6 +45,7 @@ class WorkflowOutput(BaseModel):
|
|
|
|
|
agent_outputs: List[AgentOutput]
|
|
|
|
|
metadata: Dict[str, Any] = Field(default_factory=dict)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class SpeakerRole(str, Enum):
|
|
|
|
|
COORDINATOR = "coordinator"
|
|
|
|
|
CRITIC = "critic"
|
|
|
|
@ -54,6 +53,7 @@ class SpeakerRole(str, Enum):
|
|
|
|
|
VALIDATOR = "validator"
|
|
|
|
|
DEFAULT = "default"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class SpeakerMessage(BaseModel):
|
|
|
|
|
role: SpeakerRole
|
|
|
|
|
content: Any
|
|
|
|
@ -61,6 +61,7 @@ class SpeakerMessage(BaseModel):
|
|
|
|
|
agent_name: str
|
|
|
|
|
metadata: Dict[str, Any] = Field(default_factory=dict)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class GroupChatConfig(BaseModel):
|
|
|
|
|
max_turns: int = 10
|
|
|
|
|
timeout_per_turn: float = 30.0
|
|
|
|
@ -68,6 +69,7 @@ class GroupChatConfig(BaseModel):
|
|
|
|
|
allow_concurrent: bool = True
|
|
|
|
|
save_history: bool = True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
|
class SharedMemoryItem:
|
|
|
|
|
key: str
|
|
|
|
@ -76,6 +78,7 @@ class SharedMemoryItem:
|
|
|
|
|
author: str
|
|
|
|
|
metadata: Dict[str, Any] = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
|
class SpeakerConfig:
|
|
|
|
|
role: SpeakerRole
|
|
|
|
@ -85,22 +88,30 @@ class SpeakerConfig:
|
|
|
|
|
timeout: float = 30.0
|
|
|
|
|
required: bool = False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class SharedMemory:
|
|
|
|
|
"""Thread-safe shared memory implementation with persistence"""
|
|
|
|
|
|
|
|
|
|
def __init__(self, persistence_path: Optional[str] = None):
|
|
|
|
|
self._memory = {}
|
|
|
|
|
self._lock = threading.Lock()
|
|
|
|
|
self._persistence_path = persistence_path
|
|
|
|
|
self._load_from_disk()
|
|
|
|
|
|
|
|
|
|
def set(self, key: str, value: Any, author: str, metadata: Dict[str, Any] = None) -> None:
|
|
|
|
|
def set(
|
|
|
|
|
self,
|
|
|
|
|
key: str,
|
|
|
|
|
value: Any,
|
|
|
|
|
author: str,
|
|
|
|
|
metadata: Dict[str, Any] = None,
|
|
|
|
|
) -> None:
|
|
|
|
|
with self._lock:
|
|
|
|
|
item = SharedMemoryItem(
|
|
|
|
|
key=key,
|
|
|
|
|
value=value,
|
|
|
|
|
timestamp=datetime.utcnow(),
|
|
|
|
|
author=author,
|
|
|
|
|
metadata=metadata or {}
|
|
|
|
|
metadata=metadata or {},
|
|
|
|
|
)
|
|
|
|
|
self._memory[key] = item
|
|
|
|
|
self._persist_to_disk()
|
|
|
|
@ -110,25 +121,33 @@ class SharedMemory:
|
|
|
|
|
item = self._memory.get(key)
|
|
|
|
|
return item.value if item else None
|
|
|
|
|
|
|
|
|
|
def get_with_metadata(self, key: str) -> Optional[SharedMemoryItem]:
|
|
|
|
|
def get_with_metadata(
|
|
|
|
|
self, key: str
|
|
|
|
|
) -> Optional[SharedMemoryItem]:
|
|
|
|
|
with self._lock:
|
|
|
|
|
return self._memory.get(key)
|
|
|
|
|
|
|
|
|
|
def _persist_to_disk(self) -> None:
|
|
|
|
|
if self._persistence_path:
|
|
|
|
|
with open(self._persistence_path, 'w') as f:
|
|
|
|
|
json.dump({k: asdict(v) for k, v in self._memory.items()}, f)
|
|
|
|
|
with open(self._persistence_path, "w") as f:
|
|
|
|
|
json.dump(
|
|
|
|
|
{k: asdict(v) for k, v in self._memory.items()}, f
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def _load_from_disk(self) -> None:
|
|
|
|
|
if self._persistence_path and os.path.exists(self._persistence_path):
|
|
|
|
|
with open(self._persistence_path, 'r') as f:
|
|
|
|
|
if self._persistence_path and os.path.exists(
|
|
|
|
|
self._persistence_path
|
|
|
|
|
):
|
|
|
|
|
with open(self._persistence_path, "r") as f:
|
|
|
|
|
data = json.load(f)
|
|
|
|
|
self._memory = {
|
|
|
|
|
k: SharedMemoryItem(**v) for k, v in data.items()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class SpeakerSystem:
|
|
|
|
|
"""Manages speaker interactions and group chat functionality"""
|
|
|
|
|
|
|
|
|
|
def __init__(self, default_timeout: float = 30.0):
|
|
|
|
|
self.speakers: Dict[SpeakerRole, SpeakerConfig] = {}
|
|
|
|
|
self.message_history: List[SpeakerMessage] = []
|
|
|
|
@ -147,12 +166,11 @@ class SpeakerSystem:
|
|
|
|
|
self,
|
|
|
|
|
config: SpeakerConfig,
|
|
|
|
|
input_data: Any,
|
|
|
|
|
context: Dict[str, Any] = None
|
|
|
|
|
context: Dict[str, Any] = None,
|
|
|
|
|
) -> SpeakerMessage:
|
|
|
|
|
try:
|
|
|
|
|
result = await asyncio.wait_for(
|
|
|
|
|
config.agent.arun(input_data),
|
|
|
|
|
timeout=config.timeout
|
|
|
|
|
config.agent.arun(input_data), timeout=config.timeout
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
return SpeakerMessage(
|
|
|
|
@ -160,7 +178,7 @@ class SpeakerSystem:
|
|
|
|
|
content=result,
|
|
|
|
|
timestamp=datetime.utcnow(),
|
|
|
|
|
agent_name=config.agent.agent_name,
|
|
|
|
|
metadata={"context": context or {}}
|
|
|
|
|
metadata={"context": context or {}},
|
|
|
|
|
)
|
|
|
|
|
except asyncio.TimeoutError:
|
|
|
|
|
return SpeakerMessage(
|
|
|
|
@ -168,7 +186,7 @@ class SpeakerSystem:
|
|
|
|
|
content=None,
|
|
|
|
|
timestamp=datetime.utcnow(),
|
|
|
|
|
agent_name=config.agent.agent_name,
|
|
|
|
|
metadata={"error": "Timeout"}
|
|
|
|
|
metadata={"error": "Timeout"},
|
|
|
|
|
)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
return SpeakerMessage(
|
|
|
|
@ -176,9 +194,10 @@ class SpeakerSystem:
|
|
|
|
|
content=None,
|
|
|
|
|
timestamp=datetime.utcnow(),
|
|
|
|
|
agent_name=config.agent.agent_name,
|
|
|
|
|
metadata={"error": str(e)}
|
|
|
|
|
metadata={"error": str(e)},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class AsyncWorkflow(BaseWorkflow):
|
|
|
|
|
"""Enhanced asynchronous workflow with advanced speaker system"""
|
|
|
|
|
|
|
|
|
@ -209,20 +228,26 @@ class AsyncWorkflow(BaseWorkflow):
|
|
|
|
|
self.shared_memory = SharedMemory(shared_memory_path)
|
|
|
|
|
self.speaker_system = SpeakerSystem()
|
|
|
|
|
self.enable_group_chat = enable_group_chat
|
|
|
|
|
self.group_chat_config = group_chat_config or GroupChatConfig()
|
|
|
|
|
self.group_chat_config = (
|
|
|
|
|
group_chat_config or GroupChatConfig()
|
|
|
|
|
)
|
|
|
|
|
self._setup_logging(log_path)
|
|
|
|
|
self.metadata = {}
|
|
|
|
|
|
|
|
|
|
def _setup_logging(self, log_path: str) -> None:
|
|
|
|
|
"""Configure rotating file logger"""
|
|
|
|
|
self.logger = logging.getLogger(f"workflow_{self.workflow_id}")
|
|
|
|
|
self.logger.setLevel(logging.DEBUG if self.verbose else logging.INFO)
|
|
|
|
|
self.logger = logging.getLogger(
|
|
|
|
|
f"workflow_{self.workflow_id}"
|
|
|
|
|
)
|
|
|
|
|
self.logger.setLevel(
|
|
|
|
|
logging.DEBUG if self.verbose else logging.INFO
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
handler = RotatingFileHandler(
|
|
|
|
|
log_path, maxBytes=10*1024*1024, backupCount=5
|
|
|
|
|
log_path, maxBytes=10 * 1024 * 1024, backupCount=5
|
|
|
|
|
)
|
|
|
|
|
formatter = logging.Formatter(
|
|
|
|
|
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
|
|
|
|
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
|
|
|
|
)
|
|
|
|
|
handler.setFormatter(formatter)
|
|
|
|
|
self.logger.addHandler(handler)
|
|
|
|
@ -235,35 +260,35 @@ class AsyncWorkflow(BaseWorkflow):
|
|
|
|
|
agent=agent,
|
|
|
|
|
concurrent=True,
|
|
|
|
|
timeout=30.0,
|
|
|
|
|
required=False
|
|
|
|
|
required=False,
|
|
|
|
|
)
|
|
|
|
|
self.speaker_system.add_speaker(config)
|
|
|
|
|
|
|
|
|
|
async def run_concurrent_speakers(
|
|
|
|
|
self,
|
|
|
|
|
task: str,
|
|
|
|
|
context: Dict[str, Any] = None
|
|
|
|
|
self, task: str, context: Dict[str, Any] = None
|
|
|
|
|
) -> List[SpeakerMessage]:
|
|
|
|
|
"""Run all concurrent speakers in parallel"""
|
|
|
|
|
concurrent_tasks = [
|
|
|
|
|
self.speaker_system._execute_speaker(config, task, context)
|
|
|
|
|
self.speaker_system._execute_speaker(
|
|
|
|
|
config, task, context
|
|
|
|
|
)
|
|
|
|
|
for config in self.speaker_system.speakers.values()
|
|
|
|
|
if config.concurrent
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
results = await asyncio.gather(*concurrent_tasks, return_exceptions=True)
|
|
|
|
|
results = await asyncio.gather(
|
|
|
|
|
*concurrent_tasks, return_exceptions=True
|
|
|
|
|
)
|
|
|
|
|
return [r for r in results if isinstance(r, SpeakerMessage)]
|
|
|
|
|
|
|
|
|
|
async def run_sequential_speakers(
|
|
|
|
|
self,
|
|
|
|
|
task: str,
|
|
|
|
|
context: Dict[str, Any] = None
|
|
|
|
|
self, task: str, context: Dict[str, Any] = None
|
|
|
|
|
) -> List[SpeakerMessage]:
|
|
|
|
|
"""Run non-concurrent speakers in sequence"""
|
|
|
|
|
results = []
|
|
|
|
|
for config in sorted(
|
|
|
|
|
self.speaker_system.speakers.values(),
|
|
|
|
|
key=lambda x: x.priority
|
|
|
|
|
key=lambda x: x.priority,
|
|
|
|
|
):
|
|
|
|
|
if not config.concurrent:
|
|
|
|
|
result = await self.speaker_system._execute_speaker(
|
|
|
|
@ -273,13 +298,13 @@ class AsyncWorkflow(BaseWorkflow):
|
|
|
|
|
return results
|
|
|
|
|
|
|
|
|
|
async def run_group_chat(
|
|
|
|
|
self,
|
|
|
|
|
initial_message: str,
|
|
|
|
|
context: Dict[str, Any] = None
|
|
|
|
|
self, initial_message: str, context: Dict[str, Any] = None
|
|
|
|
|
) -> List[SpeakerMessage]:
|
|
|
|
|
"""Run a group chat discussion among speakers"""
|
|
|
|
|
if not self.enable_group_chat:
|
|
|
|
|
raise ValueError("Group chat is not enabled for this workflow")
|
|
|
|
|
raise ValueError(
|
|
|
|
|
"Group chat is not enabled for this workflow"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
messages: List[SpeakerMessage] = []
|
|
|
|
|
current_turn = 0
|
|
|
|
@ -288,18 +313,26 @@ class AsyncWorkflow(BaseWorkflow):
|
|
|
|
|
turn_context = {
|
|
|
|
|
"turn": current_turn,
|
|
|
|
|
"history": messages,
|
|
|
|
|
**(context or {})
|
|
|
|
|
**(context or {}),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if self.group_chat_config.allow_concurrent:
|
|
|
|
|
turn_messages = await self.run_concurrent_speakers(
|
|
|
|
|
initial_message if current_turn == 0 else messages[-1].content,
|
|
|
|
|
turn_context
|
|
|
|
|
(
|
|
|
|
|
initial_message
|
|
|
|
|
if current_turn == 0
|
|
|
|
|
else messages[-1].content
|
|
|
|
|
),
|
|
|
|
|
turn_context,
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
turn_messages = await self.run_sequential_speakers(
|
|
|
|
|
initial_message if current_turn == 0 else messages[-1].content,
|
|
|
|
|
turn_context
|
|
|
|
|
(
|
|
|
|
|
initial_message
|
|
|
|
|
if current_turn == 0
|
|
|
|
|
else messages[-1].content
|
|
|
|
|
),
|
|
|
|
|
turn_context,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
messages.extend(turn_messages)
|
|
|
|
@ -315,7 +348,9 @@ class AsyncWorkflow(BaseWorkflow):
|
|
|
|
|
|
|
|
|
|
return messages
|
|
|
|
|
|
|
|
|
|
def _should_end_group_chat(self, messages: List[SpeakerMessage]) -> bool:
|
|
|
|
|
def _should_end_group_chat(
|
|
|
|
|
self, messages: List[SpeakerMessage]
|
|
|
|
|
) -> bool:
|
|
|
|
|
"""Determine if group chat should end based on messages"""
|
|
|
|
|
if not messages:
|
|
|
|
|
return True
|
|
|
|
@ -324,7 +359,8 @@ class AsyncWorkflow(BaseWorkflow):
|
|
|
|
|
if self.group_chat_config.require_all_speakers:
|
|
|
|
|
participating_roles = {msg.role for msg in messages}
|
|
|
|
|
required_roles = {
|
|
|
|
|
role for role, config in self.speaker_system.speakers.items()
|
|
|
|
|
role
|
|
|
|
|
for role, config in self.speaker_system.speakers.items()
|
|
|
|
|
if config.required
|
|
|
|
|
}
|
|
|
|
|
if not required_roles.issubset(participating_roles):
|
|
|
|
@ -344,9 +380,7 @@ class AsyncWorkflow(BaseWorkflow):
|
|
|
|
|
await self._save_results(start_time, end_time)
|
|
|
|
|
|
|
|
|
|
async def _execute_agent_task(
|
|
|
|
|
self,
|
|
|
|
|
agent: Agent,
|
|
|
|
|
task: str
|
|
|
|
|
self, agent: Agent, task: str
|
|
|
|
|
) -> AgentOutput:
|
|
|
|
|
"""Execute a single agent task with enhanced error handling and monitoring"""
|
|
|
|
|
start_time = datetime.utcnow()
|
|
|
|
@ -372,14 +406,14 @@ class AsyncWorkflow(BaseWorkflow):
|
|
|
|
|
output=result,
|
|
|
|
|
start_time=start_time,
|
|
|
|
|
end_time=end_time,
|
|
|
|
|
status="success"
|
|
|
|
|
status="success",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
end_time = datetime.utcnow()
|
|
|
|
|
self.logger.error(
|
|
|
|
|
f"Error in agent {agent.agent_name} task {task_id}: {str(e)}",
|
|
|
|
|
exc_info=True
|
|
|
|
|
exc_info=True,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
return AgentOutput(
|
|
|
|
@ -391,7 +425,7 @@ class AsyncWorkflow(BaseWorkflow):
|
|
|
|
|
start_time=start_time,
|
|
|
|
|
end_time=end_time,
|
|
|
|
|
status="error",
|
|
|
|
|
error=str(e)
|
|
|
|
|
error=str(e),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
async def run(self, task: str) -> WorkflowOutput:
|
|
|
|
@ -408,15 +442,21 @@ class AsyncWorkflow(BaseWorkflow):
|
|
|
|
|
if self.enable_group_chat:
|
|
|
|
|
speaker_outputs = await self.run_group_chat(task)
|
|
|
|
|
else:
|
|
|
|
|
concurrent_outputs = await self.run_concurrent_speakers(task)
|
|
|
|
|
sequential_outputs = await self.run_sequential_speakers(task)
|
|
|
|
|
speaker_outputs = concurrent_outputs + sequential_outputs
|
|
|
|
|
concurrent_outputs = (
|
|
|
|
|
await self.run_concurrent_speakers(task)
|
|
|
|
|
)
|
|
|
|
|
sequential_outputs = (
|
|
|
|
|
await self.run_sequential_speakers(task)
|
|
|
|
|
)
|
|
|
|
|
speaker_outputs = (
|
|
|
|
|
concurrent_outputs + sequential_outputs
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Store speaker outputs in shared memory
|
|
|
|
|
self.shared_memory.set(
|
|
|
|
|
"speaker_outputs",
|
|
|
|
|
[msg.dict() for msg in speaker_outputs],
|
|
|
|
|
"workflow"
|
|
|
|
|
"workflow",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Create tasks for all agents
|
|
|
|
@ -426,13 +466,19 @@ class AsyncWorkflow(BaseWorkflow):
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
# Execute all tasks concurrently
|
|
|
|
|
agent_outputs = await asyncio.gather(*tasks, return_exceptions=True)
|
|
|
|
|
agent_outputs = await asyncio.gather(
|
|
|
|
|
*tasks, return_exceptions=True
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
end_time = datetime.utcnow()
|
|
|
|
|
|
|
|
|
|
# Calculate success/failure counts
|
|
|
|
|
successful_tasks = sum(1 for output in agent_outputs
|
|
|
|
|
if isinstance(output, AgentOutput) and output.status == "success")
|
|
|
|
|
successful_tasks = sum(
|
|
|
|
|
1
|
|
|
|
|
for output in agent_outputs
|
|
|
|
|
if isinstance(output, AgentOutput)
|
|
|
|
|
and output.status == "success"
|
|
|
|
|
)
|
|
|
|
|
failed_tasks = len(agent_outputs) - successful_tasks
|
|
|
|
|
|
|
|
|
|
return WorkflowOutput(
|
|
|
|
@ -443,22 +489,36 @@ class AsyncWorkflow(BaseWorkflow):
|
|
|
|
|
total_agents=len(self.agents),
|
|
|
|
|
successful_tasks=successful_tasks,
|
|
|
|
|
failed_tasks=failed_tasks,
|
|
|
|
|
agent_outputs=[output for output in agent_outputs
|
|
|
|
|
if isinstance(output, AgentOutput)],
|
|
|
|
|
agent_outputs=[
|
|
|
|
|
output
|
|
|
|
|
for output in agent_outputs
|
|
|
|
|
if isinstance(output, AgentOutput)
|
|
|
|
|
],
|
|
|
|
|
metadata={
|
|
|
|
|
"max_workers": self.max_workers,
|
|
|
|
|
"shared_memory_keys": list(self.shared_memory._memory.keys()),
|
|
|
|
|
"shared_memory_keys": list(
|
|
|
|
|
self.shared_memory._memory.keys()
|
|
|
|
|
),
|
|
|
|
|
"group_chat_enabled": self.enable_group_chat,
|
|
|
|
|
"total_speaker_messages": len(speaker_outputs),
|
|
|
|
|
"speaker_outputs": [msg.dict() for msg in speaker_outputs]
|
|
|
|
|
}
|
|
|
|
|
"total_speaker_messages": len(
|
|
|
|
|
speaker_outputs
|
|
|
|
|
),
|
|
|
|
|
"speaker_outputs": [
|
|
|
|
|
msg.dict() for msg in speaker_outputs
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
self.logger.error(f"Critical workflow error: {str(e)}", exc_info=True)
|
|
|
|
|
self.logger.error(
|
|
|
|
|
f"Critical workflow error: {str(e)}",
|
|
|
|
|
exc_info=True,
|
|
|
|
|
)
|
|
|
|
|
raise
|
|
|
|
|
|
|
|
|
|
async def _save_results(self, start_time: datetime, end_time: datetime) -> None:
|
|
|
|
|
async def _save_results(
|
|
|
|
|
self, start_time: datetime, end_time: datetime
|
|
|
|
|
) -> None:
|
|
|
|
|
"""Save workflow results to disk"""
|
|
|
|
|
if not self.autosave:
|
|
|
|
|
return
|
|
|
|
@ -469,38 +529,59 @@ class AsyncWorkflow(BaseWorkflow):
|
|
|
|
|
filename = f"{output_dir}/workflow_{self.workflow_id}_{end_time.strftime('%Y%m%d_%H%M%S')}.json"
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
with open(filename, 'w') as f:
|
|
|
|
|
json.dump({
|
|
|
|
|
with open(filename, "w") as f:
|
|
|
|
|
json.dump(
|
|
|
|
|
{
|
|
|
|
|
"workflow_id": self.workflow_id,
|
|
|
|
|
"start_time": start_time.isoformat(),
|
|
|
|
|
"end_time": end_time.isoformat(),
|
|
|
|
|
"results": [
|
|
|
|
|
asdict(result) if hasattr(result, '__dict__')
|
|
|
|
|
else result.dict() if hasattr(result, 'dict')
|
|
|
|
|
(
|
|
|
|
|
asdict(result)
|
|
|
|
|
if hasattr(result, "__dict__")
|
|
|
|
|
else (
|
|
|
|
|
result.dict()
|
|
|
|
|
if hasattr(result, "dict")
|
|
|
|
|
else str(result)
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
for result in self.results
|
|
|
|
|
],
|
|
|
|
|
"speaker_history": [
|
|
|
|
|
msg.dict() for msg in self.speaker_system.message_history
|
|
|
|
|
msg.dict()
|
|
|
|
|
for msg in self.speaker_system.message_history
|
|
|
|
|
],
|
|
|
|
|
"metadata": self.metadata
|
|
|
|
|
}, f, default=str, indent=2)
|
|
|
|
|
"metadata": self.metadata,
|
|
|
|
|
},
|
|
|
|
|
f,
|
|
|
|
|
default=str,
|
|
|
|
|
indent=2,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
self.logger.info(f"Workflow results saved to {filename}")
|
|
|
|
|
except Exception as e:
|
|
|
|
|
self.logger.error(f"Error saving workflow results: {str(e)}")
|
|
|
|
|
self.logger.error(
|
|
|
|
|
f"Error saving workflow results: {str(e)}"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def _validate_config(self) -> None:
|
|
|
|
|
"""Validate workflow configuration"""
|
|
|
|
|
if self.max_workers < 1:
|
|
|
|
|
raise ValueError("max_workers must be at least 1")
|
|
|
|
|
|
|
|
|
|
if self.enable_group_chat and not self.speaker_system.speakers:
|
|
|
|
|
raise ValueError("Group chat enabled but no speakers configured")
|
|
|
|
|
if (
|
|
|
|
|
self.enable_group_chat
|
|
|
|
|
and not self.speaker_system.speakers
|
|
|
|
|
):
|
|
|
|
|
raise ValueError(
|
|
|
|
|
"Group chat enabled but no speakers configured"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
for config in self.speaker_system.speakers.values():
|
|
|
|
|
if config.timeout <= 0:
|
|
|
|
|
raise ValueError(f"Invalid timeout for speaker {config.role}")
|
|
|
|
|
raise ValueError(
|
|
|
|
|
f"Invalid timeout for speaker {config.role}"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
async def cleanup(self) -> None:
|
|
|
|
|
"""Cleanup workflow resources"""
|
|
|
|
@ -513,7 +594,14 @@ class AsyncWorkflow(BaseWorkflow):
|
|
|
|
|
# Persist final state
|
|
|
|
|
if self.autosave:
|
|
|
|
|
end_time = datetime.utcnow()
|
|
|
|
|
await self._save_results(self.results[0].start_time if self.results else end_time, end_time)
|
|
|
|
|
await self._save_results(
|
|
|
|
|
(
|
|
|
|
|
self.results[0].start_time
|
|
|
|
|
if self.results
|
|
|
|
|
else end_time
|
|
|
|
|
),
|
|
|
|
|
end_time,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Clear shared memory if configured
|
|
|
|
|
self.shared_memory._memory.clear()
|
|
|
|
@ -522,11 +610,12 @@ class AsyncWorkflow(BaseWorkflow):
|
|
|
|
|
self.logger.error(f"Error during cleanup: {str(e)}")
|
|
|
|
|
raise
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Utility functions for the workflow
|
|
|
|
|
def create_default_workflow(
|
|
|
|
|
agents: List[Agent],
|
|
|
|
|
name: str = "DefaultWorkflow",
|
|
|
|
|
enable_group_chat: bool = False
|
|
|
|
|
enable_group_chat: bool = False,
|
|
|
|
|
) -> AsyncWorkflow:
|
|
|
|
|
"""Create a workflow with default configuration"""
|
|
|
|
|
workflow = AsyncWorkflow(
|
|
|
|
@ -540,18 +629,19 @@ def create_default_workflow(
|
|
|
|
|
group_chat_config=GroupChatConfig(
|
|
|
|
|
max_turns=5,
|
|
|
|
|
allow_concurrent=True,
|
|
|
|
|
require_all_speakers=False
|
|
|
|
|
)
|
|
|
|
|
require_all_speakers=False,
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
workflow.add_default_speakers()
|
|
|
|
|
return workflow
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def run_workflow_with_retry(
|
|
|
|
|
workflow: AsyncWorkflow,
|
|
|
|
|
task: str,
|
|
|
|
|
max_retries: int = 3,
|
|
|
|
|
retry_delay: float = 1.0
|
|
|
|
|
retry_delay: float = 1.0,
|
|
|
|
|
) -> WorkflowOutput:
|
|
|
|
|
"""Run workflow with retry logic"""
|
|
|
|
|
for attempt in range(max_retries):
|
|
|
|
|