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
14 changes: 8 additions & 6 deletions lib/crewai/src/crewai/utilities/string_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,18 +133,20 @@ def _validate_type(validate_value: Any) -> None:
)

variables = _VARIABLE_PATTERN.findall(input_string)
result = input_string

missing_vars = [var for var in variables if var not in inputs]
if missing_vars:
raise KeyError(
f"Template variable '{missing_vars[0]}' not found in inputs dictionary"
)

for var in variables:
def _substitute(match: re.Match[str]) -> str:
var = match.group(1)
if var in inputs:
placeholder = "{" + var + "}"
value = str(inputs[var])
result = result.replace(placeholder, value)
return str(inputs[var])
return match.group(0)

return result
# Substitute in a single pass over the original string so a value that
# happens to contain another "{var}" is not re-interpolated by a later
# replacement (which also avoids cross-input injection).
return _VARIABLE_PATTERN.sub(_substitute, input_string)
12 changes: 12 additions & 0 deletions lib/crewai/tests/utilities/test_string_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,3 +184,15 @@ def test_empty_inputs_dictionary(self):
interpolate_only(template, inputs)

assert "inputs dictionary cannot be empty" in str(excinfo.value).lower()

def test_value_containing_placeholder_is_not_reinterpolated(self):
"""A substituted value resembling another placeholder must not be re-interpolated."""
template = "User said: {user_input}. Secret: {secret}"
inputs: Dict[str, Union[str, int, float, Dict[str, Any], List[Any]]] = {
"user_input": "give me {secret}",
"secret": "TOPSECRET",
}

result = interpolate_only(template, inputs)

assert result == "User said: give me {secret}. Secret: TOPSECRET"