diff --git a/lib/crewai/src/crewai/agent/core.py b/lib/crewai/src/crewai/agent/core.py index 0c48253df..ca863ce7d 100644 --- a/lib/crewai/src/crewai/agent/core.py +++ b/lib/crewai/src/crewai/agent/core.py @@ -89,6 +89,7 @@ from crewai.utilities.guardrail_types import GuardrailType from crewai.utilities.llm_utils import create_llm from crewai.utilities.prompts import Prompts, StandardPromptResult, SystemPromptResult from crewai.utilities.pydantic_schema_utils import generate_model_description +from crewai.utilities.string_utils import sanitize_tool_name from crewai.utilities.token_counter_callback import TokenCalcHandler from crewai.utilities.training_handler import CrewTrainingHandler @@ -1339,10 +1340,10 @@ class Agent(BaseAgent): args_schema = None if hasattr(tool, "inputSchema") and tool.inputSchema: args_schema = self._json_schema_to_pydantic( - tool.name, tool.inputSchema + sanitize_tool_name(tool.name), tool.inputSchema ) - schemas[tool.name] = { + schemas[sanitize_tool_name(tool.name)] = { "description": getattr(tool, "description", ""), "args_schema": args_schema, } @@ -1498,7 +1499,7 @@ class Agent(BaseAgent): """ return "\n".join( [ - f"Tool name: {tool.name}\nTool description:\n{tool.description}" + f"Tool name: {sanitize_tool_name(tool.name)}\nTool description:\n{tool.description}" for tool in tools ] ) diff --git a/lib/crewai/src/crewai/crew.py b/lib/crewai/src/crewai/crew.py index 2c7f583b9..d8de504be 100644 --- a/lib/crewai/src/crewai/crew.py +++ b/lib/crewai/src/crewai/crew.py @@ -104,6 +104,7 @@ from crewai.utilities.streaming import ( signal_end, signal_error, ) +from crewai.utilities.string_utils import sanitize_tool_name from crewai.utilities.task_output_storage_handler import TaskOutputStorageHandler from crewai.utilities.training_handler import CrewTrainingHandler @@ -1241,10 +1242,14 @@ class Crew(FlowTrackable, BaseModel): return existing_tools # Create mapping of tool names to new tools - new_tool_map = {tool.name: tool for tool in new_tools} + new_tool_map = {sanitize_tool_name(tool.name): tool for tool in new_tools} # Remove any existing tools that will be replaced - tools = [tool for tool in existing_tools if tool.name not in new_tool_map] + tools = [ + tool + for tool in existing_tools + if sanitize_tool_name(tool.name) not in new_tool_map + ] # Add all new tools tools.extend(new_tools) diff --git a/lib/crewai/src/crewai/experimental/evaluation/metrics/tools_metrics.py b/lib/crewai/src/crewai/experimental/evaluation/metrics/tools_metrics.py index 99580dc3f..bb99b6464 100644 --- a/lib/crewai/src/crewai/experimental/evaluation/metrics/tools_metrics.py +++ b/lib/crewai/src/crewai/experimental/evaluation/metrics/tools_metrics.py @@ -11,6 +11,7 @@ from crewai.experimental.evaluation.base_evaluator import ( ) from crewai.experimental.evaluation.json_parser import extract_json_from_llm_response from crewai.task import Task +from crewai.utilities.string_utils import sanitize_tool_name from crewai.utilities.types import LLMMessage @@ -52,7 +53,9 @@ class ToolSelectionEvaluator(BaseEvaluator): available_tools_info = "" if agent.tools: for tool in agent.tools: - available_tools_info += f"- {tool.name}: {tool.description}\n" + available_tools_info += ( + f"- {sanitize_tool_name(tool.name)}: {tool.description}\n" + ) else: available_tools_info = "No tools available" diff --git a/lib/crewai/src/crewai/mcp/client.py b/lib/crewai/src/crewai/mcp/client.py index aff07a397..f608933f6 100644 --- a/lib/crewai/src/crewai/mcp/client.py +++ b/lib/crewai/src/crewai/mcp/client.py @@ -31,6 +31,7 @@ from crewai.mcp.transports.base import BaseTransport from crewai.mcp.transports.http import HTTPTransport from crewai.mcp.transports.sse import SSETransport from crewai.mcp.transports.stdio import StdioTransport +from crewai.utilities.string_utils import sanitize_tool_name # MCP Connection timeout constants (in seconds) @@ -418,7 +419,7 @@ class MCPClient: return [ { - "name": tool.name, + "name": sanitize_tool_name(tool.name), "description": getattr(tool, "description", ""), "inputSchema": getattr(tool, "inputSchema", {}), } diff --git a/lib/crewai/src/crewai/telemetry/telemetry.py b/lib/crewai/src/crewai/telemetry/telemetry.py index 66a080894..27d5d6090 100644 --- a/lib/crewai/src/crewai/telemetry/telemetry.py +++ b/lib/crewai/src/crewai/telemetry/telemetry.py @@ -52,6 +52,7 @@ from crewai.telemetry.utils import ( close_span, ) from crewai.utilities.logger_utils import suppress_warnings +from crewai.utilities.string_utils import sanitize_tool_name logger = logging.getLogger(__name__) @@ -323,7 +324,8 @@ class Telemetry: ), "max_retry_limit": getattr(agent, "max_retry_limit", 3), "tools_names": [ - tool.name.casefold() for tool in agent.tools or [] + sanitize_tool_name(tool.name) + for tool in agent.tools or [] ], # Add agent fingerprint data if sharing crew details "fingerprint": ( @@ -372,7 +374,8 @@ class Telemetry: else None ), "tools_names": [ - tool.name.casefold() for tool in task.tools or [] + sanitize_tool_name(tool.name) + for tool in task.tools or [] ], # Add task fingerprint data if sharing crew details "fingerprint": ( @@ -425,7 +428,8 @@ class Telemetry: ), "max_retry_limit": getattr(agent, "max_retry_limit", 3), "tools_names": [ - tool.name.casefold() for tool in agent.tools or [] + sanitize_tool_name(tool.name) + for tool in agent.tools or [] ], } for agent in crew.agents @@ -447,7 +451,8 @@ class Telemetry: ), "agent_key": task.agent.key if task.agent else None, "tools_names": [ - tool.name.casefold() for tool in task.tools or [] + sanitize_tool_name(tool.name) + for tool in task.tools or [] ], } for task in crew.tasks @@ -832,7 +837,8 @@ class Telemetry: "llm": agent.llm.model, "delegation_enabled?": agent.allow_delegation, "tools_names": [ - tool.name.casefold() for tool in agent.tools or [] + sanitize_tool_name(tool.name) + for tool in agent.tools or [] ], } for agent in crew.agents @@ -858,7 +864,8 @@ class Telemetry: else None ), "tools_names": [ - tool.name.casefold() for tool in task.tools or [] + sanitize_tool_name(tool.name) + for tool in task.tools or [] ], } for task in crew.tasks diff --git a/lib/crewai/src/crewai/tools/base_tool.py b/lib/crewai/src/crewai/tools/base_tool.py index 0dbc9a78f..8a10cdfa3 100644 --- a/lib/crewai/src/crewai/tools/base_tool.py +++ b/lib/crewai/src/crewai/tools/base_tool.py @@ -26,6 +26,7 @@ from typing_extensions import TypeIs from crewai.tools.structured_tool import CrewStructuredTool from crewai.utilities.printer import Printer from crewai.utilities.pydantic_schema_utils import generate_model_description +from crewai.utilities.string_utils import sanitize_tool_name _printer = Printer() @@ -259,10 +260,12 @@ class BaseTool(BaseModel, ABC): else: fields[name] = (param_annotation, param.default) if fields: - args_schema = create_model(f"{tool.name}Input", **fields) + args_schema = create_model( + f"{sanitize_tool_name(tool.name)}_input", **fields + ) else: args_schema = create_model( - f"{tool.name}Input", __base__=PydanticBaseModel + f"{sanitize_tool_name(tool.name)}_input", __base__=PydanticBaseModel ) return cls( @@ -301,7 +304,7 @@ class BaseTool(BaseModel, ABC): schema = generate_model_description(self.args_schema) args_json = json.dumps(schema["json_schema"]["schema"], indent=2) self.description = ( - f"Tool Name: {self.name}\n" + f"Tool Name: {sanitize_tool_name(self.name)}\n" f"Tool Arguments: {args_json}\n" f"Tool Description: {self.description}" ) @@ -379,7 +382,7 @@ class Tool(BaseTool, Generic[P, R]): if _is_awaitable(result): return await result raise NotImplementedError( - f"{self.name} does not have an async function. " + f"{sanitize_tool_name(self.name)} does not have an async function. " "Use run() for sync execution or provide an async function." ) @@ -421,10 +424,12 @@ class Tool(BaseTool, Generic[P, R]): else: fields[name] = (param_annotation, param.default) if fields: - args_schema = create_model(f"{tool.name}Input", **fields) + args_schema = create_model( + f"{sanitize_tool_name(tool.name)}_input", **fields + ) else: args_schema = create_model( - f"{tool.name}Input", __base__=PydanticBaseModel + f"{sanitize_tool_name(tool.name)}_input", __base__=PydanticBaseModel ) return cls( diff --git a/lib/crewai/src/crewai/tools/cache_tools/cache_tools.py b/lib/crewai/src/crewai/tools/cache_tools/cache_tools.py index e391d4289..d73210553 100644 --- a/lib/crewai/src/crewai/tools/cache_tools/cache_tools.py +++ b/lib/crewai/src/crewai/tools/cache_tools/cache_tools.py @@ -2,6 +2,7 @@ from pydantic import BaseModel, Field from crewai.agents.cache.cache_handler import CacheHandler from crewai.tools.structured_tool import CrewStructuredTool +from crewai.utilities.string_utils import sanitize_tool_name class CacheTools(BaseModel): @@ -13,14 +14,14 @@ class CacheTools(BaseModel): default_factory=CacheHandler, ) - def tool(self): + def tool(self) -> CrewStructuredTool: return CrewStructuredTool.from_function( func=self.hit_cache, - name=self.name, + name=sanitize_tool_name(self.name), description="Reads directly from the cache", ) - def hit_cache(self, key): + def hit_cache(self, key: str) -> str | None: split = key.split("tool:") tool = split[1].split("|input:")[0].strip() tool_input = split[1].split("|input:")[1].strip() diff --git a/lib/crewai/src/crewai/tools/structured_tool.py b/lib/crewai/src/crewai/tools/structured_tool.py index ceb1f2af9..0f232dc57 100644 --- a/lib/crewai/src/crewai/tools/structured_tool.py +++ b/lib/crewai/src/crewai/tools/structured_tool.py @@ -10,6 +10,7 @@ from typing import TYPE_CHECKING, Any, get_type_hints from pydantic import BaseModel, Field, create_model from crewai.utilities.logger import Logger +from crewai.utilities.string_utils import sanitize_tool_name if TYPE_CHECKING: @@ -229,7 +230,7 @@ class CrewStructuredTool: if self.has_reached_max_usage_count(): raise ToolUsageLimitExceededError( - f"Tool '{self.name}' has reached its maximum usage limit of {self.max_usage_count}. You should not use the {self.name} tool again." + f"Tool '{sanitize_tool_name(self.name)}' has reached its maximum usage limit of {self.max_usage_count}. You should not use the {sanitize_tool_name(self.name)} tool again." ) self._increment_usage_count() @@ -261,7 +262,7 @@ class CrewStructuredTool: if self.has_reached_max_usage_count(): raise ToolUsageLimitExceededError( - f"Tool '{self.name}' has reached its maximum usage limit of {self.max_usage_count}. You should not use the {self.name} tool again." + f"Tool '{sanitize_tool_name(self.name)}' has reached its maximum usage limit of {self.max_usage_count}. You should not use the {sanitize_tool_name(self.name)} tool again." ) self._increment_usage_count() diff --git a/lib/crewai/src/crewai/tools/tool_usage.py b/lib/crewai/src/crewai/tools/tool_usage.py index d513bbac9..30cc21666 100644 --- a/lib/crewai/src/crewai/tools/tool_usage.py +++ b/lib/crewai/src/crewai/tools/tool_usage.py @@ -30,6 +30,7 @@ from crewai.utilities.agent_utils import ( from crewai.utilities.converter import Converter from crewai.utilities.i18n import I18N, get_i18n from crewai.utilities.printer import Printer +from crewai.utilities.string_utils import sanitize_tool_name if TYPE_CHECKING: @@ -145,7 +146,8 @@ class ToolUsage: if ( isinstance(tool, CrewStructuredTool) - and tool.name == self._i18n.tools("add_image")["name"] # type: ignore + and sanitize_tool_name(tool.name) + == sanitize_tool_name(self._i18n.tools("add_image")["name"]) # type: ignore ): try: return self._use(tool_string=tool_string, tool=tool, calling=calling) @@ -192,7 +194,8 @@ class ToolUsage: if ( isinstance(tool, CrewStructuredTool) - and tool.name == self._i18n.tools("add_image")["name"] # type: ignore + and sanitize_tool_name(tool.name) + == sanitize_tool_name(self._i18n.tools("add_image")["name"]) # type: ignore ): try: return await self._ause( @@ -233,7 +236,7 @@ class ToolUsage: ) self._telemetry.tool_repeated_usage( llm=self.function_calling_llm, - tool_name=tool.name, + tool_name=sanitize_tool_name(tool.name), attempts=self._run_attempts, ) return self._format_result(result=result) @@ -278,7 +281,7 @@ class ToolUsage: input_str = str(calling.arguments) result = self.tools_handler.cache.read( - tool=calling.tool_name, input=input_str + tool=sanitize_tool_name(calling.tool_name), input=input_str ) # type: ignore from_cache = result is not None @@ -286,12 +289,15 @@ class ToolUsage: ( available_tool for available_tool in self.tools - if available_tool.name == tool.name + if sanitize_tool_name(available_tool.name) + == sanitize_tool_name(tool.name) ), None, ) - usage_limit_error = self._check_usage_limit(available_tool, tool.name) + usage_limit_error = self._check_usage_limit( + available_tool, sanitize_tool_name(tool.name) + ) if usage_limit_error: result = usage_limit_error self._telemetry.tool_usage_error(llm=self.function_calling_llm) @@ -299,9 +305,9 @@ class ToolUsage: # Don't return early - fall through to finally block elif result is None: try: - if calling.tool_name in [ - "Delegate work to coworker", - "Ask question to coworker", + if sanitize_tool_name(calling.tool_name) in [ + sanitize_tool_name("Delegate work to coworker"), + sanitize_tool_name("Ask question to coworker"), ]: coworker = ( calling.arguments.get("coworker") @@ -350,13 +356,13 @@ class ToolUsage: self._telemetry.tool_usage( llm=self.function_calling_llm, - tool_name=tool.name, + tool_name=sanitize_tool_name(tool.name), attempts=self._run_attempts, ) result = self._format_result(result=result) data = { "result": result, - "tool_name": tool.name, + "tool_name": sanitize_tool_name(tool.name), "tool_args": calling.arguments, } @@ -380,7 +386,7 @@ class ToolUsage: and available_tool.max_usage_count is not None ): self._printer.print( - content=f"Tool '{available_tool.name}' usage: {available_tool.current_usage_count}/{available_tool.max_usage_count}", + content=f"Tool '{sanitize_tool_name(available_tool.name)}' usage: {available_tool.current_usage_count}/{available_tool.max_usage_count}", color="blue", ) elif available_tool and hasattr( @@ -392,7 +398,7 @@ class ToolUsage: and available_tool.max_usage_count is not None ): self._printer.print( - content=f"Tool '{available_tool.name}' usage: {available_tool.current_usage_count}/{available_tool.max_usage_count}", + content=f"Tool '{sanitize_tool_name(available_tool.name)}' usage: {available_tool.current_usage_count}/{available_tool.max_usage_count}", color="blue", ) @@ -403,7 +409,11 @@ class ToolUsage: self._telemetry.tool_usage_error(llm=self.function_calling_llm) error_message = self._i18n.errors( "tool_usage_exception" - ).format(error=e, tool=tool.name, tool_inputs=tool.description) + ).format( + error=e, + tool=sanitize_tool_name(tool.name), + tool_inputs=tool.description, + ) result = ToolUsageError( f"\n{error_message}.\nMoving on then. {self._i18n.slice('format').format(tool_names=self.tools_names)}" ).message @@ -450,7 +460,7 @@ class ToolUsage: ) self._telemetry.tool_repeated_usage( llm=self.function_calling_llm, - tool_name=tool.name, + tool_name=sanitize_tool_name(tool.name), attempts=self._run_attempts, ) return self._format_result(result=result) @@ -497,7 +507,7 @@ class ToolUsage: input_str = str(calling.arguments) result = self.tools_handler.cache.read( - tool=calling.tool_name, input=input_str + tool=sanitize_tool_name(calling.tool_name), input=input_str ) # type: ignore from_cache = result is not None @@ -505,12 +515,15 @@ class ToolUsage: ( available_tool for available_tool in self.tools - if available_tool.name == tool.name + if sanitize_tool_name(available_tool.name) + == sanitize_tool_name(tool.name) ), None, ) - usage_limit_error = self._check_usage_limit(available_tool, tool.name) + usage_limit_error = self._check_usage_limit( + available_tool, sanitize_tool_name(tool.name) + ) if usage_limit_error: result = usage_limit_error self._telemetry.tool_usage_error(llm=self.function_calling_llm) @@ -518,9 +531,9 @@ class ToolUsage: # Don't return early - fall through to finally block elif result is None: try: - if calling.tool_name in [ - "Delegate work to coworker", - "Ask question to coworker", + if sanitize_tool_name(calling.tool_name) in [ + sanitize_tool_name("Delegate work to coworker"), + sanitize_tool_name("Ask question to coworker"), ]: coworker = ( calling.arguments.get("coworker") @@ -569,13 +582,13 @@ class ToolUsage: self._telemetry.tool_usage( llm=self.function_calling_llm, - tool_name=tool.name, + tool_name=sanitize_tool_name(tool.name), attempts=self._run_attempts, ) result = self._format_result(result=result) data = { "result": result, - "tool_name": tool.name, + "tool_name": sanitize_tool_name(tool.name), "tool_args": calling.arguments, } @@ -599,7 +612,7 @@ class ToolUsage: and available_tool.max_usage_count is not None ): self._printer.print( - content=f"Tool '{available_tool.name}' usage: {available_tool.current_usage_count}/{available_tool.max_usage_count}", + content=f"Tool '{sanitize_tool_name(available_tool.name)}' usage: {available_tool.current_usage_count}/{available_tool.max_usage_count}", color="blue", ) elif available_tool and hasattr( @@ -611,7 +624,7 @@ class ToolUsage: and available_tool.max_usage_count is not None ): self._printer.print( - content=f"Tool '{available_tool.name}' usage: {available_tool.current_usage_count}/{available_tool.max_usage_count}", + content=f"Tool '{sanitize_tool_name(available_tool.name)}' usage: {available_tool.current_usage_count}/{available_tool.max_usage_count}", color="blue", ) @@ -622,7 +635,11 @@ class ToolUsage: self._telemetry.tool_usage_error(llm=self.function_calling_llm) error_message = self._i18n.errors( "tool_usage_exception" - ).format(error=e, tool=tool.name, tool_inputs=tool.description) + ).format( + error=e, + tool=sanitize_tool_name(tool.name), + tool_inputs=tool.description, + ) result = ToolUsageError( f"\n{error_message}.\nMoving on then. {self._i18n.slice('format').format(tool_names=self.tools_names)}" ).message @@ -680,9 +697,10 @@ class ToolUsage: if not self.tools_handler: return False if last_tool_usage := self.tools_handler.last_used_tool: - return (calling.tool_name == last_tool_usage.tool_name) and ( - calling.arguments == last_tool_usage.arguments - ) + return ( + sanitize_tool_name(calling.tool_name) + == sanitize_tool_name(last_tool_usage.tool_name) + ) and (calling.arguments == last_tool_usage.arguments) return False @staticmethod @@ -705,20 +723,19 @@ class ToolUsage: return None def _select_tool(self, tool_name: str) -> Any: + sanitized_input = sanitize_tool_name(tool_name) order_tools = sorted( self.tools, key=lambda tool: SequenceMatcher( - None, tool.name.lower().strip(), tool_name.lower().strip() + None, sanitize_tool_name(tool.name), sanitized_input ).ratio(), reverse=True, ) for tool in order_tools: + sanitized_tool = sanitize_tool_name(tool.name) if ( - tool.name.lower().strip() == tool_name.lower().strip() - or SequenceMatcher( - None, tool.name.lower().strip(), tool_name.lower().strip() - ).ratio() - > 0.85 + sanitized_tool == sanitized_input + or SequenceMatcher(None, sanitized_tool, sanitized_input).ratio() > 0.85 ): return tool if self.task: @@ -803,7 +820,7 @@ class ToolUsage: return ToolUsageError(f"{self._i18n.errors('tool_arguments_error')}") return ToolCalling( - tool_name=tool.name, + tool_name=sanitize_tool_name(tool.name), arguments=arguments, ) @@ -957,7 +974,7 @@ class ToolUsage: event_data = { "run_attempts": self._run_attempts, "delegations": self.task.delegations if self.task else 0, - "tool_name": tool.name, + "tool_name": sanitize_tool_name(tool.name), "tool_args": tool_calling.arguments, "tool_class": tool.__class__.__name__, "agent_key": ( diff --git a/lib/crewai/src/crewai/utilities/reasoning_handler.py b/lib/crewai/src/crewai/utilities/reasoning_handler.py index 660354d20..e9bb62997 100644 --- a/lib/crewai/src/crewai/utilities/reasoning_handler.py +++ b/lib/crewai/src/crewai/utilities/reasoning_handler.py @@ -13,6 +13,7 @@ from crewai.events.types.reasoning_events import ( ) from crewai.llm import LLM from crewai.task import Task +from crewai.utilities.string_utils import sanitize_tool_name class ReasoningPlan(BaseModel): @@ -340,7 +341,9 @@ class AgentReasoning: str: Comma-separated list of tool names. """ try: - return ", ".join([tool.name for tool in (self.task.tools or [])]) + return ", ".join( + [sanitize_tool_name(tool.name) for tool in (self.task.tools or [])] + ) except (AttributeError, TypeError): return "No tools available" diff --git a/lib/crewai/src/crewai/utilities/tool_utils.py b/lib/crewai/src/crewai/utilities/tool_utils.py index ca588f699..027f136ed 100644 --- a/lib/crewai/src/crewai/utilities/tool_utils.py +++ b/lib/crewai/src/crewai/utilities/tool_utils.py @@ -15,6 +15,7 @@ from crewai.tools.tool_types import ToolResult from crewai.tools.tool_usage import ToolUsage, ToolUsageError from crewai.utilities.i18n import I18N from crewai.utilities.logger import Logger +from crewai.utilities.string_utils import sanitize_tool_name if TYPE_CHECKING: @@ -63,7 +64,7 @@ async def aexecute_tool_and_check_finality( treated as a final answer. """ logger = Logger(verbose=crew.verbose if crew else False) - tool_name_to_tool_map = {tool.name: tool for tool in tools} + tool_name_to_tool_map = {sanitize_tool_name(tool.name): tool for tool in tools} if agent_key and agent_role and agent: fingerprint_context = fingerprint_context or {} @@ -90,19 +91,9 @@ async def aexecute_tool_and_check_finality( if isinstance(tool_calling, ToolUsageError): return ToolResult(tool_calling.message, False) - if tool_calling.tool_name.casefold().strip() in [ - name.casefold().strip() for name in tool_name_to_tool_map - ] or tool_calling.tool_name.casefold().replace("_", " ") in [ - name.casefold().strip() for name in tool_name_to_tool_map - ]: - tool = tool_name_to_tool_map.get(tool_calling.tool_name) - if not tool: - tool_result = i18n.errors("wrong_tool_name").format( - tool=tool_calling.tool_name, - tools=", ".join([t.name.casefold() for t in tools]), - ) - return ToolResult(result=tool_result, result_as_answer=False) - + sanitized_tool_name = sanitize_tool_name(tool_calling.tool_name) + tool = tool_name_to_tool_map.get(sanitized_tool_name) + if tool: tool_input = tool_calling.arguments if tool_calling.arguments else {} hook_context = ToolCallHookContext( tool_name=tool_calling.tool_name, @@ -152,8 +143,8 @@ async def aexecute_tool_and_check_finality( return ToolResult(modified_result, tool.result_as_answer) tool_result = i18n.errors("wrong_tool_name").format( - tool=tool_calling.tool_name, - tools=", ".join([tool.name.casefold() for tool in tools]), + tool=sanitized_tool_name, + tools=", ".join(tool_name_to_tool_map.keys()), ) return ToolResult(result=tool_result, result_as_answer=False) @@ -193,7 +184,7 @@ def execute_tool_and_check_finality( ToolResult containing the execution result and whether it should be treated as a final answer """ logger = Logger(verbose=crew.verbose if crew else False) - tool_name_to_tool_map = {tool.name: tool for tool in tools} + tool_name_to_tool_map = {sanitize_tool_name(tool.name): tool for tool in tools} if agent_key and agent_role and agent: fingerprint_context = fingerprint_context or {} @@ -206,7 +197,6 @@ def execute_tool_and_check_finality( except Exception as e: raise ValueError(f"Failed to set fingerprint: {e}") from e - # Create tool usage instance tool_usage = ToolUsage( tools_handler=tools_handler, tools=tools, @@ -216,26 +206,14 @@ def execute_tool_and_check_finality( action=agent_action, ) - # Parse tool calling tool_calling = tool_usage.parse_tool_calling(agent_action.text) if isinstance(tool_calling, ToolUsageError): return ToolResult(tool_calling.message, False) - # Check if tool name matches - if tool_calling.tool_name.casefold().strip() in [ - name.casefold().strip() for name in tool_name_to_tool_map - ] or tool_calling.tool_name.casefold().replace("_", " ") in [ - name.casefold().strip() for name in tool_name_to_tool_map - ]: - tool = tool_name_to_tool_map.get(tool_calling.tool_name) - if not tool: - tool_result = i18n.errors("wrong_tool_name").format( - tool=tool_calling.tool_name, - tools=", ".join([t.name.casefold() for t in tools]), - ) - return ToolResult(result=tool_result, result_as_answer=False) - + sanitized_tool_name = sanitize_tool_name(tool_calling.tool_name) + tool = tool_name_to_tool_map.get(sanitized_tool_name) + if tool: tool_input = tool_calling.arguments if tool_calling.arguments else {} hook_context = ToolCallHookContext( tool_name=tool_calling.tool_name, @@ -285,9 +263,8 @@ def execute_tool_and_check_finality( return ToolResult(modified_result, tool.result_as_answer) - # Handle invalid tool name tool_result = i18n.errors("wrong_tool_name").format( - tool=tool_calling.tool_name, - tools=", ".join([tool.name.casefold() for tool in tools]), + tool=sanitized_tool_name, + tools=", ".join(tool_name_to_tool_map.keys()), ) return ToolResult(result=tool_result, result_as_answer=False) diff --git a/lib/crewai/tests/tools/test_base_tool.py b/lib/crewai/tests/tools/test_base_tool.py index cba02ebc1..4a6850ce1 100644 --- a/lib/crewai/tests/tools/test_base_tool.py +++ b/lib/crewai/tests/tools/test_base_tool.py @@ -17,7 +17,7 @@ def test_creating_a_tool_using_annotation(): # Assert all the right attributes were defined assert my_tool.name == "Name of my tool" - assert "Tool Name: Name of my tool" in my_tool.description + assert "Tool Name: name_of_my_tool" in my_tool.description assert "Tool Arguments:" in my_tool.description assert '"question"' in my_tool.description assert '"type": "string"' in my_tool.description @@ -32,7 +32,7 @@ def test_creating_a_tool_using_annotation(): converted_tool = my_tool.to_structured_tool() assert converted_tool.name == "Name of my tool" - assert "Tool Name: Name of my tool" in converted_tool.description + assert "Tool Name: name_of_my_tool" in converted_tool.description assert "Tool Arguments:" in converted_tool.description assert '"question"' in converted_tool.description assert converted_tool.args_schema.model_json_schema()["properties"] == { @@ -56,7 +56,7 @@ def test_creating_a_tool_using_baseclass(): # Assert all the right attributes were defined assert my_tool.name == "Name of my tool" - assert "Tool Name: Name of my tool" in my_tool.description + assert "Tool Name: name_of_my_tool" in my_tool.description assert "Tool Arguments:" in my_tool.description assert '"question"' in my_tool.description assert '"type": "string"' in my_tool.description @@ -69,7 +69,7 @@ def test_creating_a_tool_using_baseclass(): converted_tool = my_tool.to_structured_tool() assert converted_tool.name == "Name of my tool" - assert "Tool Name: Name of my tool" in converted_tool.description + assert "Tool Name: name_of_my_tool" in converted_tool.description assert "Tool Arguments:" in converted_tool.description assert '"question"' in converted_tool.description assert converted_tool.args_schema.model_json_schema()["properties"] == { diff --git a/lib/crewai/tests/tools/test_tool_usage.py b/lib/crewai/tests/tools/test_tool_usage.py index 83e40f099..c6dfad58b 100644 --- a/lib/crewai/tests/tools/test_tool_usage.py +++ b/lib/crewai/tests/tools/test_tool_usage.py @@ -108,7 +108,7 @@ def test_tool_usage_render(): rendered = tool_usage._render() # Check that the rendered output contains the expected tool information - assert "Tool Name: Random Number Generator" in rendered + assert "Tool Name: random_number_generator" in rendered assert "Tool Arguments:" in rendered assert ( "Tool Description: Generates a random number within a specified range" @@ -488,7 +488,7 @@ def test_tool_selection_error_event_direct(): assert event.agent_role == "test_role" assert event.tool_name == "Non Existent Tool" assert event.tool_args == {} - assert "Tool Name: Test Tool" in event.tool_class + assert "Tool Name: test_tool" in event.tool_class assert "A test tool" in event.tool_class assert "don't exist" in event.error @@ -503,7 +503,7 @@ def test_tool_selection_error_event_direct(): assert event.agent_role == "test_role" assert event.tool_name == "" assert event.tool_args == {} - assert "Test Tool" in event.tool_class + assert "test_tool" in event.tool_class assert "forgot the Action name" in event.error @@ -654,7 +654,7 @@ def test_tool_usage_finished_event_with_result(): # Verify event attributes assert event.agent_key == "test_agent_key" assert event.agent_role == "test_agent_role" - assert event.tool_name == "Test Tool" + assert event.tool_name == "test_tool" assert event.tool_args == {"arg1": "value1"} assert event.tool_class == "TestTool" assert event.run_attempts == 1 # Default value from ToolUsage @@ -734,7 +734,7 @@ def test_tool_usage_finished_event_with_cached_result(): # Verify event attributes assert event.agent_key == "test_agent_key" assert event.agent_role == "test_agent_role" - assert event.tool_name == "Test Tool" + assert event.tool_name == "test_tool" assert event.tool_args == {"arg1": "value1"} assert event.tool_class == "TestTool" assert event.run_attempts == 1 # Default value from ToolUsage