fix: sanitize tool names in hook decorator filters

This commit is contained in:
Greyson LaLonde
2026-04-08 21:02:25 +08:00
committed by GitHub
parent fc9280ccf6
commit 98e0d1054f
3 changed files with 41 additions and 4 deletions

View File

@@ -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)

View File

@@ -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,
@@ -216,7 +216,7 @@ def execute_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,
@@ -240,7 +240,7 @@ def execute_tool_and_check_finality(
tool_result = tool_usage.use(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,

View File

@@ -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 = []