Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ Devin Worker 2_state.json
# C extensions
*.so
.ruff_cache
.claude



errors.txt
Expand Down
56 changes: 53 additions & 3 deletions docs/swarms/structs/sequential_workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand All @@ -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.
Expand All @@ -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.
Expand All @@ -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.
Expand All @@ -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.
Expand All @@ -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
Expand Down Expand Up @@ -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 |
Expand All @@ -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:
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion swarms/agents/reasoning_agents.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ def __init__(
majority_voting_prompt: Optional[str] = None,
reasoning_model_name: Optional[
str
] = "claude-3-5-sonnet-20240620",
] = "gpt-4o",
):
"""
Initialize the ReasoningAgentRouter with the specified configuration.
Expand Down
2 changes: 1 addition & 1 deletion swarms/agents/reasoning_duo.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def __init__(
output_type: OutputType = "dict-all-except-first",
reasoning_model_name: Optional[
str
] = "claude-3-5-sonnet-20240620",
] = "gpt-4o",
max_loops: int = 1,
*args,
**kwargs,
Expand Down
1 change: 1 addition & 0 deletions swarms/structs/hiearchical_swarm.py
Original file line number Diff line number Diff line change
Expand Up @@ -914,6 +914,7 @@ def reliability_checks(self):
logger.error(
f"{error_msg}\n[TRACE] Traceback: {traceback.format_exc()}\n[BUG] If this issue persists, please report it at: https://github.com/kyegomez/swarms/issues"
)
raise

def agents_no_print(self):
for agent in self.agents:
Expand Down
61 changes: 45 additions & 16 deletions swarms/structs/sequential_workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,18 +80,23 @@ def __init__(
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):
"""
Expand Down Expand Up @@ -180,9 +185,15 @@ def run(
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(
Expand Down Expand Up @@ -221,9 +232,15 @@ def run_batched(self, tasks: List[str]) -> List[str]:
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
):
Expand All @@ -250,9 +267,15 @@ async def run_async(self, task: str) -> str:
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")

Expand All @@ -275,9 +298,15 @@ async def run_concurrent(self, tasks: List[str]) -> List[str]:
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
):
Expand Down
8 changes: 6 additions & 2 deletions tests/structs/test_reasoning_agent_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
ReasoningAgentRouter,
)

from dotenv import load_dotenv

Check failure

Code scanning / Pyre

Undefined import Error test

Undefined import [21]: Could not find a module corresponding to import dotenv.
load_dotenv()



def test_router_initialization():
"""
Expand Down Expand Up @@ -55,7 +59,7 @@
eval=True,
random_models_on=True,
majority_voting_prompt="Custom voting prompt",
reasoning_model_name="claude-3-5-sonnet-20240620",
reasoning_model_name="gpt-4o",
)
assert (
custom_router is not None
Expand Down Expand Up @@ -250,7 +254,7 @@
"eval": False,
"random_models_on": False,
"majority_voting_prompt": None,
"reasoning_model_name": "claude-3-5-sonnet-20240620",
"reasoning_model_name": "gpt-4o",
}

# Test 1: Reasoning Duo factory
Expand Down
32 changes: 21 additions & 11 deletions tests/structs/test_sequential_workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,12 @@
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():
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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"):
Expand Down
Loading