Repository: crewAIInc/crewAI
Pull request #1525 (2024-10-28 20:32:55)

Update flows cli to allow you to easily add additional crews to a flow
This branch allows users to easily add new crews to their flows by running `crewai flow add-crew <crew name>`.

To test, create a flow by running `crewai create flow <flow name>`

`cd` into the new flow.

then run the new command to add the crew to the flow.


Patch:
diff --git a/docs/concepts/flows.mdx b/docs/concepts/flows.mdx
index dee6fd4f..39cd6357 100644
--- a/docs/concepts/flows.mdx
+++ b/docs/concepts/flows.mdx
@@ -560,6 +560,40 @@ uv run kickoff
 
 The flow will execute, and you should see the output in the console.
 
+
+### Adding Additional Crews Using the CLI
+
+Once you have created your initial flow, you can easily add additional crews to your project using the CLI. This allows you to expand your flow's capabilities by integrating new crews without starting from scratch.
+
+To add a new crew to your existing flow, use the following command:
+
+```bash
+crewai flow add-crew <crew_name>
+```
+
+This command will create a new directory for your crew within the `crews` folder of your flow project. It will include the necessary configuration files and a crew definition file, similar to the initial setup.
+
+#### Folder Structure
+
+After adding a new crew, your folder structure will look like this:
+
+name_of_flow/
+├── crews/
+│   ├── poem_crew/
+│   │   ├── config/
+│   │   │   ├── agents.yaml
+│   │   │   └── tasks.yaml
+│   │   └── poem_crew.py
+│   └── name_of_crew/
+│       ├── config/
+│       │   ├── agents.yaml
+│       │   └── tasks.yaml
+│       └── name_of_crew.py
+
+You can then customize the `agents.yaml` and `tasks.yaml` files to define the agents and tasks for your new crew. The `name_of_crew.py` file will contain the crew's logic, which you can modify to suit your needs.
+
+By using the CLI to add additional crews, you can efficiently build complex AI workflows that leverage multiple crews working together.
+
 ## Plot Flows
 
 Visualizing your AI workflows can provide valuable insights into the structure and execution paths of your flows. CrewAI offers a powerful visualization tool that allows you to generate interactive plots of your flows, making it easier to understand and optimize your AI workflows.
diff --git a/src/crewai/cli/add_crew_to_flow.py b/src/crewai/cli/add_crew_to_flow.py
new file mode 100644
index 00000000..e4901fa8
--- /dev/null
+++ b/src/crewai/cli/add_crew_to_flow.py
@@ -0,0 +1,70 @@
+from pathlib import Path
+
+import click
+
+from crewai.cli.utils import copy_template
+
+
+def add_crew_to_flow(crew_name: str) -> None:
+    """Add a new crew to the current flow."""
+    # Check if pyproject.toml exists in the current directory
+    if not Path("pyproject.toml").exists():
+        print("This command must be run from the root of a flow project.")
+        raise click.ClickException(
+            "This command must be run from the root of a flow project."
+        )
+
+    # Determine the flow folder based on the current directory
+    flow_folder = Path.cwd()
+    crews_folder = flow_folder / "src" / flow_folder.name / "crews"
+
+    if not crews_folder.exists():
+        print("Crews folder does not exist in the current flow.")
+        raise click.ClickException("Crews folder does not exist in the current flow.")
+
+    # Create the crew within the flow's crews directory
+    create_embedded_crew(crew_name, parent_folder=crews_folder)
+
+    click.echo(
+        f"Crew {crew_name} added to the current flow successfully!",
+    )
+
+
+def create_embedded_crew(crew_name: str, parent_folder: Path) -> None:
+    """Create a new crew within an existing flow project."""
+    folder_name = crew_name.replace(" ", "_").replace("-", "_").lower()
+    class_name = crew_name.replace("_", " ").replace("-", " ").title().replace(" ", "")
+
+    crew_folder = parent_folder / folder_name
+
+    if crew_folder.exists():
+        if not click.confirm(
+            f"Crew {folder_name} already exists. Do you want to override it?"
+        ):
+            click.secho("Operation cancelled.", fg="yellow")
+            return
+        click.secho(f"Overriding crew {folder_name}...", fg="green", bold=True)
+    else:
+        click.secho(f"Creating crew {folder_name}...", fg="green", bold=True)
+        crew_folder.mkdir(parents=True)
+
+    # Create config and crew.py files
+    config_folder = crew_folder / "config"
+    config_folder.mkdir(exist_ok=True)
+
+    templates_dir = Path(__file__).parent / "templates" / "crew"
+    config_template_files = ["agents.yaml", "tasks.yaml"]
+    crew_template_file = f"{folder_name}_crew.py"  # Updated file name
+
+    for file_name in config_template_files:
+        src_file = templates_dir / "config" / file_name
+        dst_file = config_folder / file_name
+        copy_template(src_file, dst_file, crew_name, class_name, folder_name)
+
+    src_file = templates_dir / "crew.py"
+    dst_file = crew_folder / crew_template_file
+    copy_template(src_file, dst_file, crew_name, class_name, folder_name)
+
+    click.secho(
+        f"Crew {crew_name} added to the flow successfully!", fg="green", bold=True
+    )
diff --git a/src/crewai/cli/cli.py b/src/crewai/cli/cli.py
index 1ea2ef4e..0f43ff3f 100644
--- a/src/crewai/cli/cli.py
+++ b/src/crewai/cli/cli.py
@@ -3,6 +3,7 @@ from typing import Optional
 import click
 import pkg_resources
 
