mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-07-05 23:19:22 +00:00
Implement DMN mode support in crew creation and execution (#6194)
* Implement DMN mode support in crew creation and execution - Added `is_dmn_mode_enabled` utility to check for enterprise non-interactive mode based on the `CREWAI_DMN` environment variable. - Updated `create` function in `cli.py` to enforce required parameters when DMN mode is active, raising appropriate usage errors. - Enhanced `create_crew` and `create_json_crew` functions to skip provider prompts and handle folder existence checks in DMN mode. - Introduced non-interactive defaults for agent and task creation in DMN mode, ensuring seamless project setup without user input. - Modified `run_crew` to bypass TUI and handle runtime inputs directly when in DMN mode, improving execution flow for JSON-defined crews. - Added tests to validate DMN mode behavior, ensuring correct handling of required inputs and non-interactive defaults. * Implement DMN mode support in crew creation and execution - Introduced `is_dmn_mode_enabled()` utility to check for non-interactive mode based on the `CREWAI_DMN` environment variable. - Updated `create` function to enforce required parameters when DMN mode is active, raising appropriate usage errors. - Modified `create_crew` and `create_json_crew` functions to skip provider prompts and utilize non-interactive defaults in DMN mode. - Enhanced `run_crew` to bypass TUI and handle runtime inputs directly in DMN mode, ensuring smooth execution without user interaction. - Added tests to validate DMN mode behavior, including requirements for type and name, and ensuring proper handling of existing folders and missing inputs.
This commit is contained in:
@@ -4,6 +4,7 @@ from unittest import mock
|
||||
import pytest
|
||||
from click.testing import CliRunner
|
||||
from crewai_cli.cli import (
|
||||
create,
|
||||
deploy_create,
|
||||
deploy_list,
|
||||
deploy_logs,
|
||||
@@ -157,6 +158,28 @@ def test_run_rejects_inputs_without_definition(run_flow_definition, run_crew, ru
|
||||
run_crew.assert_not_called()
|
||||
|
||||
|
||||
@mock.patch("crewai_cli.create_json_crew.create_json_crew")
|
||||
def test_create_crew_in_dmn_mode_skips_provider_prompts(create_json_crew, runner):
|
||||
result = runner.invoke(create, ["crew", "DMN Crew"], env={"CREWAI_DMN": "True"})
|
||||
|
||||
assert result.exit_code == 0
|
||||
create_json_crew.assert_called_once_with("DMN Crew", None, True)
|
||||
|
||||
|
||||
def test_create_requires_type_in_dmn_mode(runner):
|
||||
result = runner.invoke(create, env={"CREWAI_DMN": "True"})
|
||||
|
||||
assert result.exit_code == 2
|
||||
assert "TYPE is required when CREWAI_DMN is set" in result.output
|
||||
|
||||
|
||||
def test_create_requires_name_in_dmn_mode(runner):
|
||||
result = runner.invoke(create, ["flow"], env={"CREWAI_DMN": "True"})
|
||||
|
||||
assert result.exit_code == 2
|
||||
assert "NAME is required when CREWAI_DMN is set" in result.output
|
||||
|
||||
|
||||
@mock.patch("crewai_cli.cli.AuthenticationCommand")
|
||||
def test_login(command, runner):
|
||||
mock_auth = command.return_value
|
||||
|
||||
@@ -812,3 +812,34 @@ def test_json_wizard_task_reprompts_on_cancelled_agent_pick(monkeypatch):
|
||||
|
||||
assert len(pick_calls) == 2
|
||||
assert task["agent"] == "second_agent"
|
||||
|
||||
|
||||
def test_json_create_dmn_mode_uses_non_interactive_defaults(tmp_path, monkeypatch):
|
||||
monkeypatch.chdir(tmp_path)
|
||||
monkeypatch.setenv("CREWAI_DMN", "True")
|
||||
monkeypatch.setattr(
|
||||
json_crew,
|
||||
"_wizard_agents_and_tasks",
|
||||
lambda **_: pytest.fail("DMN mode must not run the wizard"),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
json_crew,
|
||||
"_setup_env",
|
||||
lambda *_args, **_kwargs: pytest.fail("DMN mode must not prompt for env vars"),
|
||||
)
|
||||
|
||||
json_crew.create_json_crew("DMN Crew", provider="anthropic", skip_provider=False)
|
||||
|
||||
project_root = tmp_path / "dmn_crew"
|
||||
assert (project_root / "crew.jsonc").exists()
|
||||
assert (project_root / "agents" / "researcher.jsonc").exists()
|
||||
assert not (project_root / ".env").exists()
|
||||
|
||||
crew_template = (project_root / "crew.jsonc").read_text()
|
||||
agent_template = (project_root / "agents" / "researcher.jsonc").read_text()
|
||||
|
||||
assert '"memory": false' in crew_template
|
||||
assert '"description": "Research current AI trends and write a concise summary."' in (
|
||||
crew_template
|
||||
)
|
||||
assert '"llm": "anthropic/claude-opus-4-6"' in agent_template
|
||||
|
||||
@@ -402,6 +402,7 @@ def test_missing_input_names_accepts_hyphenated_placeholders():
|
||||
|
||||
def _patch_tui_run(monkeypatch, status: str):
|
||||
"""Stub the TUI pieces of _run_json_crew so only exit handling runs."""
|
||||
monkeypatch.delenv("CREWAI_DMN", raising=False)
|
||||
|
||||
class FakeApp:
|
||||
def __init__(self, **kwargs):
|
||||
@@ -446,6 +447,84 @@ def test_run_json_crew_completed_status_returns_result(monkeypatch, tmp_path: Pa
|
||||
assert run_crew_module._run_json_crew() == "result"
|
||||
|
||||
|
||||
def test_run_json_crew_dmn_mode_bypasses_tui(monkeypatch, tmp_path: Path, capsys):
|
||||
from types import SimpleNamespace
|
||||
|
||||
monkeypatch.chdir(tmp_path)
|
||||
monkeypatch.setenv("CREWAI_DMN", "True")
|
||||
crew_path = tmp_path / "crew.jsonc"
|
||||
crew_path.write_text("{}")
|
||||
kickoff_calls = []
|
||||
|
||||
class FakeCrew:
|
||||
name = "Demo"
|
||||
agents = [SimpleNamespace(role="Researcher", goal="Research", backstory="")]
|
||||
tasks = [
|
||||
SimpleNamespace(
|
||||
description="Research",
|
||||
expected_output="Findings",
|
||||
output_file=None,
|
||||
)
|
||||
]
|
||||
|
||||
def kickoff(self, inputs):
|
||||
kickoff_calls.append(inputs)
|
||||
return "plain result"
|
||||
|
||||
monkeypatch.setattr(run_crew_module, "find_crew_json_file", lambda: crew_path)
|
||||
monkeypatch.setattr(
|
||||
run_crew_module,
|
||||
"_load_json_crew",
|
||||
lambda _path: (FakeCrew(), {"topic": "AI"}),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
run_crew_module,
|
||||
"_load_json_crew_for_tui",
|
||||
lambda _path: pytest.fail("DMN mode must not start the TUI loader"),
|
||||
)
|
||||
|
||||
assert run_crew_module._run_json_crew() == "plain result"
|
||||
|
||||
captured = capsys.readouterr()
|
||||
assert kickoff_calls == [{"topic": "AI"}]
|
||||
assert "plain result" in captured.out
|
||||
|
||||
|
||||
def test_run_json_crew_dmn_mode_exits_on_missing_inputs(
|
||||
monkeypatch, tmp_path: Path, capsys
|
||||
):
|
||||
from types import SimpleNamespace
|
||||
|
||||
monkeypatch.chdir(tmp_path)
|
||||
monkeypatch.setenv("CREWAI_DMN", "True")
|
||||
crew_path = tmp_path / "crew.jsonc"
|
||||
crew_path.write_text("{}")
|
||||
crew = SimpleNamespace(
|
||||
agents=[
|
||||
SimpleNamespace(
|
||||
role="Researcher",
|
||||
goal="Research {topic}",
|
||||
backstory="",
|
||||
)
|
||||
],
|
||||
tasks=[],
|
||||
)
|
||||
|
||||
monkeypatch.setattr(run_crew_module, "find_crew_json_file", lambda: crew_path)
|
||||
monkeypatch.setattr(
|
||||
run_crew_module,
|
||||
"_load_json_crew",
|
||||
lambda _path: (crew, {}),
|
||||
)
|
||||
|
||||
with pytest.raises(SystemExit) as exc_info:
|
||||
run_crew_module._run_json_crew()
|
||||
|
||||
captured = capsys.readouterr()
|
||||
assert exc_info.value.code == 1
|
||||
assert "Missing runtime inputs for CREWAI_DMN mode: topic" in captured.err
|
||||
|
||||
|
||||
def test_has_json_crew_defers_to_declared_flow_type(monkeypatch, tmp_path: Path):
|
||||
"""A flow project containing a stray crew.jsonc must still run as a flow."""
|
||||
monkeypatch.chdir(tmp_path)
|
||||
|
||||
@@ -102,6 +102,23 @@ def test_tree_copy_to_existing_directory(temp_tree):
|
||||
shutil.rmtree(dest_dir)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("value", ["1", "true", "True", "yes", "anything"])
|
||||
def test_is_dmn_mode_enabled_for_truthy_values(monkeypatch, value):
|
||||
monkeypatch.setenv("CREWAI_DMN", value)
|
||||
|
||||
assert utils.is_dmn_mode_enabled() is True
|
||||
|
||||
|
||||
@pytest.mark.parametrize("value", [None, "", "0", "false", "no", "off"])
|
||||
def test_is_dmn_mode_enabled_for_falsey_values(monkeypatch, value):
|
||||
if value is None:
|
||||
monkeypatch.delenv("CREWAI_DMN", raising=False)
|
||||
else:
|
||||
monkeypatch.setenv("CREWAI_DMN", value)
|
||||
|
||||
assert utils.is_dmn_mode_enabled() is False
|
||||
|
||||
|
||||
# Tests for extract_available_exports, get_crews, get_flows, fetch_crews,
|
||||
# is_valid_tool live in lib/crewai/tests/cli/test_utils.py — the canonical
|
||||
# implementations are in crewai.utilities.project_utils.
|
||||
|
||||
Reference in New Issue
Block a user