parent
2d5232f19b
commit
4953b39948
@ -1,88 +0,0 @@
|
|||||||
from swarms.structs.agent import Agent
|
|
||||||
from swarms.structs.sequential_workflow import SequentialWorkflow
|
|
||||||
|
|
||||||
def run_workflow_with_streaming_callback(task, streaming_callback):
|
|
||||||
"""
|
|
||||||
Run a sequential workflow with two agents and a streaming callback.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
task (str): The task to process through the workflow.
|
|
||||||
streaming_callback (callable): Function to handle streaming output.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The final result from the workflow.
|
|
||||||
"""
|
|
||||||
|
|
||||||
agent1 = Agent(
|
|
||||||
name="Research Agent",
|
|
||||||
description="A research agent that can answer questions",
|
|
||||||
model_name="gpt-4o",
|
|
||||||
system_prompt=(
|
|
||||||
"You are a ResearchAgent. Your task is to research and gather "
|
|
||||||
"information about the given topic. Provide comprehensive research "
|
|
||||||
"findings and key insights."
|
|
||||||
),
|
|
||||||
max_loops=1,
|
|
||||||
interactive=True,
|
|
||||||
verbose=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
agent2 = Agent(
|
|
||||||
name="Analysis Agent",
|
|
||||||
description="An analysis agent that draws conclusions from research",
|
|
||||||
model_name="gpt-4o-mini",
|
|
||||||
system_prompt=(
|
|
||||||
"You are an AnalysisAgent. Your task is to analyze the research "
|
|
||||||
"provided by the previous agent and draw meaningful conclusions. "
|
|
||||||
"Provide detailed analysis and actionable insights."
|
|
||||||
),
|
|
||||||
max_loops=1,
|
|
||||||
interactive=True,
|
|
||||||
verbose=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
workflow = SequentialWorkflow(
|
|
||||||
id="research_analysis_workflow",
|
|
||||||
name="Research Analysis Workflow",
|
|
||||||
description="A sequential workflow that researches and analyzes topics",
|
|
||||||
agents=[agent1, agent2],
|
|
||||||
max_loops=1,
|
|
||||||
output_type="str",
|
|
||||||
streaming_callback=streaming_callback,
|
|
||||||
multi_agent_collab_prompt=True,
|
|
||||||
)
|
|
||||||
return workflow.run(task)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
|
|
||||||
# ## REGULAR STREAMING CALLBACK
|
|
||||||
# def streaming_callback(token):
|
|
||||||
# print(token, end="", flush=True)
|
|
||||||
|
|
||||||
# run_workflow_with_streaming_callback(
|
|
||||||
# task="What are the latest advancements in AI?",
|
|
||||||
# streaming_callback=streaming_callback,
|
|
||||||
# )
|
|
||||||
|
|
||||||
|
|
||||||
# ## CUSTOM BUFFERING STREAMING_CALLBACK BASED ON DEV PREFERED
|
|
||||||
# buffer = []
|
|
||||||
# def streaming_callback(token):
|
|
||||||
# buffer.append(token)
|
|
||||||
# # Print in bigger chunks (e.g., every 20 tokens or on final flush)
|
|
||||||
# if len(buffer) >= 20 or token.endswith("\n"):
|
|
||||||
# print("".join(buffer), end="", flush=True)
|
|
||||||
# buffer.clear()
|
|
||||||
# # Optionally, you could add a flush at the end of the run if needed
|
|
||||||
|
|
||||||
# run_workflow_with_streaming_callback(
|
|
||||||
# task="What are the latest advancements in AI?",
|
|
||||||
# streaming_callback=streaming_callback,
|
|
||||||
# )
|
|
||||||
|
|
||||||
|
|
||||||
## NO ADDED STREAMING_CALLBACK
|
|
||||||
run_workflow_with_streaming_callback(
|
|
||||||
task="What are the latest advancements in AI?",
|
|
||||||
streaming_callback=None,
|
|
||||||
)
|
|
@ -1,723 +0,0 @@
|
|||||||
import json
|
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
|
||||||
from typing import Any, Callable, Dict, List, Optional, Union
|
|
||||||
|
|
||||||
from swarms.structs.agent import Agent
|
|
||||||
from swarms.structs.conversation import Conversation
|
|
||||||
from swarms.telemetry.main import log_agent_data
|
|
||||||
from swarms.utils.any_to_str import any_to_str
|
|
||||||
from swarms.utils.history_output_formatter import (
|
|
||||||
history_output_formatter,
|
|
||||||
)
|
|
||||||
from swarms.utils.loguru_logger import initialize_logger
|
|
||||||
from swarms.utils.output_types import OutputType
|
|
||||||
from swarms.structs.swarm_id import swarm_id
|
|
||||||
|
|
||||||
logger = initialize_logger(log_folder="rearrange")
|
|
||||||
|
|
||||||
|
|
||||||
class AgentRearrange:
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
id: str = swarm_id(),
|
|
||||||
name: str = "AgentRearrange",
|
|
||||||
description: str = "A swarm of agents for rearranging tasks.",
|
|
||||||
agents: List[Union[Agent, Callable]] = None,
|
|
||||||
flow: str = None,
|
|
||||||
max_loops: int = 1,
|
|
||||||
verbose: bool = True,
|
|
||||||
memory_system: Any = None,
|
|
||||||
human_in_the_loop: bool = False,
|
|
||||||
custom_human_in_the_loop: Optional[
|
|
||||||
Callable[[str], str]
|
|
||||||
] = None,
|
|
||||||
output_type: OutputType = "all",
|
|
||||||
autosave: bool = True,
|
|
||||||
rules: str = None,
|
|
||||||
team_awareness: bool = False,
|
|
||||||
time_enabled: bool = False,
|
|
||||||
message_id_on: bool = False,
|
|
||||||
streaming_callback: Optional[Callable[[str], None]] = None,
|
|
||||||
*args,
|
|
||||||
**kwargs,
|
|
||||||
):
|
|
||||||
self.name = name
|
|
||||||
self.description = description
|
|
||||||
self.id = id
|
|
||||||
self.agents = {agent.agent_name: agent for agent in agents}
|
|
||||||
self.flow = flow if flow is not None else ""
|
|
||||||
self.verbose = verbose
|
|
||||||
self.max_loops = max_loops if max_loops > 0 else 1
|
|
||||||
self.memory_system = memory_system
|
|
||||||
self.human_in_the_loop = human_in_the_loop
|
|
||||||
self.custom_human_in_the_loop = custom_human_in_the_loop
|
|
||||||
self.output_type = output_type
|
|
||||||
self.autosave = autosave
|
|
||||||
self.time_enabled = time_enabled
|
|
||||||
self.message_id_on = message_id_on
|
|
||||||
self.streaming_callback = streaming_callback
|
|
||||||
|
|
||||||
self.conversation = Conversation(
|
|
||||||
name=f"{self.name}-Conversation",
|
|
||||||
time_enabled=self.time_enabled,
|
|
||||||
token_count=False,
|
|
||||||
message_id_on=self.message_id_on,
|
|
||||||
)
|
|
||||||
|
|
||||||
if rules:
|
|
||||||
self.conversation.add("user", rules)
|
|
||||||
|
|
||||||
if team_awareness is True:
|
|
||||||
# agents_info = get_agents_info(agents=self.agents, team_name=self.name)
|
|
||||||
|
|
||||||
# Add sequential flow information if available
|
|
||||||
sequential_info = self._get_sequential_flow_info()
|
|
||||||
if sequential_info:
|
|
||||||
# agents_info += "\n\n" + sequential_info
|
|
||||||
self.conversation.add("system", sequential_info)
|
|
||||||
|
|
||||||
# self.conversation.add("system", agents_info)
|
|
||||||
|
|
||||||
self.reliability_check()
|
|
||||||
|
|
||||||
def reliability_check(self):
|
|
||||||
if self.agents is None or len(self.agents) == 0:
|
|
||||||
raise ValueError("Agents list cannot be None or empty")
|
|
||||||
|
|
||||||
if self.max_loops == 0:
|
|
||||||
raise ValueError("max_loops cannot be 0")
|
|
||||||
|
|
||||||
if self.flow is None or self.flow == "":
|
|
||||||
raise ValueError("flow cannot be None or empty")
|
|
||||||
|
|
||||||
if self.output_type is None or self.output_type == "":
|
|
||||||
raise ValueError("output_type cannot be None or empty")
|
|
||||||
|
|
||||||
def set_custom_flow(self, flow: str):
|
|
||||||
self.flow = flow
|
|
||||||
logger.info(f"Custom flow set: {flow}")
|
|
||||||
|
|
||||||
def add_agent(self, agent: Agent):
|
|
||||||
"""
|
|
||||||
Adds an agent to the swarm.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
agent (Agent): The agent to be added.
|
|
||||||
"""
|
|
||||||
logger.info(f"Adding agent {agent.agent_name} to the swarm.")
|
|
||||||
self.agents[agent.agent_name] = agent
|
|
||||||
|
|
||||||
def track_history(
|
|
||||||
self,
|
|
||||||
agent_name: str,
|
|
||||||
result: str,
|
|
||||||
):
|
|
||||||
self.swarm_history[agent_name].append(result)
|
|
||||||
|
|
||||||
def remove_agent(self, agent_name: str):
|
|
||||||
"""
|
|
||||||
Removes an agent from the swarm.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
agent_name (str): The name of the agent to be removed.
|
|
||||||
"""
|
|
||||||
del self.agents[agent_name]
|
|
||||||
|
|
||||||
def add_agents(self, agents: List[Agent]):
|
|
||||||
"""
|
|
||||||
Adds multiple agents to the swarm.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
agents (List[Agent]): A list of Agent objects.
|
|
||||||
"""
|
|
||||||
for agent in agents:
|
|
||||||
self.agents[agent.agent_name] = agent
|
|
||||||
|
|
||||||
def validate_flow(self):
|
|
||||||
"""
|
|
||||||
Validates the flow pattern.
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
ValueError: If the flow pattern is incorrectly formatted or contains duplicate agent names.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
bool: True if the flow pattern is valid.
|
|
||||||
"""
|
|
||||||
if "->" not in self.flow:
|
|
||||||
raise ValueError(
|
|
||||||
"Flow must include '->' to denote the direction of the task."
|
|
||||||
)
|
|
||||||
|
|
||||||
agents_in_flow = []
|
|
||||||
|
|
||||||
# Arrow
|
|
||||||
tasks = self.flow.split("->")
|
|
||||||
|
|
||||||
# For the task in tasks
|
|
||||||
for task in tasks:
|
|
||||||
agent_names = [name.strip() for name in task.split(",")]
|
|
||||||
|
|
||||||
# Loop over the agent names
|
|
||||||
for agent_name in agent_names:
|
|
||||||
if (
|
|
||||||
agent_name not in self.agents
|
|
||||||
and agent_name != "H"
|
|
||||||
):
|
|
||||||
raise ValueError(
|
|
||||||
f"Agent '{agent_name}' is not registered."
|
|
||||||
)
|
|
||||||
agents_in_flow.append(agent_name)
|
|
||||||
|
|
||||||
# # If the length of the agents does not equal the length of the agents in flow
|
|
||||||
# if len(set(agents_in_flow)) != len(agents_in_flow):
|
|
||||||
# raise ValueError(
|
|
||||||
# "Duplicate agent names in the flow are not allowed."
|
|
||||||
# )
|
|
||||||
|
|
||||||
logger.info(f"Flow: {self.flow} is valid.")
|
|
||||||
return True
|
|
||||||
|
|
||||||
def _get_sequential_awareness(
|
|
||||||
self, agent_name: str, tasks: List[str]
|
|
||||||
) -> str:
|
|
||||||
"""
|
|
||||||
Determines the sequential awareness information for an agent in a sequential flow.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
agent_name (str): The name of the current agent.
|
|
||||||
tasks (List[str]): The list of tasks in the flow.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
str: A string describing the agents ahead and behind in the sequence.
|
|
||||||
"""
|
|
||||||
# Find the position of the current agent in the flow
|
|
||||||
agent_position = None
|
|
||||||
for i, task in enumerate(tasks):
|
|
||||||
agent_names = [name.strip() for name in task.split(",")]
|
|
||||||
if agent_name in agent_names:
|
|
||||||
agent_position = i
|
|
||||||
break
|
|
||||||
|
|
||||||
if agent_position is None:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
awareness_info = []
|
|
||||||
|
|
||||||
# Check if there's an agent before (ahead in the sequence)
|
|
||||||
if agent_position > 0:
|
|
||||||
prev_task = tasks[agent_position - 1]
|
|
||||||
prev_agents = [
|
|
||||||
name.strip() for name in prev_task.split(",")
|
|
||||||
]
|
|
||||||
if (
|
|
||||||
prev_agents and prev_agents[0] != "H"
|
|
||||||
): # Skip human agents
|
|
||||||
awareness_info.append(
|
|
||||||
f"Agent ahead: {', '.join(prev_agents)}"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Check if there's an agent after (behind in the sequence)
|
|
||||||
if agent_position < len(tasks) - 1:
|
|
||||||
next_task = tasks[agent_position + 1]
|
|
||||||
next_agents = [
|
|
||||||
name.strip() for name in next_task.split(",")
|
|
||||||
]
|
|
||||||
if (
|
|
||||||
next_agents and next_agents[0] != "H"
|
|
||||||
): # Skip human agents
|
|
||||||
awareness_info.append(
|
|
||||||
f"Agent behind: {', '.join(next_agents)}"
|
|
||||||
)
|
|
||||||
|
|
||||||
if awareness_info:
|
|
||||||
return (
|
|
||||||
f"Sequential awareness: {' | '.join(awareness_info)}"
|
|
||||||
)
|
|
||||||
return ""
|
|
||||||
|
|
||||||
def _get_sequential_flow_info(self) -> str:
|
|
||||||
"""
|
|
||||||
Gets information about the overall sequential flow structure.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
str: A string describing the sequential flow structure.
|
|
||||||
"""
|
|
||||||
if not self.flow or "->" not in self.flow:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
tasks = self.flow.split("->")
|
|
||||||
flow_info = []
|
|
||||||
|
|
||||||
for i, task in enumerate(tasks):
|
|
||||||
agent_names = [name.strip() for name in task.split(",")]
|
|
||||||
if (
|
|
||||||
agent_names and agent_names[0] != "H"
|
|
||||||
): # Skip human agents
|
|
||||||
position_info = (
|
|
||||||
f"Step {i+1}: {', '.join(agent_names)}"
|
|
||||||
)
|
|
||||||
if i > 0:
|
|
||||||
prev_task = tasks[i - 1]
|
|
||||||
prev_agents = [
|
|
||||||
name.strip() for name in prev_task.split(",")
|
|
||||||
]
|
|
||||||
if prev_agents and prev_agents[0] != "H":
|
|
||||||
position_info += (
|
|
||||||
f" (follows: {', '.join(prev_agents)})"
|
|
||||||
)
|
|
||||||
if i < len(tasks) - 1:
|
|
||||||
next_task = tasks[i + 1]
|
|
||||||
next_agents = [
|
|
||||||
name.strip() for name in next_task.split(",")
|
|
||||||
]
|
|
||||||
if next_agents and next_agents[0] != "H":
|
|
||||||
position_info += (
|
|
||||||
f" (leads to: {', '.join(next_agents)})"
|
|
||||||
)
|
|
||||||
flow_info.append(position_info)
|
|
||||||
|
|
||||||
if flow_info:
|
|
||||||
return "Sequential Flow Structure:\n" + "\n".join(
|
|
||||||
flow_info
|
|
||||||
)
|
|
||||||
return ""
|
|
||||||
|
|
||||||
def get_agent_sequential_awareness(self, agent_name: str) -> str:
|
|
||||||
"""
|
|
||||||
Gets the sequential awareness information for a specific agent.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
agent_name (str): The name of the agent to get awareness for.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
str: A string describing the agents ahead and behind in the sequence.
|
|
||||||
"""
|
|
||||||
if not self.flow or "->" not in self.flow:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
tasks = self.flow.split("->")
|
|
||||||
return self._get_sequential_awareness(agent_name, tasks)
|
|
||||||
|
|
||||||
def get_sequential_flow_structure(self) -> str:
|
|
||||||
"""
|
|
||||||
Gets the overall sequential flow structure information.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
str: A string describing the complete sequential flow structure.
|
|
||||||
"""
|
|
||||||
return self._get_sequential_flow_info()
|
|
||||||
|
|
||||||
def _run(
|
|
||||||
self,
|
|
||||||
task: str = None,
|
|
||||||
img: str = None,
|
|
||||||
custom_tasks: Dict[str, str] = None,
|
|
||||||
*args,
|
|
||||||
**kwargs,
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
Runs the swarm to rearrange the tasks.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
task (str, optional): The initial task to be processed. Defaults to None.
|
|
||||||
img (str, optional): Image input for agents that support it. Defaults to None.
|
|
||||||
custom_tasks (Dict[str, str], optional): Custom tasks for specific agents. Defaults to None.
|
|
||||||
output_type (str, optional): Format of the output. Can be:
|
|
||||||
- "all": String containing all agent responses concatenated
|
|
||||||
- "final": Only the final agent's response
|
|
||||||
- "list": List of all agent responses
|
|
||||||
- "dict": Dict mapping agent names to their responses
|
|
||||||
Defaults to "final".
|
|
||||||
*args: Additional positional arguments
|
|
||||||
**kwargs: Additional keyword arguments
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Union[str, List[str], Dict[str, str]]: The processed output in the specified format
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
ValueError: If flow validation fails
|
|
||||||
Exception: For any other errors during execution
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
self.conversation.add("User", task)
|
|
||||||
|
|
||||||
if not self.validate_flow():
|
|
||||||
logger.error("Flow validation failed")
|
|
||||||
return "Invalid flow configuration."
|
|
||||||
|
|
||||||
tasks = self.flow.split("->")
|
|
||||||
current_task = task
|
|
||||||
response_dict = {}
|
|
||||||
|
|
||||||
logger.info(
|
|
||||||
f"Starting task execution with {len(tasks)} steps"
|
|
||||||
)
|
|
||||||
|
|
||||||
# # Handle custom tasks
|
|
||||||
if custom_tasks is not None:
|
|
||||||
logger.info("Processing custom tasks")
|
|
||||||
c_agent_name, c_task = next(
|
|
||||||
iter(custom_tasks.items())
|
|
||||||
)
|
|
||||||
position = tasks.index(c_agent_name)
|
|
||||||
|
|
||||||
if position > 0:
|
|
||||||
tasks[position - 1] += "->" + c_task
|
|
||||||
else:
|
|
||||||
tasks.insert(position, c_task)
|
|
||||||
|
|
||||||
loop_count = 0
|
|
||||||
while loop_count < self.max_loops:
|
|
||||||
logger.info(
|
|
||||||
f"Starting loop {loop_count + 1}/{self.max_loops}"
|
|
||||||
)
|
|
||||||
|
|
||||||
for task_idx, task in enumerate(tasks):
|
|
||||||
agent_names = [
|
|
||||||
name.strip() for name in task.split(",")
|
|
||||||
]
|
|
||||||
|
|
||||||
if len(agent_names) > 1:
|
|
||||||
# Parallel processing
|
|
||||||
logger.info(
|
|
||||||
f"Running agents in parallel: {agent_names}"
|
|
||||||
)
|
|
||||||
|
|
||||||
for agent_name in agent_names:
|
|
||||||
agent = self.agents[agent_name]
|
|
||||||
# Set agent.streaming_on if no streaming_callback
|
|
||||||
if self.streaming_callback is not None:
|
|
||||||
agent.streaming_on = True
|
|
||||||
result = agent.run(
|
|
||||||
task=self.conversation.get_str(),
|
|
||||||
img=img,
|
|
||||||
*args,
|
|
||||||
**kwargs,
|
|
||||||
)
|
|
||||||
result = any_to_str(result)
|
|
||||||
|
|
||||||
|
|
||||||
# Call streaming callback with the result if provided
|
|
||||||
if self.streaming_callback:
|
|
||||||
self.streaming_callback(result)
|
|
||||||
|
|
||||||
self.conversation.add(
|
|
||||||
agent.agent_name, result
|
|
||||||
)
|
|
||||||
|
|
||||||
response_dict[agent_name] = result
|
|
||||||
logger.debug(
|
|
||||||
f"Agent {agent_name} output: {result}"
|
|
||||||
)
|
|
||||||
|
|
||||||
",".join(agent_names)
|
|
||||||
|
|
||||||
else:
|
|
||||||
# Sequential processing
|
|
||||||
logger.info(
|
|
||||||
f"Running agent sequentially: {agent_names[0]}"
|
|
||||||
)
|
|
||||||
agent_name = agent_names[0]
|
|
||||||
|
|
||||||
agent = self.agents[agent_name]
|
|
||||||
|
|
||||||
# Add sequential awareness information for the agent
|
|
||||||
awareness_info = (
|
|
||||||
self._get_sequential_awareness(
|
|
||||||
agent_name, tasks
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if awareness_info:
|
|
||||||
self.conversation.add(
|
|
||||||
"system", awareness_info
|
|
||||||
)
|
|
||||||
logger.info(
|
|
||||||
f"Added sequential awareness for {agent_name}: {awareness_info}"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Set agent.streaming_on if no streaming_callback
|
|
||||||
if self.streaming_callback is not None:
|
|
||||||
agent.streaming_on = True
|
|
||||||
current_task = agent.run(
|
|
||||||
task=self.conversation.get_str(),
|
|
||||||
img=img,
|
|
||||||
*args,
|
|
||||||
**kwargs,
|
|
||||||
)
|
|
||||||
current_task = any_to_str(current_task)
|
|
||||||
|
|
||||||
# Call streaming callback with the result if provided
|
|
||||||
if self.streaming_callback:
|
|
||||||
self.streaming_callback(current_task)
|
|
||||||
|
|
||||||
self.conversation.add(
|
|
||||||
agent.agent_name, current_task
|
|
||||||
)
|
|
||||||
|
|
||||||
response_dict[agent_name] = current_task
|
|
||||||
|
|
||||||
loop_count += 1
|
|
||||||
|
|
||||||
logger.info("Task execution completed")
|
|
||||||
|
|
||||||
return history_output_formatter(
|
|
||||||
conversation=self.conversation,
|
|
||||||
type=self.output_type,
|
|
||||||
)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
self._catch_error(e)
|
|
||||||
|
|
||||||
def _catch_error(self, e: Exception):
|
|
||||||
if self.autosave is True:
|
|
||||||
log_agent_data(self.to_dict())
|
|
||||||
|
|
||||||
logger.error(
|
|
||||||
f"An error occurred with your swarm {self.name}: Error: {e} Traceback: {e.__traceback__}"
|
|
||||||
)
|
|
||||||
|
|
||||||
return e
|
|
||||||
|
|
||||||
def run(
|
|
||||||
self,
|
|
||||||
task: str = None,
|
|
||||||
img: str = None,
|
|
||||||
*args,
|
|
||||||
**kwargs,
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
Execute the agent rearrangement task with specified compute resources.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
task (str, optional): The task to execute. Defaults to None.
|
|
||||||
img (str, optional): Path to input image if required. Defaults to None.
|
|
||||||
*args: Additional positional arguments passed to _run().
|
|
||||||
**kwargs: Additional keyword arguments passed to _run().
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The result from executing the task through the cluster operations wrapper.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
log_agent_data(self.to_dict())
|
|
||||||
|
|
||||||
out = self._run(
|
|
||||||
task=task,
|
|
||||||
img=img,
|
|
||||||
*args,
|
|
||||||
**kwargs,
|
|
||||||
)
|
|
||||||
|
|
||||||
log_agent_data(self.to_dict())
|
|
||||||
|
|
||||||
return out
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
self._catch_error(e)
|
|
||||||
|
|
||||||
def __call__(self, task: str, *args, **kwargs):
|
|
||||||
"""
|
|
||||||
Make the class callable by executing the run() method.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
task (str): The task to execute.
|
|
||||||
*args: Additional positional arguments passed to run().
|
|
||||||
**kwargs: Additional keyword arguments passed to run().
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The result from executing run().
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
return self.run(task=task, *args, **kwargs)
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"An error occurred: {e}")
|
|
||||||
return e
|
|
||||||
|
|
||||||
def batch_run(
|
|
||||||
self,
|
|
||||||
tasks: List[str],
|
|
||||||
img: Optional[List[str]] = None,
|
|
||||||
batch_size: int = 10,
|
|
||||||
*args,
|
|
||||||
**kwargs,
|
|
||||||
) -> List[str]:
|
|
||||||
"""
|
|
||||||
Process multiple tasks in batches.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
tasks: List of tasks to process
|
|
||||||
img: Optional list of images corresponding to tasks
|
|
||||||
batch_size: Number of tasks to process simultaneously
|
|
||||||
device: Computing device to use
|
|
||||||
device_id: Specific device ID if applicable
|
|
||||||
all_cores: Whether to use all CPU cores
|
|
||||||
all_gpus: Whether to use all available GPUs
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
List of results corresponding to input tasks
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
results = []
|
|
||||||
for i in range(0, len(tasks), batch_size):
|
|
||||||
batch_tasks = tasks[i : i + batch_size]
|
|
||||||
batch_imgs = (
|
|
||||||
img[i : i + batch_size]
|
|
||||||
if img
|
|
||||||
else [None] * len(batch_tasks)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Process batch using concurrent execution
|
|
||||||
batch_results = [
|
|
||||||
self.run(
|
|
||||||
task=task,
|
|
||||||
img=img_path,
|
|
||||||
*args,
|
|
||||||
**kwargs,
|
|
||||||
)
|
|
||||||
for task, img_path in zip(batch_tasks, batch_imgs)
|
|
||||||
]
|
|
||||||
results.extend(batch_results)
|
|
||||||
|
|
||||||
return results
|
|
||||||
except Exception as e:
|
|
||||||
self._catch_error(e)
|
|
||||||
|
|
||||||
def concurrent_run(
|
|
||||||
self,
|
|
||||||
tasks: List[str],
|
|
||||||
img: Optional[List[str]] = None,
|
|
||||||
max_workers: Optional[int] = None,
|
|
||||||
*args,
|
|
||||||
**kwargs,
|
|
||||||
) -> List[str]:
|
|
||||||
"""
|
|
||||||
Process multiple tasks concurrently using ThreadPoolExecutor.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
tasks: List of tasks to process
|
|
||||||
img: Optional list of images corresponding to tasks
|
|
||||||
max_workers: Maximum number of worker threads
|
|
||||||
device: Computing device to use
|
|
||||||
device_id: Specific device ID if applicable
|
|
||||||
all_cores: Whether to use all CPU cores
|
|
||||||
all_gpus: Whether to use all available GPUs
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
List of results corresponding to input tasks
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
with ThreadPoolExecutor(
|
|
||||||
max_workers=max_workers
|
|
||||||
) as executor:
|
|
||||||
imgs = img if img else [None] * len(tasks)
|
|
||||||
futures = [
|
|
||||||
executor.submit(
|
|
||||||
self.run,
|
|
||||||
task=task,
|
|
||||||
img=img_path,
|
|
||||||
*args,
|
|
||||||
**kwargs,
|
|
||||||
)
|
|
||||||
for task, img_path in zip(tasks, imgs)
|
|
||||||
]
|
|
||||||
return [future.result() for future in futures]
|
|
||||||
except Exception as e:
|
|
||||||
self._catch_error(e)
|
|
||||||
|
|
||||||
def _serialize_callable(
|
|
||||||
self, attr_value: Callable
|
|
||||||
) -> Dict[str, Any]:
|
|
||||||
"""
|
|
||||||
Serializes callable attributes by extracting their name and docstring.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
attr_value (Callable): The callable to serialize.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Dict[str, Any]: Dictionary with name and docstring of the callable.
|
|
||||||
"""
|
|
||||||
return {
|
|
||||||
"name": getattr(
|
|
||||||
attr_value, "__name__", type(attr_value).__name__
|
|
||||||
),
|
|
||||||
"doc": getattr(attr_value, "__doc__", None),
|
|
||||||
}
|
|
||||||
|
|
||||||
def _serialize_attr(self, attr_name: str, attr_value: Any) -> Any:
|
|
||||||
"""
|
|
||||||
Serializes an individual attribute, handling non-serializable objects.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
attr_name (str): The name of the attribute.
|
|
||||||
attr_value (Any): The value of the attribute.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Any: The serialized value of the attribute.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
if callable(attr_value):
|
|
||||||
return self._serialize_callable(attr_value)
|
|
||||||
elif hasattr(attr_value, "to_dict"):
|
|
||||||
return (
|
|
||||||
attr_value.to_dict()
|
|
||||||
) # Recursive serialization for nested objects
|
|
||||||
else:
|
|
||||||
json.dumps(
|
|
||||||
attr_value
|
|
||||||
) # Attempt to serialize to catch non-serializable objects
|
|
||||||
return attr_value
|
|
||||||
except (TypeError, ValueError):
|
|
||||||
return f"<Non-serializable: {type(attr_value).__name__}>"
|
|
||||||
|
|
||||||
def to_dict(self) -> Dict[str, Any]:
|
|
||||||
"""
|
|
||||||
Converts all attributes of the class, including callables, into a dictionary.
|
|
||||||
Handles non-serializable attributes by converting them or skipping them.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Dict[str, Any]: A dictionary representation of the class attributes.
|
|
||||||
"""
|
|
||||||
return {
|
|
||||||
attr_name: self._serialize_attr(attr_name, attr_value)
|
|
||||||
for attr_name, attr_value in self.__dict__.items()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def rearrange(
|
|
||||||
name: str = None,
|
|
||||||
description: str = None,
|
|
||||||
agents: List[Agent] = None,
|
|
||||||
flow: str = None,
|
|
||||||
task: str = None,
|
|
||||||
img: str = None,
|
|
||||||
*args,
|
|
||||||
**kwargs,
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
Rearranges the given list of agents based on the specified flow.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
agents (List[Agent]): The list of agents to be rearranged.
|
|
||||||
flow (str): The flow used for rearranging the agents.
|
|
||||||
task (str, optional): The task to be performed during rearrangement. Defaults to None.
|
|
||||||
*args: Additional positional arguments.
|
|
||||||
**kwargs: Additional keyword arguments.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The result of running the agent system with the specified task.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
agents = [agent1, agent2, agent3]
|
|
||||||
flow = "agent1 -> agent2, agent3"
|
|
||||||
task = "Perform a task"
|
|
||||||
rearrange(agents, flow, task)
|
|
||||||
"""
|
|
||||||
agent_system = AgentRearrange(
|
|
||||||
name=name,
|
|
||||||
description=description,
|
|
||||||
agents=agents,
|
|
||||||
flow=flow,
|
|
||||||
*args,
|
|
||||||
**kwargs,
|
|
||||||
)
|
|
||||||
return agent_system.run(task=task, img=img)
|
|
@ -1,307 +0,0 @@
|
|||||||
import os
|
|
||||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
||||||
from typing import Callable, List, Optional, Union
|
|
||||||
|
|
||||||
from swarms.prompts.multi_agent_collab_prompt import (
|
|
||||||
MULTI_AGENT_COLLAB_PROMPT,
|
|
||||||
)
|
|
||||||
from swarms.structs.agent import Agent
|
|
||||||
from swarms.structs.agent_rearrange import AgentRearrange
|
|
||||||
from swarms.utils.loguru_logger import initialize_logger
|
|
||||||
from swarms.utils.output_types import OutputType
|
|
||||||
|
|
||||||
logger = initialize_logger(log_folder="sequential_workflow")
|
|
||||||
|
|
||||||
|
|
||||||
class SequentialWorkflow:
|
|
||||||
"""
|
|
||||||
Orchestrates the execution of a sequence of agents in a defined workflow.
|
|
||||||
|
|
||||||
This class enables the construction and execution of a workflow where multiple agents
|
|
||||||
(or callables) are executed in a specified order, passing tasks and optional data
|
|
||||||
through the chain. It supports both synchronous and asynchronous execution, as well as
|
|
||||||
batched and concurrent task processing.
|
|
||||||
|
|
||||||
Attributes:
|
|
||||||
id (str): Unique identifier for the workflow instance.
|
|
||||||
name (str): Human-readable name for the workflow.
|
|
||||||
description (str): Description of the workflow's purpose.
|
|
||||||
agents (List[Union[Agent, Callable]]): List of agents or callables to execute in sequence.
|
|
||||||
max_loops (int): Maximum number of times to execute the workflow.
|
|
||||||
output_type (OutputType): Format of the output from the workflow.
|
|
||||||
shared_memory_system (callable): Optional callable for managing shared memory between agents.
|
|
||||||
multi_agent_collab_prompt (bool): Whether to append a collaborative prompt to each agent.
|
|
||||||
flow (str): String representation of the agent execution order.
|
|
||||||
agent_rearrange (AgentRearrange): Internal helper for managing agent execution.
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
ValueError: If the agents list is None or empty, or if max_loops is set to 0.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
id: str = "sequential_workflow",
|
|
||||||
name: str = "SequentialWorkflow",
|
|
||||||
description: str = "Sequential Workflow, where agents are executed in a sequence.",
|
|
||||||
agents: List[Union[Agent, Callable]] = None,
|
|
||||||
max_loops: int = 1,
|
|
||||||
output_type: OutputType = "dict",
|
|
||||||
shared_memory_system: callable = None,
|
|
||||||
multi_agent_collab_prompt: bool = True,
|
|
||||||
team_awareness: bool = False,
|
|
||||||
streaming_callback: Optional[Callable[[str], None]] = None,
|
|
||||||
*args,
|
|
||||||
**kwargs,
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
Initialize a SequentialWorkflow instance.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
id (str, optional): Unique identifier for the workflow. Defaults to "sequential_workflow".
|
|
||||||
name (str, optional): Name of the workflow. Defaults to "SequentialWorkflow".
|
|
||||||
description (str, optional): Description of the workflow. Defaults to a standard description.
|
|
||||||
agents (List[Union[Agent, Callable]], optional): List of agents or callables to execute in sequence.
|
|
||||||
max_loops (int, optional): Maximum number of times to execute the workflow. Defaults to 1.
|
|
||||||
output_type (OutputType, optional): Output format for the workflow. Defaults to "dict".
|
|
||||||
shared_memory_system (callable, optional): Callable for shared memory management. Defaults to None.
|
|
||||||
multi_agent_collab_prompt (bool, optional): If True, appends a collaborative prompt to each agent.
|
|
||||||
team_awareness (bool, optional): Whether to enable team awareness. Defaults to False.
|
|
||||||
streaming_callback (Optional[Callable[[str], None]], optional): Callback function to receive streaming tokens in real-time. Defaults to None.
|
|
||||||
*args: Additional positional arguments.
|
|
||||||
**kwargs: Additional keyword arguments.
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
ValueError: If the agents list is None or empty, or if max_loops is set to 0.
|
|
||||||
"""
|
|
||||||
self.id = id
|
|
||||||
self.name = name
|
|
||||||
self.description = description
|
|
||||||
self.agents = agents
|
|
||||||
self.max_loops = max_loops
|
|
||||||
self.output_type = output_type
|
|
||||||
self.shared_memory_system = shared_memory_system
|
|
||||||
self.multi_agent_collab_prompt = multi_agent_collab_prompt
|
|
||||||
self.team_awareness = team_awareness
|
|
||||||
self.streaming_callback = streaming_callback
|
|
||||||
|
|
||||||
self.reliability_check()
|
|
||||||
self.flow = self.sequential_flow()
|
|
||||||
|
|
||||||
self.agent_rearrange = AgentRearrange(
|
|
||||||
name=self.name,
|
|
||||||
description=self.description,
|
|
||||||
agents=self.agents,
|
|
||||||
flow=self.flow,
|
|
||||||
max_loops=self.max_loops,
|
|
||||||
output_type=self.output_type,
|
|
||||||
team_awareness=self.team_awareness,
|
|
||||||
streaming_callback=self.streaming_callback,
|
|
||||||
*args,
|
|
||||||
**kwargs,
|
|
||||||
)
|
|
||||||
|
|
||||||
def reliability_check(self):
|
|
||||||
"""
|
|
||||||
Validates the workflow configuration and prepares agents for execution.
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
ValueError: If the agents list is None or empty, or if max_loops is set to 0.
|
|
||||||
"""
|
|
||||||
if self.agents is None or len(self.agents) == 0:
|
|
||||||
raise ValueError("Agents list cannot be None or empty")
|
|
||||||
|
|
||||||
if self.max_loops == 0:
|
|
||||||
raise ValueError("max_loops cannot be 0")
|
|
||||||
|
|
||||||
if self.multi_agent_collab_prompt is True:
|
|
||||||
for agent in self.agents:
|
|
||||||
agent.system_prompt += MULTI_AGENT_COLLAB_PROMPT
|
|
||||||
|
|
||||||
logger.info(
|
|
||||||
f"Sequential Workflow Name: {self.name} is ready to run."
|
|
||||||
)
|
|
||||||
|
|
||||||
def sequential_flow(self):
|
|
||||||
"""
|
|
||||||
Constructs a string representation of the agent execution order.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
str: A string showing the order of agent execution (e.g., "AgentA -> AgentB -> AgentC").
|
|
||||||
Returns an empty string if no valid agent names are found.
|
|
||||||
"""
|
|
||||||
if self.agents:
|
|
||||||
agent_names = []
|
|
||||||
for agent in self.agents:
|
|
||||||
try:
|
|
||||||
# Try to get agent_name, fallback to name if not available
|
|
||||||
agent_name = (
|
|
||||||
getattr(agent, "agent_name", None)
|
|
||||||
or agent.name
|
|
||||||
)
|
|
||||||
agent_names.append(agent_name)
|
|
||||||
except AttributeError:
|
|
||||||
logger.warning(
|
|
||||||
f"Could not get name for agent {agent}"
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
|
|
||||||
if agent_names:
|
|
||||||
flow = " -> ".join(agent_names)
|
|
||||||
else:
|
|
||||||
flow = ""
|
|
||||||
logger.warning(
|
|
||||||
"No valid agent names found to create flow"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
flow = ""
|
|
||||||
logger.warning("No agents provided to create flow")
|
|
||||||
|
|
||||||
return flow
|
|
||||||
|
|
||||||
def run(
|
|
||||||
self,
|
|
||||||
task: str,
|
|
||||||
img: Optional[str] = None,
|
|
||||||
imgs: Optional[List[str]] = None,
|
|
||||||
*args,
|
|
||||||
**kwargs,
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
Executes a specified task through the agents in the dynamically constructed flow.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
task (str): The task for the agents to execute.
|
|
||||||
img (Optional[str], optional): An optional image input for the agents.
|
|
||||||
imgs (Optional[List[str]], optional): Optional list of images for the agents.
|
|
||||||
*args: Additional positional arguments.
|
|
||||||
**kwargs: Additional keyword arguments.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
str: The final result after processing through all agents.
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
ValueError: If the task is None or empty.
|
|
||||||
Exception: If any error occurs during task execution.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
# prompt = f"{MULTI_AGENT_COLLAB_PROMPT}\n\n{task}"
|
|
||||||
return self.agent_rearrange.run(
|
|
||||||
task=task,
|
|
||||||
img=img,
|
|
||||||
streaming_callback=self.streaming_callback,
|
|
||||||
*args,
|
|
||||||
**kwargs,
|
|
||||||
)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(
|
|
||||||
f"An error occurred while executing the task: {e}"
|
|
||||||
)
|
|
||||||
raise e
|
|
||||||
|
|
||||||
def __call__(self, task: str, *args, **kwargs):
|
|
||||||
"""
|
|
||||||
Allows the SequentialWorkflow instance to be called as a function.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
task (str): The task for the agents to execute.
|
|
||||||
*args: Additional positional arguments.
|
|
||||||
**kwargs: Additional keyword arguments.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
str: The final result after processing through all agents.
|
|
||||||
"""
|
|
||||||
return self.run(task, *args, **kwargs)
|
|
||||||
|
|
||||||
def run_batched(self, tasks: List[str]) -> List[str]:
|
|
||||||
"""
|
|
||||||
Executes a batch of tasks through the agents in the dynamically constructed flow.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
tasks (List[str]): A list of tasks for the agents to execute.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
List[str]: A list of final results after processing through all agents.
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
ValueError: If tasks is None, empty, or contains non-string elements.
|
|
||||||
Exception: If any error occurs during task execution.
|
|
||||||
"""
|
|
||||||
if not tasks or not all(
|
|
||||||
isinstance(task, str) for task in tasks
|
|
||||||
):
|
|
||||||
raise ValueError(
|
|
||||||
"Tasks must be a non-empty list of strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
return [self.agent_rearrange.run(task) for task in tasks]
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(
|
|
||||||
f"An error occurred while executing the batch of tasks: {e}"
|
|
||||||
)
|
|
||||||
raise
|
|
||||||
|
|
||||||
async def run_async(self, task: str) -> str:
|
|
||||||
"""
|
|
||||||
Executes the specified task through the agents in the dynamically constructed flow asynchronously.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
task (str): The task for the agents to execute.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
str: The final result after processing through all agents.
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
ValueError: If task is None or not a string.
|
|
||||||
Exception: If any error occurs during task execution.
|
|
||||||
"""
|
|
||||||
if not task or not isinstance(task, str):
|
|
||||||
raise ValueError("Task must be a non-empty string")
|
|
||||||
|
|
||||||
try:
|
|
||||||
return await self.agent_rearrange.run_async(task)
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(
|
|
||||||
f"An error occurred while executing the task asynchronously: {e}"
|
|
||||||
)
|
|
||||||
raise
|
|
||||||
|
|
||||||
async def run_concurrent(self, tasks: List[str]) -> List[str]:
|
|
||||||
"""
|
|
||||||
Executes a batch of tasks through the agents in the dynamically constructed flow concurrently.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
tasks (List[str]): A list of tasks for the agents to execute.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
List[str]: A list of final results after processing through all agents.
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
ValueError: If tasks is None, empty, or contains non-string elements.
|
|
||||||
Exception: If any error occurs during task execution.
|
|
||||||
"""
|
|
||||||
if not tasks or not all(
|
|
||||||
isinstance(task, str) for task in tasks
|
|
||||||
):
|
|
||||||
raise ValueError(
|
|
||||||
"Tasks must be a non-empty list of strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
with ThreadPoolExecutor(
|
|
||||||
max_workers=os.cpu_count()
|
|
||||||
) as executor:
|
|
||||||
results = [
|
|
||||||
executor.submit(self.agent_rearrange.run, task)
|
|
||||||
for task in tasks
|
|
||||||
]
|
|
||||||
return [
|
|
||||||
result.result()
|
|
||||||
for result in as_completed(results)
|
|
||||||
]
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(
|
|
||||||
f"An error occurred while executing the batch of tasks concurrently: {e}"
|
|
||||||
)
|
|
||||||
raise
|
|
Loading…
Reference in new issue