+from crewai.cli.add_crew_to_flow import add_crew_to_flow
 from crewai.cli.create_crew import create_crew
 from crewai.cli.create_flow import create_flow
 from crewai.cli.create_pipeline import create_pipeline
@@ -178,10 +179,12 @@ def test(n_iterations: int, model: str):
     evaluate_crew(n_iterations, model)
 
 
-@crewai.command(context_settings=dict(
-    ignore_unknown_options=True,
-    allow_extra_args=True,
-))
+@crewai.command(
+    context_settings=dict(
+        ignore_unknown_options=True,
+        allow_extra_args=True,
+    )
+)
 @click.pass_context
 def install(context):
     """Install the Crew."""
@@ -324,5 +327,13 @@ def flow_plot():
     plot_flow()
 
 
+@flow.command(name="add-crew")
+@click.argument("crew_name")
+def flow_add_crew(crew_name):
+    """Add a crew to an existing flow."""
+    click.echo(f"Adding crew {crew_name} to the flow")
+    add_crew_to_flow(crew_name)
+
+
 if __name__ == "__main__":
     crewai()


Test patch:
diff --git a/tests/cli/cli_test.py b/tests/cli/cli_test.py
index b2fb8d0e..05e1cf03 100644
--- a/tests/cli/cli_test.py
+++ b/tests/cli/cli_test.py
@@ -1,7 +1,9 @@
+from pathlib import Path
 from unittest import mock
 
 import pytest
 from click.testing import CliRunner
+
 from crewai.cli.cli import (
     deploy_create,
     deploy_list,
@@ -9,6 +11,7 @@ from crewai.cli.cli import (
     deploy_push,
     deploy_remove,
     deply_status,
+    flow_add_crew,
     reset_memories,
     signup,
     test,
@@ -277,3 +280,42 @@ def test_deploy_remove_no_uuid(command, runner):
 
     assert result.exit_code == 0
     mock_deploy.remove_crew.assert_called_once_with(uuid=None)
+
+
+@mock.patch("crewai.cli.add_crew_to_flow.create_embedded_crew")
+@mock.patch("pathlib.Path.exists", return_value=True)  # Mock the existence check
+def test_flow_add_crew(mock_path_exists, mock_create_embedded_crew, runner):
+    crew_name = "new_crew"
+    result = runner.invoke(flow_add_crew, [crew_name])
+
+    # Log the output for debugging
+    print(result.output)
+
+    assert result.exit_code == 0, f"Command failed with output: {result.output}"
+    assert f"Adding crew {crew_name} to the flow" in result.output
+
+    # Verify that create_embedded_crew was called with the correct arguments
+    mock_create_embedded_crew.assert_called_once()
+    call_args, call_kwargs = mock_create_embedded_crew.call_args
+    assert call_args[0] == crew_name
+    assert "parent_folder" in call_kwargs
+    assert isinstance(call_kwargs["parent_folder"], Path)
+
+
+def test_add_crew_to_flow_not_in_root(runner):
+    # Simulate not being in the root of a flow project
+    with mock.patch("pathlib.Path.exists", autospec=True) as mock_exists:
+        # Mock Path.exists to return False when checking for pyproject.toml
+        def exists_side_effect(self):
+            if self.name == "pyproject.toml":
+                return False  # Simulate that pyproject.toml does not exist
+            return True  # All other paths exist
+
+        mock_exists.side_effect = exists_side_effect
+
+        result = runner.invoke(flow_add_crew, ["new_crew"])
+
+        assert result.exit_code != 0
+        assert "This command must be run from the root of a flow project." in str(
+            result.output
+        )