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:
João Moura
2026-06-16 19:48:31 -03:00
committed by GitHub
parent 06ada68083
commit ebbc0998ef
9 changed files with 270 additions and 8 deletions

View File

@@ -17,6 +17,7 @@ from crewai_cli.user_data import (
from crewai_cli.utils import (
build_env_with_all_tool_credentials,
enable_prompt_line_editing,
is_dmn_mode_enabled,
read_toml,
)
@@ -162,7 +163,13 @@ def create(
classic: bool = False,
) -> None:
"""Create a new crew, or flow."""
dmn_mode = is_dmn_mode_enabled()
if not type:
if dmn_mode:
raise click.UsageError(
"TYPE is required when CREWAI_DMN is set. "
"Use `crewai create crew <name>` or `crewai create flow <name>`."
)
from crewai_cli.tui_picker import pick
options = [
@@ -177,11 +184,15 @@ def create(
raise SystemExit(0)
click.echo()
if not name:
if dmn_mode:
raise click.UsageError("NAME is required when CREWAI_DMN is set.")
enable_prompt_line_editing()
name = click.prompt(
click.style(f" Name of your {type}", fg="cyan", bold=True),
prompt_suffix=click.style(" ", fg="bright_white"), # noqa: RUF001
)
if dmn_mode:
skip_provider = True
if type == "crew":
if classic:
from crewai_cli.create_crew import create_crew

View File

@@ -11,7 +11,12 @@ from crewai_cli.provider import (
select_model,
select_provider,
)
from crewai_cli.utils import copy_template, load_env_vars, write_env_file
from crewai_cli.utils import (
copy_template,
is_dmn_mode_enabled,
load_env_vars,
write_env_file,
)
def get_reserved_script_names() -> set[str]:
@@ -120,6 +125,8 @@ def create_folder_structure(
folder_path = Path(folder_name)
if folder_path.exists():
if is_dmn_mode_enabled():
raise click.ClickException(f"Folder {folder_name} already exists.")
if not click.confirm(
f"Folder {folder_name} already exists. Do you want to override it?"
):
@@ -201,6 +208,8 @@ def create_crew(
) -> None:
folder_path, folder_name, class_name = create_folder_structure(name, parent_folder)
env_vars = load_env_vars(folder_path)
if is_dmn_mode_enabled():
skip_provider = True
if not skip_provider:
if not provider:
provider_models = get_provider_data()

View File

@@ -14,7 +14,12 @@ from rich.text import Text
from crewai_cli.constants import ENV_VARS
from crewai_cli.tui_picker import pick_many, pick_one
from crewai_cli.utils import enable_prompt_line_editing, load_env_vars, write_env_file
from crewai_cli.utils import (
enable_prompt_line_editing,
is_dmn_mode_enabled,
load_env_vars,
write_env_file,
)
# ── Provider / model data ───────────────────────────────────────
@@ -641,6 +646,43 @@ def _wizard_agents_and_tasks(
return agents, tasks, crew_settings
def _default_agents_and_tasks(
default_llm: str | None = None,
) -> tuple[list[dict[str, Any]], list[dict[str, Any]], dict[str, Any]]:
"""Return deterministic scaffold data for non-interactive project creation."""
llm = default_llm or "openai/gpt-4o"
agents = [
{
"name": "researcher",
"role": "Senior Researcher",
"goal": "Research the requested topic and identify useful findings.",
"backstory": (
"You are an experienced researcher who finds relevant information "
"and presents it clearly."
),
"llm": llm,
"tools": [],
"planning": False,
"allow_delegation": False,
}
]
tasks = [
{
"name": "research_task",
"description": "Research current AI trends and write a concise summary.",
"expected_output": "A concise markdown report with key findings.",
"agent": "researcher",
"context": [],
}
]
crew_settings = {
"process": "sequential",
"memory": False,
"inputs": {},
}
return agents, tasks, crew_settings
# ── JSONC generation from wizard data ──────────────────────────
@@ -1029,7 +1071,9 @@ def create_json_crew(
import keyword
import shutil
enable_prompt_line_editing()
dmn_mode = is_dmn_mode_enabled()
if not dmn_mode:
enable_prompt_line_editing()
name = name.rstrip("/")
if not name.strip():
@@ -1048,6 +1092,8 @@ def create_json_crew(
folder_path = Path(folder_name)
if folder_path.exists():
if dmn_mode:
raise click.ClickException(f"Folder {folder_name} already exists.")
if not click.confirm(f"Folder {folder_name} already exists. Override?"):
click.secho("Cancelled.", fg="yellow")
sys.exit(0)
@@ -1056,10 +1102,14 @@ def create_json_crew(
click.echo()
click.secho(f" Creating crew: {name}", fg="green", bold=True)
agents, tasks, crew_settings = _wizard_agents_and_tasks(
skip_provider=skip_provider,
default_llm=_default_model_for_provider(provider),
)
default_llm = _default_model_for_provider(provider)
if dmn_mode:
agents, tasks, crew_settings = _default_agents_and_tasks(default_llm)
else:
agents, tasks, crew_settings = _wizard_agents_and_tasks(
skip_provider=skip_provider,
default_llm=default_llm,
)
# Create directories
folder_path.mkdir(parents=True)
@@ -1104,7 +1154,7 @@ def create_json_crew(
(folder_path / "skills" / ".gitkeep").write_text("", encoding="utf-8")
# Setup .env with API keys
if not skip_provider:
if not skip_provider and not dmn_mode:
models = list({a["llm"] for a in agents})
for model in models:
_setup_env(folder_path, model)

View File

@@ -17,6 +17,7 @@ from packaging import version
from crewai_cli.utils import (
build_env_with_all_tool_credentials,
enable_prompt_line_editing,
is_dmn_mode_enabled,
read_toml,
)
from crewai_cli.version import get_crewai_version
@@ -202,6 +203,35 @@ def _prepare_json_crew_for_tui(crew: Any) -> None:
agent.llm.stream = True
def _runtime_inputs_without_prompt(
crew: Any, default_inputs: dict[str, Any]
) -> dict[str, Any]:
"""Return runtime inputs in non-interactive mode or exit on missing values."""
inputs = dict(default_inputs or {})
missing = _missing_input_names(crew, inputs)
if missing:
missing_list = ", ".join(missing)
click.echo(
"Missing runtime inputs for CREWAI_DMN mode: "
f"{missing_list}. Add them to the `inputs` object in crew.json(c).",
err=True,
)
raise SystemExit(1)
return inputs
def _run_json_crew_without_tui(crew_path: Path) -> Any:
"""Run a JSON-defined crew with plain terminal output."""
with _json_loading_status("Preparing crew..."):
crew, default_inputs = _load_json_crew(crew_path)
runtime_inputs = _runtime_inputs_without_prompt(crew, default_inputs)
result = crew.kickoff(inputs=runtime_inputs)
if result is not None:
click.echo(str(result))
return result
def _run_json_crew(trained_agents_file: str | None = None) -> Any:
"""Load and run a JSON-defined crew."""
from dotenv import load_dotenv
@@ -219,6 +249,9 @@ def _run_json_crew(trained_agents_file: str | None = None) -> Any:
if crew_path is None:
raise FileNotFoundError("No crew.jsonc or crew.json found")
if is_dmn_mode_enabled():
return _run_json_crew_without_tui(crew_path)
crew_run_app_cls, crew, default_inputs, task_names, agent_names = (
_load_json_crew_for_tui(crew_path)
)

View File

@@ -29,6 +29,7 @@ __all__ = [
"get_project_description",
"get_project_name",
"get_project_version",
"is_dmn_mode_enabled",
"load_env_vars",
"parse_toml",
"read_toml",
@@ -41,6 +42,14 @@ __all__ = [
console = Console()
def is_dmn_mode_enabled() -> bool:
"""Return True when the enterprise non-interactive mode is enabled."""
value = os.environ.get("CREWAI_DMN")
if value is None:
return False
return value.strip().lower() not in {"", "0", "false", "no", "off"}
def enable_prompt_line_editing() -> None:
"""Enable cursor movement/history editing for Click text prompts when available."""
try: