mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-04-30 23:02:50 +00:00
fix: sanitize tool names in hook decorator filters
The tools parameter in before_tool_call/after_tool_call decorators now auto-sanitizes names via sanitize_tool_name(), so users can pass BaseTool.name directly without knowing the internal normalization. Also fixes tool_utils.py to pass the sanitized name to ToolCallHookContext for consistency with crew_agent_executor.py. Closes #5335
This commit is contained in:
@@ -5,6 +5,8 @@ from functools import wraps
|
||||
import inspect
|
||||
from typing import TYPE_CHECKING, Any, TypeVar, overload
|
||||
|
||||
from crewai.utilities.string_utils import sanitize_tool_name
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from crewai.hooks.llm_hooks import LLMCallHookContext
|
||||
@@ -37,6 +39,9 @@ def _create_hook_decorator(
|
||||
tools: list[str] | None = None,
|
||||
agents: list[str] | None = None,
|
||||
) -> Callable[..., Any]:
|
||||
if tools:
|
||||
tools = [sanitize_tool_name(t) for t in tools]
|
||||
|
||||
def decorator(f: Callable[..., Any]) -> Callable[..., Any]:
|
||||
setattr(f, marker_attribute, True)
|
||||
|
||||
|
||||
@@ -96,7 +96,7 @@ async def aexecute_tool_and_check_finality(
|
||||
if tool:
|
||||
tool_input = tool_calling.arguments if tool_calling.arguments else {}
|
||||
hook_context = ToolCallHookContext(
|
||||
tool_name=tool_calling.tool_name,
|
||||
tool_name=sanitized_tool_name,
|
||||
tool_input=tool_input,
|
||||
tool=tool,
|
||||
agent=agent,
|
||||
@@ -120,7 +120,7 @@ async def aexecute_tool_and_check_finality(
|
||||
tool_result = await tool_usage.ause(tool_calling, agent_action.text)
|
||||
|
||||
after_hook_context = ToolCallHookContext(
|
||||
tool_name=tool_calling.tool_name,
|
||||
tool_name=sanitized_tool_name,
|
||||
tool_input=tool_input,
|
||||
tool=tool,
|
||||
agent=agent,
|
||||
|
||||
@@ -192,6 +192,38 @@ class TestToolHookDecorators:
|
||||
# Should still be 1 (hook didn't execute for read_file)
|
||||
assert len(execution_log) == 1
|
||||
|
||||
def test_before_tool_call_tool_filter_sanitizes_names(self):
|
||||
"""Tool filter should auto-sanitize names so users can pass BaseTool.name directly."""
|
||||
execution_log = []
|
||||
|
||||
# User passes the human-readable tool name (e.g. BaseTool.name)
|
||||
@before_tool_call(tools=["Delete File", "Execute Code"])
|
||||
def filtered_hook(context):
|
||||
execution_log.append(context.tool_name)
|
||||
return None
|
||||
|
||||
hooks = get_before_tool_call_hooks()
|
||||
assert len(hooks) == 1
|
||||
|
||||
mock_tool = Mock()
|
||||
# Context uses the sanitized name (as set by the executor)
|
||||
context = ToolCallHookContext(
|
||||
tool_name="delete_file",
|
||||
tool_input={},
|
||||
tool=mock_tool,
|
||||
)
|
||||
hooks[0](context)
|
||||
assert execution_log == ["delete_file"]
|
||||
|
||||
# Non-matching tool still filtered out
|
||||
context2 = ToolCallHookContext(
|
||||
tool_name="read_file",
|
||||
tool_input={},
|
||||
tool=mock_tool,
|
||||
)
|
||||
hooks[0](context2)
|
||||
assert execution_log == ["delete_file"]
|
||||
|
||||
def test_before_tool_call_with_combined_filters(self):
|
||||
"""Test that combined tool and agent filters work."""
|
||||
execution_log = []
|
||||
|
||||
Reference in New Issue
Block a user