Skip to content
Open
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
16 changes: 11 additions & 5 deletions lib/crewai/src/crewai/crew.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
from hashlib import md5
import json
from pathlib import Path
import re
from typing import (
TYPE_CHECKING,
Annotated,
Expand Down Expand Up @@ -142,7 +141,10 @@ def get_supported_content_types(provider: str, api: str | None = None) -> list[s
signal_end,
signal_error,
)
from crewai.utilities.string_utils import sanitize_tool_name
from crewai.utilities.string_utils import (
TEMPLATE_VARIABLE_PATTERN,
sanitize_tool_name,
)
from crewai.utilities.task_output_storage_handler import TaskOutputStorageHandler
from crewai.utilities.training_handler import CrewTrainingHandler

Expand Down Expand Up @@ -1971,21 +1973,25 @@ def fetch_inputs(self) -> set[str]:
'role', 'goal', and 'backstory'.

Returns a set of all discovered placeholder names.

Only valid template variables are reported — the same pattern used by
``interpolate_only`` to substitute them. Literal braces (e.g. embedded
JSON examples or output schemas) are not interpolated, so they are not
treated as required inputs.
"""
placeholder_pattern = re.compile(r"\{(.+?)}")
required_inputs: set[str] = set()

# Scan tasks for inputs
for task in self.tasks:
# description and expected_output might contain e.g. {topic}, {user_name}
text = f"{task.description or ''} {task.expected_output or ''}"
required_inputs.update(placeholder_pattern.findall(text))
required_inputs.update(TEMPLATE_VARIABLE_PATTERN.findall(text))

# Scan agents for inputs
for agent in self.agents:
# role, goal, backstory might have placeholders like {role_detail}, etc.
text = f"{agent.role or ''} {agent.goal or ''} {agent.backstory or ''}"
required_inputs.update(placeholder_pattern.findall(text))
required_inputs.update(TEMPLATE_VARIABLE_PATTERN.findall(text))

return required_inputs

Expand Down
6 changes: 4 additions & 2 deletions lib/crewai/src/crewai/utilities/string_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
import unicodedata


_VARIABLE_PATTERN: Final[re.Pattern[str]] = re.compile(r"\{([A-Za-z_][A-Za-z0-9_\-]*)}")
TEMPLATE_VARIABLE_PATTERN: Final[re.Pattern[str]] = re.compile(
r"\{([A-Za-z_][A-Za-z0-9_\-]*)}"
)
_QUOTE_PATTERN: Final[re.Pattern[str]] = re.compile(r"[\'\"]+")
_CAMEL_LOWER_UPPER: Final[re.Pattern[str]] = re.compile(r"([a-z])([A-Z])")
_CAMEL_UPPER_LOWER: Final[re.Pattern[str]] = re.compile(r"([A-Z]+)([A-Z][a-z])")
Expand Down Expand Up @@ -134,7 +136,7 @@ def _validate_type(validate_value: Any) -> None:
"Inputs dictionary cannot be empty when interpolating variables"
)

variables = _VARIABLE_PATTERN.findall(input_string)
variables = TEMPLATE_VARIABLE_PATTERN.findall(input_string)
result = input_string

# Check if all variables exist in inputs
Expand Down
27 changes: 27 additions & 0 deletions lib/crewai/tests/test_crew.py
Original file line number Diff line number Diff line change
Expand Up @@ -3966,6 +3966,33 @@ def test_fetch_inputs():
)


def test_fetch_inputs_ignores_literal_braces():
"""fetch_inputs must only report real template variables.

Literal braces (embedded JSON examples, output schemas) and non-identifier
braces are not interpolated by ``interpolate_only``, so they must not be
reported as required inputs. Otherwise ``kickoff`` accepts inputs that are
silently ignored, while ``fetch_inputs`` advertises placeholders that can
never be filled.
"""
agent = Agent(
role="Researcher",
goal="Research on {topic}.",
backstory='Reply as JSON like {"name": "value"}.',
)

task = Task(
description='Analyze {topic} and return {"result": [1, 2, 3]}.',
expected_output="Summary of {topic}. Ignore {123} markers.",
agent=agent,
)

crew = Crew(agents=[agent], tasks=[task])

# Only the genuine identifier placeholder should be reported.
assert crew.fetch_inputs() == {"topic"}


@pytest.mark.vcr()
def test_task_tools_preserve_code_execution_tools():
"""
Expand Down
Loading