diff --git a/docs/swarms/structs/sequential_workflow.md b/docs/swarms/structs/sequential_workflow.md index c7fd6cd0..4cb45429 100644 --- a/docs/swarms/structs/sequential_workflow.md +++ b/docs/swarms/structs/sequential_workflow.md @@ -41,13 +41,13 @@ graph TD ### `__init__(self, agents: List[Union[Agent, Callable]] = None, max_loops: int = 1, team_awareness: bool = False, *args, **kwargs)` -The constructor initializes the `SequentialWorkflow` object. +The constructor initializes the `SequentialWorkflow` object. Agents can be provided during initialization or the workflow can be initialized empty and configured later. - **Parameters:** - `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): The list of agents or callables to execute in sequence. + - `agents` (`List[Union[Agent, Callable]]`, optional): The list of agents or callables to execute in sequence. Can be `None` during initialization but must be set before calling any run methods. Defaults to `None`. - `max_loops` (`int`, optional): The maximum number of loops 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`. @@ -56,6 +56,8 @@ The constructor initializes the `SequentialWorkflow` object. - `*args`: Variable length argument list. - `**kwargs`: Arbitrary keyword arguments. +- **Note:** When initialized without agents, the workflow's `agent_rearrange` attribute will be `None` and `flow` will be an empty string. You must provide agents before running the workflow. + ### `run(self, task: str, img: Optional[str] = None, imgs: Optional[List[str]] = None, *args, **kwargs) -> str` Runs the specified task through the agents in the dynamically constructed flow. @@ -70,6 +72,9 @@ Runs the specified task through the agents in the dynamically constructed flow. - **Returns:** - The final result after processing through all agents. +- **Raises:** + - `ValueError`: If agents list is None or empty when attempting to run. + ### `run_batched(self, tasks: List[str]) -> List[str]` Executes a batch of tasks through the agents in the dynamically constructed flow. @@ -80,6 +85,9 @@ Executes a batch of tasks through the agents in the dynamically constructed flow - **Returns:** - `List[str]`: A list of final results after processing through all agents. +- **Raises:** + - `ValueError`: If agents list is None or empty when attempting to run. + ### `async run_async(self, task: str) -> str` Executes the specified task through the agents asynchronously. @@ -90,6 +98,9 @@ Executes the specified task through the agents asynchronously. - **Returns:** - `str`: The final result after processing through all agents. +- **Raises:** + - `ValueError`: If agents list is None or empty when attempting to run. + ### `async run_concurrent(self, tasks: List[str]) -> List[str]` Executes a batch of tasks through the agents concurrently. @@ -100,6 +111,9 @@ Executes a batch of tasks through the agents concurrently. - **Returns:** - `List[str]`: A list of final results after processing through all agents. +- **Raises:** + - `ValueError`: If agents list is None or empty when attempting to run. + ## Usage Examples ### Basic Sequential Workflow @@ -303,7 +317,7 @@ print(result) | Parameter | Description | Default | |-----------|-------------|---------| -| `agents` | List of agents to execute in sequence | Required | +| `agents` | List of agents to execute in sequence | None (optional at init, required before run) | | `name` | Name of the workflow | "SequentialWorkflow" | | `description` | Description of workflow purpose | Standard description | | `max_loops` | Number of times to execute workflow | 1 | @@ -317,6 +331,29 @@ print(result) 4. **System Prompts**: Write comprehensive system prompts that explain the agent's expertise and responsibilities. 5. **Task Clarity**: Provide clear, specific tasks when calling `run()`. +## Initialization Behavior + +The `SequentialWorkflow` can be initialized with or without agents: + +### With Agents (Standard) +```python +workflow = SequentialWorkflow(agents=[agent1, agent2, agent3]) +# Ready to run immediately +workflow.run("Task to execute") +``` + +### Without Agents (Deferred Configuration) +```python +# Create an empty workflow +workflow = SequentialWorkflow() + +# Configure later (you'll need to reinitialize with agents) +workflow = SequentialWorkflow(agents=[agent1, agent2]) +workflow.run("Task to execute") +``` + +**Note**: When initialized without agents, `agent_rearrange` will be `None` and `flow` will be an empty string. You must provide agents before calling any execution methods. + ## Logging and Error Handling The `run` method includes comprehensive logging to track workflow execution: @@ -325,6 +362,19 @@ The `run` method includes comprehensive logging to track workflow execution: 2023-05-08 10:30:15.456 | INFO | Sequential Workflow Name: SequentialWorkflow is ready to run. ``` +### Error Handling + +All execution methods (`run`, `run_batched`, `run_async`, `run_concurrent`) validate that agents are configured before execution: + +```python +workflow = SequentialWorkflow() # No agents + +try: + workflow.run("Some task") +except ValueError as e: + print(e) # "Agents list cannot be None or empty. Add agents before running the workflow." +``` + All errors during execution are logged and re-raised for proper error handling. ## Accessing Workflow Information diff --git a/swarms/structs/sequential_workflow.py b/swarms/structs/sequential_workflow.py index 7ccb1c6c..4820e932 100644 --- a/swarms/structs/sequential_workflow.py +++ b/swarms/structs/sequential_workflow.py @@ -80,18 +80,23 @@ class SequentialWorkflow: self.multi_agent_collab_prompt = multi_agent_collab_prompt self.team_awareness = team_awareness - 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, - ) + # Only validate and initialize if agents are provided + if self.agents is not None and len(self.agents) > 0: + 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, + ) + else: + self.flow = "" + self.agent_rearrange = None def reliability_check(self): """ @@ -180,9 +185,15 @@ class SequentialWorkflow: str: The final result after processing through all agents. Raises: - ValueError: If the task is None or empty. + ValueError: If the task is None or empty, or if no agents are configured. Exception: If any error occurs during task execution. """ + if self.agents is None or len(self.agents) == 0: + raise ValueError("Agents list cannot be None or empty. Add agents before running the workflow.") + + if self.agent_rearrange is None: + raise ValueError("Workflow not properly initialized. AgentRearrange is None.") + try: # prompt = f"{MULTI_AGENT_COLLAB_PROMPT}\n\n{task}" return self.agent_rearrange.run( @@ -221,9 +232,15 @@ class SequentialWorkflow: List[str]: A list of final results after processing through all agents. Raises: - ValueError: If tasks is None, empty, or contains non-string elements. + ValueError: If tasks is None, empty, contains non-string elements, or if no agents are configured. Exception: If any error occurs during task execution. """ + if self.agents is None or len(self.agents) == 0: + raise ValueError("Agents list cannot be None or empty. Add agents before running the workflow.") + + if self.agent_rearrange is None: + raise ValueError("Workflow not properly initialized. AgentRearrange is None.") + if not tasks or not all( isinstance(task, str) for task in tasks ): @@ -250,9 +267,15 @@ class SequentialWorkflow: str: The final result after processing through all agents. Raises: - ValueError: If task is None or not a string. + ValueError: If task is None, not a string, or if no agents are configured. Exception: If any error occurs during task execution. """ + if self.agents is None or len(self.agents) == 0: + raise ValueError("Agents list cannot be None or empty. Add agents before running the workflow.") + + if self.agent_rearrange is None: + raise ValueError("Workflow not properly initialized. AgentRearrange is None.") + if not task or not isinstance(task, str): raise ValueError("Task must be a non-empty string") @@ -275,9 +298,15 @@ class SequentialWorkflow: List[str]: A list of final results after processing through all agents. Raises: - ValueError: If tasks is None, empty, or contains non-string elements. + ValueError: If tasks is None, empty, contains non-string elements, or if no agents are configured. Exception: If any error occurs during task execution. """ + if self.agents is None or len(self.agents) == 0: + raise ValueError("Agents list cannot be None or empty. Add agents before running the workflow.") + + if self.agent_rearrange is None: + raise ValueError("Workflow not properly initialized. AgentRearrange is None.") + if not tasks or not all( isinstance(task, str) for task in tasks ): diff --git a/tests/structs/test_sequential_workflow.py b/tests/structs/test_sequential_workflow.py index 6d8f74a1..ba26ac92 100644 --- a/tests/structs/test_sequential_workflow.py +++ b/tests/structs/test_sequential_workflow.py @@ -7,15 +7,12 @@ from swarms import Agent, SequentialWorkflow def test_sequential_workflow_initialization(): workflow = SequentialWorkflow() assert isinstance(workflow, SequentialWorkflow) - assert len(workflow.tasks) == 0 + assert workflow.agents is None assert workflow.max_loops == 1 - assert workflow.autosave is False - assert ( - workflow.saved_state_filepath - == "sequential_workflow_state.json" - ) - assert workflow.restore_state_filepath is None - assert workflow.dashboard is False + assert workflow.flow == "" + assert workflow.agent_rearrange is None + assert workflow.name == "SequentialWorkflow" + assert workflow.id == "sequential_workflow" def test_sequential_workflow_initialization_with_agents(): @@ -83,6 +80,11 @@ def test_sequential_workflow_multi_agent_execution(): result = workflow.run( "Analyze the impact of renewable energy on climate change" ) + print("\n" + "="*80) + print("WORKFLOW RESULT:") + print("="*80) + print(result) + print("="*80 + "\n") assert result is not None # SequentialWorkflow may return different types based on output_type, just ensure it's not None @@ -232,16 +234,24 @@ def test_sequential_workflow_with_multi_agent_collaboration(): def test_sequential_workflow_error_handling(): """Test SequentialWorkflow error handling""" - # Test with invalid agents list + # Test that initialization with None agents is allowed + workflow_none = SequentialWorkflow(agents=None) + assert workflow_none.agents is None + + # Test that initialization with empty agents is allowed + workflow_empty = SequentialWorkflow(agents=[]) + assert workflow_empty.agents == [] + + # Test that running with no agents raises error with pytest.raises( ValueError, match="Agents list cannot be None or empty" ): - SequentialWorkflow(agents=None) + workflow_none.run("test task") with pytest.raises( ValueError, match="Agents list cannot be None or empty" ): - SequentialWorkflow(agents=[]) + workflow_empty.run("test task") # Test with zero max_loops with pytest.raises(ValueError, match="max_loops cannot be 0"):