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
13 changes: 10 additions & 3 deletions lib/crewai/src/crewai/experimental/agent_executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from datetime import datetime
import inspect
import json
import re
import threading
from typing import TYPE_CHECKING, Any, Literal, TypeVar, cast
from uuid import uuid4
Expand Down Expand Up @@ -3149,16 +3150,22 @@ async def _ainject_files_from_inputs(self, inputs: dict[str, Any]) -> None:
def _format_prompt(prompt: str, inputs: dict[str, str]) -> str:
"""Format prompt template with input values.

Substitutes every placeholder in a single pass so a substituted value
that itself contains a placeholder token (e.g. task input mentioning
the literal ``{tools}``) is not clobbered by a later replacement.

Args:
prompt: Template string.
inputs: Values to substitute.

Returns:
Formatted prompt.
"""
prompt = prompt.replace("{input}", inputs["input"])
prompt = prompt.replace("{tool_names}", inputs["tool_names"])
return prompt.replace("{tools}", inputs["tools"])
return re.sub(
r"\{(input|tool_names|tools)\}",
lambda match: inputs[match.group(1)],
prompt,
)

def _handle_human_feedback(self, formatted_answer: AgentFinish) -> AgentFinish:
"""Process human feedback and refine answer.
Expand Down
23 changes: 23 additions & 0 deletions lib/crewai/tests/agents/test_agent_executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,29 @@ def test_format_prompt(self, mock_dependencies):
assert "tool1, tool2" in result
assert "desc" in result

def test_format_prompt_preserves_placeholder_tokens_in_values(
self, mock_dependencies
):
"""A value containing a placeholder token must not be re-substituted.

Regression: sequential ``str.replace`` substituted ``{input}`` first, so
task input that legitimately mentioned ``{tools}`` or ``{tool_names}``
had those tokens clobbered by the later replacements.
"""
executor = _build_executor(**mock_dependencies)
inputs = {
"input": "explain the {tools} and {tool_names} placeholders",
"tool_names": "search",
"tools": "search: web search",
}

result = executor._format_prompt("Task: {input}\nTools: {tools}", inputs)

assert result == (
"Task: explain the {tools} and {tool_names} placeholders\n"
"Tools: search: web search"
)

def test_is_training_mode_false(self, mock_dependencies):
"""Test training mode detection when not in training."""
executor = _build_executor(**mock_dependencies)
Expand Down