mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-07-01 05:08:12 +00:00
Fix JSON crew template rendering (#6359)
JSON crews were not using existing CLI templates.
This commit is contained in:
@@ -18,6 +18,7 @@ from crewai_cli.utils import (
|
||||
enable_prompt_line_editing,
|
||||
is_dmn_mode_enabled,
|
||||
load_env_vars,
|
||||
render_template,
|
||||
write_env_file,
|
||||
)
|
||||
from crewai_cli.version import get_crewai_tools_dependency
|
||||
@@ -79,61 +80,7 @@ _PROVIDER_MODELS: dict[str, list[tuple[str, str]]] = {
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
# ── Static project files ───────────────────────────────────────
|
||||
|
||||
_PYPROJECT_TOML = """\
|
||||
[project]
|
||||
name = "{folder_name}"
|
||||
version = "0.1.0"
|
||||
description = "{name} using crewAI"
|
||||
authors = [{{ name = "Your Name", email = "you@example.com" }}]
|
||||
requires-python = ">=3.10,<3.14"
|
||||
dependencies = [
|
||||
"{crewai_tools_dependency}"
|
||||
]
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
only-include = ["agents", "crew.jsonc", "tools", "knowledge", "skills"]
|
||||
|
||||
[tool.crewai]
|
||||
type = "crew"
|
||||
definition = "crew.jsonc"
|
||||
"""
|
||||
|
||||
_GITIGNORE = """\
|
||||
.env
|
||||
__pycache__/
|
||||
.DS_Store
|
||||
report.md
|
||||
"""
|
||||
|
||||
_README = """\
|
||||
# {name}
|
||||
|
||||
A crewAI project using JSON-first configuration.
|
||||
|
||||
## Running
|
||||
|
||||
```bash
|
||||
crewai run
|
||||
```
|
||||
|
||||
## Project Structure
|
||||
|
||||
- `agents/` - Agent definitions (JSONC)
|
||||
- `crew.jsonc` - Crew definition with tasks and configuration
|
||||
- `tools/` - Custom tools (Python)
|
||||
- `knowledge/` - Knowledge files for agents
|
||||
|
||||
> **Note:** `custom:<name>` tool references execute `tools/<name>.py` as local
|
||||
> Python code when the crew loads. Only run crew projects from sources you
|
||||
> trust.
|
||||
"""
|
||||
_TEMPLATES_DIR = Path(__file__).parent / "templates" / "json_crew"
|
||||
|
||||
|
||||
# ── Common tools for picker ────────────────────────────────────
|
||||
@@ -694,187 +641,64 @@ def _default_agents_and_tasks(
|
||||
def _agent_to_jsonc(agent: dict[str, Any]) -> str:
|
||||
"""Convert agent wizard data to JSONC string with comments."""
|
||||
has_planning = agent["planning"]
|
||||
delegation_val = "true" if agent["allow_delegation"] else "false"
|
||||
delegation_comma = "," if has_planning else ""
|
||||
|
||||
settings_lines = []
|
||||
settings_lines.append(" // Show detailed execution logs")
|
||||
settings_lines.append(' "verbose": false,')
|
||||
settings_lines.append("")
|
||||
settings_lines.append(
|
||||
" // Allow this agent to delegate tasks to other agents in the crew"
|
||||
settings_block = _render_json_crew_template(
|
||||
"agent_settings.jsonc",
|
||||
{
|
||||
"allow_delegation": "true" if agent["allow_delegation"] else "false",
|
||||
"delegation_comma": "," if has_planning else "",
|
||||
"planning_line": '"planning": true'
|
||||
if has_planning
|
||||
else '// "planning": false',
|
||||
},
|
||||
)
|
||||
settings_lines.append(f' "allow_delegation": {delegation_val}{delegation_comma}')
|
||||
settings_lines.append("")
|
||||
settings_lines.append(
|
||||
" // Maximum reasoning iterations per task (prevents infinite loops)"
|
||||
|
||||
return _render_json_crew_template(
|
||||
"agent.jsonc",
|
||||
{
|
||||
"role_json": json.dumps(agent["role"]),
|
||||
"goal_json": json.dumps(agent["goal"]),
|
||||
"backstory_json": json.dumps(agent["backstory"]),
|
||||
"llm_json": json.dumps(agent["llm"]),
|
||||
"tools_json": json.dumps(agent["tools"]),
|
||||
"settings_block": settings_block,
|
||||
},
|
||||
)
|
||||
settings_lines.append(' // "max_iter": 25,')
|
||||
settings_lines.append("")
|
||||
settings_lines.append(" // Maximum tokens for agent's response generation")
|
||||
settings_lines.append(' // "max_tokens": null,')
|
||||
settings_lines.append("")
|
||||
settings_lines.append(" // Maximum execution time in seconds")
|
||||
settings_lines.append(' // "max_execution_time": null,')
|
||||
settings_lines.append("")
|
||||
settings_lines.append(" // Maximum LLM requests per minute (rate limiting)")
|
||||
settings_lines.append(' // "max_rpm": null,')
|
||||
settings_lines.append("")
|
||||
settings_lines.append(" // Enable agent-level memory (persists across tasks)")
|
||||
settings_lines.append(' // "memory": false,')
|
||||
settings_lines.append("")
|
||||
settings_lines.append(" // Cache tool results to avoid duplicate calls")
|
||||
settings_lines.append(' // "cache": true,')
|
||||
settings_lines.append("")
|
||||
settings_lines.append(
|
||||
" // Auto-summarize context when it exceeds the LLM's context window"
|
||||
)
|
||||
settings_lines.append(' // "respect_context_window": true,')
|
||||
settings_lines.append("")
|
||||
settings_lines.append(" // Maximum retries on execution errors")
|
||||
settings_lines.append(' // "max_retry_limit": 2,')
|
||||
settings_lines.append("")
|
||||
settings_lines.append(" // Enable step-by-step planning before task execution")
|
||||
if has_planning:
|
||||
settings_lines.append(' "planning": true')
|
||||
else:
|
||||
settings_lines.append(' // "planning": false')
|
||||
settings_lines.append("")
|
||||
settings_lines.append(" // Include system prompt in LLM calls")
|
||||
settings_lines.append(' // "use_system_prompt": true')
|
||||
|
||||
settings_block = "\n".join(settings_lines)
|
||||
|
||||
return f"""\
|
||||
{{
|
||||
// Agent's role title — appears in prompts and logs.
|
||||
// You can use {{placeholder}} inputs in role, goal, or backstory.
|
||||
// Example: "role": "Senior {{industry}} Researcher"
|
||||
"role": {json.dumps(agent["role"])},
|
||||
|
||||
// Optional custom Agent subclass
|
||||
// "type": {{"python": "my_project.agents.CustomAgent"}},
|
||||
|
||||
// The agent's primary objective
|
||||
"goal": {json.dumps(agent["goal"])},
|
||||
|
||||
// Background story that shapes the agent's personality and approach
|
||||
"backstory": {json.dumps(agent["backstory"])},
|
||||
|
||||
// LLM model in provider/model format
|
||||
// Examples: "openai/gpt-4o", "anthropic/claude-sonnet-4-6", "ollama/llama3.3"
|
||||
// For custom endpoints or deployment-based providers, replace with:
|
||||
// "llm": {{"model": "llama3", "provider": "ollama", "base_url": "http://localhost:11434"}},
|
||||
// "llm": {{"deployment_name": "my-deployment", "provider": "azure", "api_version": "2024-10-21"}},
|
||||
"llm": {json.dumps(agent["llm"])},
|
||||
|
||||
// Override LLM used specifically for tool/function calling
|
||||
// "function_calling_llm": "openai/gpt-5.4-mini",
|
||||
|
||||
// Tools available to this agent
|
||||
// Built-in: "SerperDevTool", "ScrapeWebsiteTool", "FileReadTool", etc.
|
||||
// Custom: "custom:my_tool" loads from tools/my_tool.py
|
||||
"tools": {json.dumps(agent["tools"])},
|
||||
|
||||
// Optional agent-level guardrail — validates this agent's final output.
|
||||
// String guardrails are checked by an LLM and can reject/retry output.
|
||||
// Python refs must point to module-level functions/classes in trusted code.
|
||||
// "guardrail": "Only answer with information supported by retrieved evidence.",
|
||||
// "step_callback": {{"python": "my_project.callbacks.on_agent_step"}},
|
||||
// "guardrail_max_retries": 2,
|
||||
|
||||
// Advanced agent options:
|
||||
// Docs: https://docs.crewai.com/concepts/agents
|
||||
// "reasoning": true,
|
||||
// "max_reasoning_attempts": 3,
|
||||
// "planning_config": {{
|
||||
// "reasoning_effort": "medium",
|
||||
// "llm": {{"model": "deepseek-chat", "provider": "deepseek"}}
|
||||
// }},
|
||||
// "multimodal": false,
|
||||
// "allow_code_execution": false,
|
||||
// "code_execution_mode": "safe",
|
||||
// "knowledge_sources": [],
|
||||
// "knowledge_config": {{}},
|
||||
// "inject_date": true,
|
||||
// "date_format": "%Y-%m-%d",
|
||||
// "security_config": {{}},
|
||||
|
||||
// Agent behavior settings
|
||||
"settings": {{
|
||||
{settings_block}
|
||||
}}
|
||||
}}
|
||||
"""
|
||||
|
||||
|
||||
def _task_to_json_fragment(task: dict[str, Any]) -> str:
|
||||
"""Convert task wizard data to a JSON-like fragment for embedding in crew JSONC."""
|
||||
lines = []
|
||||
lines.append(" {")
|
||||
lines.append(" // Task identifier")
|
||||
lines.append(f' "name": {json.dumps(task["name"])},')
|
||||
lines.append("")
|
||||
lines.append(" // What the task should accomplish")
|
||||
lines.append(
|
||||
" // Use {placeholder} inputs here; crewai run prompts for missing values"
|
||||
)
|
||||
lines.append(f' "description": {json.dumps(task["description"])},')
|
||||
lines.append("")
|
||||
lines.append(" // Clear definition of what the output should look like")
|
||||
lines.append(f' "expected_output": {json.dumps(task["expected_output"])},')
|
||||
lines.append("")
|
||||
lines.append(
|
||||
" // Optional task guardrail(s) validate output before completion"
|
||||
)
|
||||
lines.append(' // Use "guardrail" for one rule or "guardrails" for many')
|
||||
lines.append(" // Failed guardrails retry up to guardrail_max_retries times")
|
||||
lines.append(' // "guardrail": "Every factual claim needs context support.",')
|
||||
lines.append(' // "guardrails": [')
|
||||
lines.append(' // "Every factual claim must be supported by context.",')
|
||||
lines.append(' // "The answer must match the expected output format."')
|
||||
lines.append(" // ],")
|
||||
lines.append(' // "guardrail_max_retries": 2,')
|
||||
lines.append("")
|
||||
lines.append(" // Advanced task options:")
|
||||
lines.append(" // Docs: https://docs.crewai.com/concepts/tasks")
|
||||
lines.append(' // "type": "ConditionalTask",')
|
||||
lines.append(
|
||||
' // "condition": { "python": "my_project.conditions.should_run" },'
|
||||
)
|
||||
lines.append(
|
||||
' // "output_json": { "python": "my_project.models.ReportOutput" },'
|
||||
)
|
||||
lines.append(' // "output_pydantic": null,')
|
||||
lines.append(' // "response_model": null,')
|
||||
lines.append(
|
||||
' // "converter_cls": { "python": "my_project.converters.CustomConverter" },'
|
||||
)
|
||||
lines.append(' // "markdown": false,')
|
||||
lines.append(' // "input_files": { "brief": "data/brief.txt" },')
|
||||
lines.append(' // "security_config": {},')
|
||||
lines.append("")
|
||||
lines.append(" // Which agent handles this task")
|
||||
lines.append(f' "agent": {json.dumps(task["agent"])}')
|
||||
has_context = bool(task.get("context"))
|
||||
has_output_file = bool(task.get("output_file"))
|
||||
context_block = ""
|
||||
output_file_block = ""
|
||||
|
||||
if task.get("context"):
|
||||
lines[-1] += "," # add comma to agent line
|
||||
lines.append("")
|
||||
lines.append(" // Task outputs used as context")
|
||||
lines.append(f' "context": {json.dumps(task["context"])}')
|
||||
if has_context:
|
||||
context_block = (
|
||||
"\n\n"
|
||||
" // Task outputs used as context\n"
|
||||
f' "context": {json.dumps(task["context"])}'
|
||||
f"{',' if has_output_file else ''}"
|
||||
)
|
||||
|
||||
if task.get("output_file"):
|
||||
lines[-1] += ","
|
||||
lines.append("")
|
||||
lines.append(" // Save output to a file")
|
||||
lines.append(f' "output_file": {json.dumps(task["output_file"])}')
|
||||
if has_output_file:
|
||||
output_file_block = (
|
||||
"\n\n"
|
||||
" // Save output to a file\n"
|
||||
f' "output_file": {json.dumps(task["output_file"])}'
|
||||
)
|
||||
|
||||
lines.append("")
|
||||
lines.append(' // "tools": [],')
|
||||
lines.append(' // "human_input": false,')
|
||||
lines.append(' // "async_execution": false')
|
||||
lines.append(" }")
|
||||
return "\n".join(lines)
|
||||
return _render_json_crew_template(
|
||||
"task.jsonc",
|
||||
{
|
||||
"name_json": json.dumps(task["name"]),
|
||||
"description_json": json.dumps(task["description"]),
|
||||
"expected_output_json": json.dumps(task["expected_output"]),
|
||||
"agent_json": json.dumps(task["agent"]),
|
||||
"agent_comma": "," if has_context or has_output_file else "",
|
||||
"context_block": context_block,
|
||||
"output_file_block": output_file_block,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def _crew_to_jsonc(
|
||||
@@ -894,69 +718,20 @@ def _crew_to_jsonc(
|
||||
inputs_lines[0] + "\n" + "\n".join(" " + line for line in inputs_lines[1:])
|
||||
)
|
||||
|
||||
process = settings.get("process", "sequential")
|
||||
memory = "true" if settings.get("memory") else "false"
|
||||
|
||||
return f"""\
|
||||
{{
|
||||
// Display name for this crew
|
||||
"name": {json.dumps(name)},
|
||||
|
||||
// Agents to include — each must have a matching agents/<name>.jsonc file
|
||||
"agents": {agent_names_json},
|
||||
|
||||
// Task definitions — executed in order for sequential process
|
||||
"tasks": [
|
||||
{tasks_fragments}
|
||||
],
|
||||
|
||||
// Execution process
|
||||
// "sequential" — tasks run in order, each receiving prior task outputs
|
||||
// "hierarchical" — a manager agent delegates tasks (requires manager_llm)
|
||||
"process": "{process}",
|
||||
|
||||
// Enable verbose logging during execution
|
||||
"verbose": true,
|
||||
|
||||
// Enable crew memory — persists context and learnings across tasks
|
||||
"memory": {memory},
|
||||
|
||||
// Automatically plan the execution strategy before running tasks
|
||||
// "planning": false,
|
||||
|
||||
// LLM for the planning step (used when planning is true)
|
||||
// "planning_llm": "openai/gpt-4o",
|
||||
|
||||
// LLM for the manager agent (required when process is "hierarchical")
|
||||
// "manager_llm": "openai/gpt-4o",
|
||||
|
||||
// Crew-level LLM fields also accept object form for custom endpoints
|
||||
// "chat_llm": {{"model": "llama3", "provider": "ollama", "base_url": "http://localhost:11434"}},
|
||||
|
||||
// Advanced crew options:
|
||||
// Docs: https://docs.crewai.com/concepts/crews
|
||||
// For hierarchical crews, manager_agent can reference an agents/<name>.jsonc file
|
||||
// that is not included in the "agents" list.
|
||||
// "manager_agent": "{agents[0]["name"]}",
|
||||
// "before_kickoff_callbacks": [{{"python": "my_project.callbacks.before_kickoff"}}],
|
||||
// "after_kickoff_callbacks": [{{"python": "my_project.callbacks.after_kickoff"}}],
|
||||
// "function_calling_llm": "openai/gpt-4o-mini",
|
||||
// "max_rpm": null,
|
||||
// "cache": true,
|
||||
// "knowledge_sources": [],
|
||||
// "embedder": {{}},
|
||||
// "output_log_file": "crew.log",
|
||||
// "stream": false,
|
||||
// "tracing": false,
|
||||
// "security_config": {{}},
|
||||
|
||||
// Optional runtime input defaults.
|
||||
// Use {{placeholder}} in agent or task text, for example:
|
||||
// "description": "Research {{topic}} and write a brief"
|
||||
// `crewai run` prompts for any placeholders missing from this object.
|
||||
"inputs": {inputs_json}
|
||||
}}
|
||||
"""
|
||||
return _render_json_crew_template(
|
||||
"crew.jsonc",
|
||||
{
|
||||
"name_json": json.dumps(name),
|
||||
"agent_names_json": agent_names_json,
|
||||
"tasks_fragments": tasks_fragments,
|
||||
"process_json": json.dumps(settings.get("process", "sequential")),
|
||||
"memory": memory,
|
||||
"manager_agent_name": agents[0]["name"],
|
||||
"inputs_json": inputs_json,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
# ── Model selection ─────────────────────────────────────────────
|
||||
@@ -1031,6 +806,12 @@ def _default_model_for_provider(provider: str | None) -> str | None:
|
||||
# ── Helpers ─────────────────────────────────────────────────────
|
||||
|
||||
|
||||
def _render_json_crew_template(
|
||||
template_name: str, replacements: dict[str, str] | None = None
|
||||
) -> str:
|
||||
return render_template(_TEMPLATES_DIR / template_name, replacements or {})
|
||||
|
||||
|
||||
def _write_jsonc(path: Path, content: str) -> None:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(content, encoding="utf-8")
|
||||
@@ -1136,26 +917,32 @@ def create_json_crew(
|
||||
|
||||
# Write pyproject.toml
|
||||
(folder_path / "pyproject.toml").write_text(
|
||||
_PYPROJECT_TOML.format(
|
||||
folder_name=folder_name,
|
||||
name=name,
|
||||
crewai_tools_dependency=get_crewai_tools_dependency(),
|
||||
_render_json_crew_template(
|
||||
"pyproject.toml",
|
||||
{
|
||||
"folder_name": folder_name,
|
||||
"name": name,
|
||||
"crewai_tools_dependency": get_crewai_tools_dependency(),
|
||||
},
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
# Write .gitignore
|
||||
(folder_path / ".gitignore").write_text(_GITIGNORE, encoding="utf-8")
|
||||
(folder_path / ".gitignore").write_text(
|
||||
_render_json_crew_template(".gitignore"),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
# Write README
|
||||
(folder_path / "README.md").write_text(
|
||||
_README.format(name=name),
|
||||
_render_json_crew_template("README.md", {"name": name}),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
# Write knowledge placeholder
|
||||
(folder_path / "knowledge" / "user_preference.txt").write_text(
|
||||
"# Add your knowledge files here\n",
|
||||
_render_json_crew_template("knowledge/user_preference.txt"),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
|
||||
@@ -10,12 +10,6 @@ from typing import TYPE_CHECKING, Any
|
||||
|
||||
import click
|
||||
from crewai_core.constants import CREWAI_TRAINED_AGENTS_FILE_ENV
|
||||
from crewai_core.project import (
|
||||
ProjectDefinitionError,
|
||||
configured_project_definition,
|
||||
get_crewai_project_type,
|
||||
read_toml,
|
||||
)
|
||||
from packaging import version
|
||||
|
||||
from crewai_cli.utils import (
|
||||
@@ -102,11 +96,28 @@ def _full_crewai_install_error() -> click.ClickException:
|
||||
return click.ClickException(_FULL_CREWAI_INSTALL_MESSAGE)
|
||||
|
||||
|
||||
def read_toml(*args: Any, **kwargs: Any) -> dict[str, Any]:
|
||||
from crewai_core.project import read_toml as _read_toml
|
||||
|
||||
return _read_toml(*args, **kwargs)
|
||||
|
||||
|
||||
def get_crewai_project_type(pyproject_data: dict[str, Any]) -> str | None:
|
||||
from crewai_core.project import get_crewai_project_type as _get_crewai_project_type
|
||||
|
||||
return _get_crewai_project_type(pyproject_data)
|
||||
|
||||
|
||||
def configured_project_json_crew(
|
||||
pyproject_data: dict[str, Any] | None = None,
|
||||
project_root: Path | None = None,
|
||||
) -> Path | None:
|
||||
"""Return the configured JSON crew definition for crew projects."""
|
||||
from crewai_core.project import (
|
||||
ProjectDefinitionError,
|
||||
configured_project_definition,
|
||||
)
|
||||
|
||||
root = project_root or Path.cwd()
|
||||
if pyproject_data is None and not (root / "pyproject.toml").is_file():
|
||||
return None
|
||||
|
||||
4
lib/cli/src/crewai_cli/templates/json_crew/.gitignore
vendored
Normal file
4
lib/cli/src/crewai_cli/templates/json_crew/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
.env
|
||||
__pycache__/
|
||||
.DS_Store
|
||||
report.md
|
||||
20
lib/cli/src/crewai_cli/templates/json_crew/README.md
Normal file
20
lib/cli/src/crewai_cli/templates/json_crew/README.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# {{name}}
|
||||
|
||||
A crewAI project using JSON-first configuration.
|
||||
|
||||
## Running
|
||||
|
||||
```bash
|
||||
crewai run
|
||||
```
|
||||
|
||||
## Project Structure
|
||||
|
||||
- `agents/` - Agent definitions (JSONC)
|
||||
- `crew.jsonc` - Crew definition with tasks and configuration
|
||||
- `tools/` - Custom tools (Python)
|
||||
- `knowledge/` - Knowledge files for agents
|
||||
|
||||
> **Note:** `custom:<name>` tool references execute `tools/<name>.py` as local
|
||||
> Python code when the crew loads. Only run crew projects from sources you
|
||||
> trust.
|
||||
59
lib/cli/src/crewai_cli/templates/json_crew/agent.jsonc
Normal file
59
lib/cli/src/crewai_cli/templates/json_crew/agent.jsonc
Normal file
@@ -0,0 +1,59 @@
|
||||
{
|
||||
// Agent's role title — appears in prompts and logs.
|
||||
// You can use {placeholder} inputs in role, goal, or backstory.
|
||||
// Example: "role": "Senior {industry} Researcher"
|
||||
"role": {{role_json}},
|
||||
|
||||
// Optional custom Agent subclass
|
||||
// "type": {"python": "my_project.agents.CustomAgent"},
|
||||
|
||||
// The agent's primary objective
|
||||
"goal": {{goal_json}},
|
||||
|
||||
// Background story that shapes the agent's personality and approach
|
||||
"backstory": {{backstory_json}},
|
||||
|
||||
// LLM model in provider/model format
|
||||
// Examples: "openai/gpt-4o", "anthropic/claude-sonnet-4-6", "ollama/llama3.3"
|
||||
// For custom endpoints or deployment-based providers, replace with:
|
||||
// "llm": {"model": "llama3", "provider": "ollama", "base_url": "http://localhost:11434"},
|
||||
// "llm": {"deployment_name": "my-deployment", "provider": "azure", "api_version": "2024-10-21"},
|
||||
"llm": {{llm_json}},
|
||||
|
||||
// Override LLM used specifically for tool/function calling
|
||||
// "function_calling_llm": "openai/gpt-5.4-mini",
|
||||
|
||||
// Tools available to this agent
|
||||
// Built-in: "SerperDevTool", "ScrapeWebsiteTool", "FileReadTool", etc.
|
||||
// Custom: "custom:my_tool" loads from tools/my_tool.py
|
||||
"tools": {{tools_json}},
|
||||
|
||||
// Optional agent-level guardrail — validates this agent's final output.
|
||||
// String guardrails are checked by an LLM and can reject/retry output.
|
||||
// Python refs must point to module-level functions/classes in trusted code.
|
||||
// "guardrail": "Only answer with information supported by retrieved evidence.",
|
||||
// "step_callback": {"python": "my_project.callbacks.on_agent_step"},
|
||||
// "guardrail_max_retries": 2,
|
||||
|
||||
// Advanced agent options:
|
||||
// Docs: https://docs.crewai.com/concepts/agents
|
||||
// "reasoning": true,
|
||||
// "max_reasoning_attempts": 3,
|
||||
// "planning_config": {
|
||||
// "reasoning_effort": "medium",
|
||||
// "llm": {"model": "deepseek-chat", "provider": "deepseek"}
|
||||
// },
|
||||
// "multimodal": false,
|
||||
// "allow_code_execution": false,
|
||||
// "code_execution_mode": "safe",
|
||||
// "knowledge_sources": [],
|
||||
// "knowledge_config": {},
|
||||
// "inject_date": true,
|
||||
// "date_format": "%Y-%m-%d",
|
||||
// "security_config": {},
|
||||
|
||||
// Agent behavior settings
|
||||
"settings": {
|
||||
{{settings_block}}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// Show detailed execution logs
|
||||
"verbose": false,
|
||||
|
||||
// Allow this agent to delegate tasks to other agents in the crew
|
||||
"allow_delegation": {{allow_delegation}}{{delegation_comma}}
|
||||
|
||||
// Maximum reasoning iterations per task (prevents infinite loops)
|
||||
// "max_iter": 25,
|
||||
|
||||
// Maximum tokens for agent's response generation
|
||||
// "max_tokens": null,
|
||||
|
||||
// Maximum execution time in seconds
|
||||
// "max_execution_time": null,
|
||||
|
||||
// Maximum LLM requests per minute (rate limiting)
|
||||
// "max_rpm": null,
|
||||
|
||||
// Enable agent-level memory (persists across tasks)
|
||||
// "memory": false,
|
||||
|
||||
// Cache tool results to avoid duplicate calls
|
||||
// "cache": true,
|
||||
|
||||
// Auto-summarize context when it exceeds the LLM's context window
|
||||
// "respect_context_window": true,
|
||||
|
||||
// Maximum retries on execution errors
|
||||
// "max_retry_limit": 2,
|
||||
|
||||
// Enable step-by-step planning before task execution
|
||||
{{planning_line}}
|
||||
|
||||
// Include system prompt in LLM calls
|
||||
// "use_system_prompt": true
|
||||
58
lib/cli/src/crewai_cli/templates/json_crew/crew.jsonc
Normal file
58
lib/cli/src/crewai_cli/templates/json_crew/crew.jsonc
Normal file
@@ -0,0 +1,58 @@
|
||||
{
|
||||
// Display name for this crew
|
||||
"name": {{name_json}},
|
||||
|
||||
// Agents to include — each must have a matching agents/<name>.jsonc file
|
||||
"agents": {{agent_names_json}},
|
||||
|
||||
// Task definitions — executed in order for sequential process
|
||||
"tasks": [
|
||||
{{tasks_fragments}}
|
||||
],
|
||||
|
||||
// Execution process
|
||||
// "sequential" — tasks run in order, each receiving prior task outputs
|
||||
// "hierarchical" — a manager agent delegates tasks (requires manager_llm)
|
||||
"process": {{process_json}},
|
||||
|
||||
// Enable verbose logging during execution
|
||||
"verbose": true,
|
||||
|
||||
// Enable crew memory — persists context and learnings across tasks
|
||||
"memory": {{memory}},
|
||||
|
||||
// Automatically plan the execution strategy before running tasks
|
||||
// "planning": false,
|
||||
|
||||
// LLM for the planning step (used when planning is true)
|
||||
// "planning_llm": "openai/gpt-4o",
|
||||
|
||||
// LLM for the manager agent (required when process is "hierarchical")
|
||||
// "manager_llm": "openai/gpt-4o",
|
||||
|
||||
// Crew-level LLM fields also accept object form for custom endpoints
|
||||
// "chat_llm": {"model": "llama3", "provider": "ollama", "base_url": "http://localhost:11434"},
|
||||
|
||||
// Advanced crew options:
|
||||
// Docs: https://docs.crewai.com/concepts/crews
|
||||
// For hierarchical crews, manager_agent can reference an agents/<name>.jsonc file
|
||||
// that is not included in the "agents" list.
|
||||
// "manager_agent": "{{manager_agent_name}}",
|
||||
// "before_kickoff_callbacks": [{"python": "my_project.callbacks.before_kickoff"}],
|
||||
// "after_kickoff_callbacks": [{"python": "my_project.callbacks.after_kickoff"}],
|
||||
// "function_calling_llm": "openai/gpt-4o-mini",
|
||||
// "max_rpm": null,
|
||||
// "cache": true,
|
||||
// "knowledge_sources": [],
|
||||
// "embedder": {},
|
||||
// "output_log_file": "crew.log",
|
||||
// "stream": false,
|
||||
// "tracing": false,
|
||||
// "security_config": {},
|
||||
|
||||
// Optional runtime input defaults.
|
||||
// Use {placeholder} in agent or task text, for example:
|
||||
// "description": "Research {topic} and write a brief"
|
||||
// `crewai run` prompts for any placeholders missing from this object.
|
||||
"inputs": {{inputs_json}}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
# Add your knowledge files here
|
||||
20
lib/cli/src/crewai_cli/templates/json_crew/pyproject.toml
Normal file
20
lib/cli/src/crewai_cli/templates/json_crew/pyproject.toml
Normal file
@@ -0,0 +1,20 @@
|
||||
[project]
|
||||
name = "{{folder_name}}"
|
||||
version = "0.1.0"
|
||||
description = "{{name}} using crewAI"
|
||||
authors = [{ name = "Your Name", email = "you@example.com" }]
|
||||
requires-python = ">=3.10,<3.14"
|
||||
dependencies = [
|
||||
"{{crewai_tools_dependency}}"
|
||||
]
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
only-include = ["agents", "crew.jsonc", "tools", "knowledge", "skills"]
|
||||
|
||||
[tool.crewai]
|
||||
type = "crew"
|
||||
definition = "crew.jsonc"
|
||||
40
lib/cli/src/crewai_cli/templates/json_crew/task.jsonc
Normal file
40
lib/cli/src/crewai_cli/templates/json_crew/task.jsonc
Normal file
@@ -0,0 +1,40 @@
|
||||
{
|
||||
// Task identifier
|
||||
"name": {{name_json}},
|
||||
|
||||
// What the task should accomplish
|
||||
// Use {placeholder} inputs here; crewai run prompts for missing values
|
||||
"description": {{description_json}},
|
||||
|
||||
// Clear definition of what the output should look like
|
||||
"expected_output": {{expected_output_json}},
|
||||
|
||||
// Optional task guardrail(s) validate output before completion
|
||||
// Use "guardrail" for one rule or "guardrails" for many
|
||||
// Failed guardrails retry up to guardrail_max_retries times
|
||||
// "guardrail": "Every factual claim needs context support.",
|
||||
// "guardrails": [
|
||||
// "Every factual claim must be supported by context.",
|
||||
// "The answer must match the expected output format."
|
||||
// ],
|
||||
// "guardrail_max_retries": 2,
|
||||
|
||||
// Advanced task options:
|
||||
// Docs: https://docs.crewai.com/concepts/tasks
|
||||
// "type": "ConditionalTask",
|
||||
// "condition": { "python": "my_project.conditions.should_run" },
|
||||
// "output_json": { "python": "my_project.models.ReportOutput" },
|
||||
// "output_pydantic": null,
|
||||
// "response_model": null,
|
||||
// "converter_cls": { "python": "my_project.converters.CustomConverter" },
|
||||
// "markdown": false,
|
||||
// "input_files": { "brief": "data/brief.txt" },
|
||||
// "security_config": {},
|
||||
|
||||
// Which agent handles this task
|
||||
"agent": {{agent_json}}{{agent_comma}}{{context_block}}{{output_file_block}}
|
||||
|
||||
// "tools": [],
|
||||
// "human_input": false,
|
||||
// "async_execution": false
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Mapping
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import shutil
|
||||
from typing import Any
|
||||
|
||||
@@ -35,6 +37,7 @@ __all__ = [
|
||||
"load_env_vars",
|
||||
"parse_toml",
|
||||
"read_toml",
|
||||
"render_template",
|
||||
"tree_copy",
|
||||
"tree_find_and_replace",
|
||||
"write_env_file",
|
||||
@@ -42,6 +45,7 @@ __all__ = [
|
||||
|
||||
|
||||
console = Console()
|
||||
_TEMPLATE_TOKEN_RE = re.compile(r"{{([a-zA-Z_][a-zA-Z0-9_]*)}}")
|
||||
|
||||
|
||||
def is_dmn_mode_enabled() -> bool:
|
||||
@@ -69,14 +73,14 @@ def copy_template(
|
||||
src: Path, dst: Path, name: str, class_name: str, folder_name: str
|
||||
) -> None:
|
||||
"""Copy a file from src to dst."""
|
||||
with open(src, "r") as file:
|
||||
content = file.read()
|
||||
|
||||
content = content.replace("{{name}}", name)
|
||||
content = content.replace("{{crew_name}}", class_name)
|
||||
content = content.replace("{{folder_name}}", folder_name)
|
||||
content = content.replace(
|
||||
"{{crewai_tools_dependency}}", get_crewai_tools_dependency()
|
||||
content = render_template(
|
||||
src,
|
||||
{
|
||||
"name": name,
|
||||
"crew_name": class_name,
|
||||
"folder_name": folder_name,
|
||||
"crewai_tools_dependency": get_crewai_tools_dependency(),
|
||||
},
|
||||
)
|
||||
|
||||
with open(dst, "w") as file:
|
||||
@@ -85,6 +89,15 @@ def copy_template(
|
||||
click.secho(f" - Created {dst}", fg="green")
|
||||
|
||||
|
||||
def render_template(src: Path, replacements: Mapping[str, str]) -> str:
|
||||
"""Render a template file using ``{{placeholder}}`` replacements."""
|
||||
content = src.read_text(encoding="utf-8")
|
||||
return _TEMPLATE_TOKEN_RE.sub(
|
||||
lambda match: replacements.get(match.group(1), match.group(0)),
|
||||
content,
|
||||
)
|
||||
|
||||
|
||||
def fetch_and_json_env_file(env_file_path: str = ".env") -> dict[str, Any]:
|
||||
"""Fetch the environment variables from a .env file and return them as a dictionary."""
|
||||
try:
|
||||
|
||||
@@ -12,6 +12,8 @@ from packaging.version import Version
|
||||
import crewai_cli.create_json_crew as json_crew
|
||||
import crewai_cli.tui_picker as tui_picker
|
||||
from crewai_cli.create_crew import create_crew, create_folder_structure
|
||||
from crewai_cli.utils import render_template
|
||||
from crewai_cli.version import get_crewai_tools_dependency
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -735,7 +737,7 @@ def test_json_create_provider_preselects_default_model(tmp_path, monkeypatch):
|
||||
|
||||
pyproject = tomli.loads((tmp_path / "json_crew" / "pyproject.toml").read_text())
|
||||
dependency = pyproject["project"]["dependencies"][0]
|
||||
assert dependency == "crewai[tools]>=1.15.0,<2.0.0"
|
||||
assert dependency == get_crewai_tools_dependency()
|
||||
assert Version("1.15.0") in Requirement(dependency).specifier
|
||||
assert Version("2.0.0") not in Requirement(dependency).specifier
|
||||
assert pyproject["tool"]["hatch"]["build"]["targets"]["wheel"][
|
||||
@@ -816,6 +818,37 @@ def test_json_create_provider_preselects_default_model(tmp_path, monkeypatch):
|
||||
assert '"knowledge_sources": []' in agent_template
|
||||
|
||||
|
||||
def test_json_crew_uses_template_files():
|
||||
template_names = {
|
||||
"pyproject.toml",
|
||||
"README.md",
|
||||
".gitignore",
|
||||
"agent.jsonc",
|
||||
"agent_settings.jsonc",
|
||||
"task.jsonc",
|
||||
"crew.jsonc",
|
||||
"knowledge/user_preference.txt",
|
||||
}
|
||||
|
||||
for template_name in template_names:
|
||||
assert (json_crew._TEMPLATES_DIR / template_name).is_file()
|
||||
|
||||
|
||||
def test_render_template_does_not_replace_tokens_inside_replacement_values(tmp_path):
|
||||
template = tmp_path / "template.txt"
|
||||
template.write_text("{{first}} {{second}}", encoding="utf-8")
|
||||
|
||||
rendered = render_template(
|
||||
template,
|
||||
{
|
||||
"first": "{{second}}",
|
||||
"second": "done",
|
||||
},
|
||||
)
|
||||
|
||||
assert rendered == "{{second}} done"
|
||||
|
||||
|
||||
def test_json_provider_default_model_helper():
|
||||
assert json_crew._default_model_for_provider("openai") == "openai/gpt-5.5"
|
||||
assert json_crew._default_model_for_provider("anthropic/claude-custom") == (
|
||||
|
||||
@@ -227,6 +227,80 @@ def test_json_runner_code_loads_current_cli_package_over_project_env(tmp_path: P
|
||||
assert marker.read_text() == "current:trained.pkl"
|
||||
|
||||
|
||||
def test_json_runner_imports_with_older_project_env_crewai_core(tmp_path: Path):
|
||||
old_parent = tmp_path / "old_env"
|
||||
old_crewai_core = old_parent / "crewai_core"
|
||||
old_crewai_core.mkdir(parents=True)
|
||||
(old_crewai_core / "__init__.py").write_text("")
|
||||
(old_crewai_core / "constants.py").write_text(
|
||||
"CREWAI_TRAINED_AGENTS_FILE_ENV = 'CREWAI_TRAINED_AGENTS_FILE'\n"
|
||||
)
|
||||
(old_crewai_core / "project.py").write_text(
|
||||
"def read_toml(*args, **kwargs):\n"
|
||||
" return {}\n"
|
||||
"def parse_toml(*args, **kwargs):\n"
|
||||
" return {}\n"
|
||||
"def get_project_description(*args, **kwargs):\n"
|
||||
" return None\n"
|
||||
"def get_project_name(*args, **kwargs):\n"
|
||||
" return None\n"
|
||||
"def get_project_version(*args, **kwargs):\n"
|
||||
" return None\n"
|
||||
)
|
||||
(old_crewai_core / "tool_credentials.py").write_text(
|
||||
"def build_env_with_all_tool_credentials(*args, **kwargs):\n"
|
||||
" return {}\n"
|
||||
"def build_env_with_tool_repository_credentials(*args, **kwargs):\n"
|
||||
" return {}\n"
|
||||
)
|
||||
(old_crewai_core / "version.py").write_text(
|
||||
"def check_version(*args, **kwargs):\n"
|
||||
" return None\n"
|
||||
"def get_crewai_version(*args, **kwargs):\n"
|
||||
" return '1.0.0'\n"
|
||||
"def get_latest_version_from_pypi(*args, **kwargs):\n"
|
||||
" return None\n"
|
||||
"def is_current_version_yanked(*args, **kwargs):\n"
|
||||
" return False\n"
|
||||
"def is_newer_version_available(*args, **kwargs):\n"
|
||||
" return False\n"
|
||||
)
|
||||
|
||||
marker = tmp_path / "marker.txt"
|
||||
old_crewai_project = old_parent / "crewai" / "project"
|
||||
old_crewai_project.mkdir(parents=True)
|
||||
(old_parent / "crewai" / "__init__.py").write_text("")
|
||||
(old_crewai_project / "__init__.py").write_text("")
|
||||
(old_crewai_project / "crew_loader.py").write_text(
|
||||
"from pathlib import Path\n"
|
||||
"class Crew:\n"
|
||||
" agents = []\n"
|
||||
" tasks = []\n"
|
||||
" def kickoff(self, inputs):\n"
|
||||
f" Path({str(marker)!r}).write_text('ran')\n"
|
||||
" return 'done'\n"
|
||||
"def load_crew(path):\n"
|
||||
" return Crew(), {}\n"
|
||||
)
|
||||
|
||||
env = os.environ.copy()
|
||||
env["PYTHONPATH"] = str(old_parent)
|
||||
env["CREWAI_DMN"] = "true"
|
||||
env[run_crew_module._CREWAI_CLI_RUNNER_PACKAGE_DIR_ENV] = str(
|
||||
Path(run_crew_module.__file__).resolve().parent
|
||||
)
|
||||
env[run_crew_module._CREWAI_JSON_CREW_DEFINITION_ENV] = "crew.jsonc"
|
||||
|
||||
subprocess.run(
|
||||
[sys.executable, "-c", run_crew_module._JSON_CREW_RUNNER_CODE],
|
||||
check=True,
|
||||
env=env,
|
||||
cwd=tmp_path,
|
||||
)
|
||||
|
||||
assert marker.read_text() == "ran"
|
||||
|
||||
|
||||
def test_json_run_without_pyproject_runs_in_process(monkeypatch, tmp_path: Path):
|
||||
monkeypatch.chdir(tmp_path)
|
||||
called: dict = {}
|
||||
|
||||
Reference in New Issue
Block a user