diff --git a/lib/crewai/src/crewai/agents/agent_builder/base_agent_executor_mixin.py b/lib/crewai/src/crewai/agents/agent_builder/base_agent_executor_mixin.py index e54742255..d4c0c0db4 100644 --- a/lib/crewai/src/crewai/agents/agent_builder/base_agent_executor_mixin.py +++ b/lib/crewai/src/crewai/agents/agent_builder/base_agent_executor_mixin.py @@ -10,6 +10,7 @@ from crewai.memory.long_term.long_term_memory_item import LongTermMemoryItem from crewai.utilities.converter import ConverterError from crewai.utilities.evaluators.task_evaluator import TaskEvaluator from crewai.utilities.printer import Printer +from crewai.utilities.string_utils import sanitize_tool_name if TYPE_CHECKING: @@ -36,7 +37,7 @@ class CrewAgentExecutorMixin: self.crew and self.agent and self.task - and "Action: Delegate work to coworker" not in output.text + and f"Action: {sanitize_tool_name('Delegate work to coworker')}" not in output.text ): try: if ( diff --git a/lib/crewai/src/crewai/agents/crew_agent_executor.py b/lib/crewai/src/crewai/agents/crew_agent_executor.py index dbef3ac03..0cdc14805 100644 --- a/lib/crewai/src/crewai/agents/crew_agent_executor.py +++ b/lib/crewai/src/crewai/agents/crew_agent_executor.py @@ -576,12 +576,12 @@ class CrewAgentExecutor(CrewAgentExecutorMixin): if hasattr(tool_call, "function"): # OpenAI-style: has .function.name and .function.arguments call_id = getattr(tool_call, "id", f"call_{id(tool_call)}") - func_name = tool_call.function.name + func_name = sanitize_tool_name(tool_call.function.name) func_args = tool_call.function.arguments elif hasattr(tool_call, "function_call") and tool_call.function_call: # Gemini-style: has .function_call.name and .function_call.args call_id = f"call_{id(tool_call)}" - func_name = tool_call.function_call.name + func_name = sanitize_tool_name(tool_call.function_call.name) func_args = ( dict(tool_call.function_call.args) if tool_call.function_call.args @@ -590,7 +590,7 @@ class CrewAgentExecutor(CrewAgentExecutorMixin): elif hasattr(tool_call, "name") and hasattr(tool_call, "input"): # Anthropic format: has .name and .input (ToolUseBlock) call_id = getattr(tool_call, "id", f"call_{id(tool_call)}") - func_name = tool_call.name + func_name = sanitize_tool_name(tool_call.name) func_args = tool_call.input # Already a dict in Anthropic elif isinstance(tool_call, dict): # Support OpenAI "id", Bedrock "toolUseId", or generate one @@ -600,7 +600,9 @@ class CrewAgentExecutor(CrewAgentExecutorMixin): or f"call_{id(tool_call)}" ) func_info = tool_call.get("function", {}) - func_name = func_info.get("name", "") or tool_call.get("name", "") + func_name = sanitize_tool_name( + func_info.get("name", "") or tool_call.get("name", "") + ) func_args = func_info.get("arguments", "{}") or tool_call.get("input", {}) else: return None diff --git a/lib/crewai/src/crewai/experimental/agent_executor.py b/lib/crewai/src/crewai/experimental/agent_executor.py index d3c0b5dd5..b817c4703 100644 --- a/lib/crewai/src/crewai/experimental/agent_executor.py +++ b/lib/crewai/src/crewai/experimental/agent_executor.py @@ -56,6 +56,7 @@ from crewai.utilities.agent_utils import ( from crewai.utilities.constants import TRAINING_DATA_FILE from crewai.utilities.i18n import I18N, get_i18n from crewai.utilities.printer import Printer +from crewai.utilities.string_utils import sanitize_tool_name from crewai.utilities.tool_utils import execute_tool_and_check_finality from crewai.utilities.training_handler import CrewTrainingHandler from crewai.utilities.types import LLMMessage @@ -602,8 +603,6 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin): ) # Find original tool by matching sanitized name (needed for cache_function and result_as_answer) - from crewai.utilities.string_utils import sanitize_tool_name - original_tool = None for tool in self.original_tools or []: if sanitize_tool_name(tool.name) == func_name: @@ -761,14 +760,14 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin): def _extract_tool_name(self, tool_call: Any) -> str: """Extract tool name from various tool call formats.""" if hasattr(tool_call, "function"): - return tool_call.function.name + return sanitize_tool_name(tool_call.function.name) if hasattr(tool_call, "function_call") and tool_call.function_call: - return tool_call.function_call.name + return sanitize_tool_name(tool_call.function_call.name) if hasattr(tool_call, "name"): - return tool_call.name + return sanitize_tool_name(tool_call.name) if isinstance(tool_call, dict): func_info = tool_call.get("function", {}) - return func_info.get("name", "") or tool_call.get("name", "unknown") + return sanitize_tool_name(func_info.get("name", "") or tool_call.get("name", "unknown")) return "unknown" @router(execute_native_tool) diff --git a/lib/crewai/src/crewai/llm.py b/lib/crewai/src/crewai/llm.py index 3d321225d..00897688b 100644 --- a/lib/crewai/src/crewai/llm.py +++ b/lib/crewai/src/crewai/llm.py @@ -50,6 +50,7 @@ from crewai.utilities.exceptions.context_window_exceeding_exception import ( LLMContextLengthExceededError, ) from crewai.utilities.logger_utils import suppress_warnings +from crewai.utilities.string_utils import sanitize_tool_name if TYPE_CHECKING: @@ -1540,7 +1541,7 @@ class LLM(BaseLLM): # --- 2) Extract function name from first tool call tool_call = tool_calls[0] - function_name = tool_call.function.name + function_name = sanitize_tool_name(tool_call.function.name) function_args = {} # Initialize to empty dict to avoid unbound variable # --- 3) Check if function is available diff --git a/lib/crewai/src/crewai/tools/structured_tool.py b/lib/crewai/src/crewai/tools/structured_tool.py index 0f232dc57..44f0af2d9 100644 --- a/lib/crewai/src/crewai/tools/structured_tool.py +++ b/lib/crewai/src/crewai/tools/structured_tool.py @@ -296,6 +296,4 @@ class CrewStructuredTool: return self.args_schema.model_json_schema()["properties"] def __repr__(self) -> str: - return ( - f"CrewStructuredTool(name='{self.name}', description='{self.description}')" - ) + return f"CrewStructuredTool(name='{sanitize_tool_name(self.name)}', description='{self.description}')" diff --git a/lib/crewai/src/crewai/utilities/agent_utils.py b/lib/crewai/src/crewai/utilities/agent_utils.py index a191c1c0a..d29fc4471 100644 --- a/lib/crewai/src/crewai/utilities/agent_utils.py +++ b/lib/crewai/src/crewai/utilities/agent_utils.py @@ -821,10 +821,8 @@ def load_agent_from_repository(from_repository: str) -> dict[str, Any]: DELEGATION_TOOL_NAMES: Final[frozenset[str]] = frozenset( [ - "Delegate work to coworker", - "Delegate_work_to_coworker", - "Ask question to coworker", - "Ask_question_to_coworker", + sanitize_tool_name("Delegate work to coworker"), + sanitize_tool_name("Ask question to coworker"), ] ) @@ -842,7 +840,7 @@ def track_delegation_if_needed( tool_args: Arguments passed to the tool. task: The task being executed (used to track delegations). """ - if tool_name in DELEGATION_TOOL_NAMES and task is not None: + if sanitize_tool_name(tool_name) in DELEGATION_TOOL_NAMES and task is not None: coworker = tool_args.get("coworker") task.increment_delegations(coworker) @@ -861,19 +859,19 @@ def extract_tool_call_info( if hasattr(tool_call, "function"): # OpenAI-style: has .function.name and .function.arguments call_id = getattr(tool_call, "id", f"call_{id(tool_call)}") - return call_id, tool_call.function.name, tool_call.function.arguments + return call_id, sanitize_tool_name(tool_call.function.name), tool_call.function.arguments if hasattr(tool_call, "function_call") and tool_call.function_call: # Gemini-style: has .function_call.name and .function_call.args call_id = f"call_{id(tool_call)}" return ( call_id, - tool_call.function_call.name, + sanitize_tool_name(tool_call.function_call.name), dict(tool_call.function_call.args) if tool_call.function_call.args else {}, ) if hasattr(tool_call, "name") and hasattr(tool_call, "input"): # Anthropic format: has .name and .input (ToolUseBlock) call_id = getattr(tool_call, "id", f"call_{id(tool_call)}") - return call_id, tool_call.name, tool_call.input + return call_id, sanitize_tool_name(tool_call.name), tool_call.input if isinstance(tool_call, dict): # Support OpenAI "id", Bedrock "toolUseId", or generate one call_id = ( @@ -882,7 +880,7 @@ def extract_tool_call_info( func_info = tool_call.get("function", {}) func_name = func_info.get("name", "") or tool_call.get("name", "") func_args = func_info.get("arguments", "{}") or tool_call.get("input", {}) - return call_id, func_name, func_args + return call_id, sanitize_tool_name(func_name), func_args return None diff --git a/lib/crewai/src/crewai/utilities/streaming.py b/lib/crewai/src/crewai/utilities/streaming.py index 12cae2760..8f43e8ef0 100644 --- a/lib/crewai/src/crewai/utilities/streaming.py +++ b/lib/crewai/src/crewai/utilities/streaming.py @@ -18,6 +18,7 @@ from crewai.types.streaming import ( StreamChunkType, ToolCallChunk, ) +from crewai.utilities.string_utils import sanitize_tool_name class TaskInfo(TypedDict): @@ -58,7 +59,7 @@ def _extract_tool_call_info( StreamChunkType.TOOL_CALL, ToolCallChunk( tool_id=event.tool_call.id, - tool_name=event.tool_call.function.name, + tool_name=sanitize_tool_name(event.tool_call.function.name), arguments=event.tool_call.function.arguments, index=event.tool_call.index, ),