From 0b8e8313c69f9480b96d9ef76f33187f4ed86997 Mon Sep 17 00:00:00 2001 From: Nolan Date: Thu, 9 Jul 2026 23:15:12 +0800 Subject: [PATCH] fix(tools): raise ToolUsageError instead of bare raise for non-dict arguments _original_tool_calling used a bare outside any active except block when the parsed arguments were not a dict. With raise_error=True this raised rather than the intended ToolUsageError. Raise the explicit exception instead. Fixes #6430 --- lib/crewai/src/crewai/tools/tool_usage.py | 2 +- lib/crewai/tests/tools/test_tool_usage.py | 54 ++++++++++++++++++++++- 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/lib/crewai/src/crewai/tools/tool_usage.py b/lib/crewai/src/crewai/tools/tool_usage.py index e92ba03eed..c523b2bb43 100644 --- a/lib/crewai/src/crewai/tools/tool_usage.py +++ b/lib/crewai/src/crewai/tools/tool_usage.py @@ -850,7 +850,7 @@ def _original_tool_calling( if not isinstance(arguments, dict): if raise_error: - raise + raise ToolUsageError(f"{I18N_DEFAULT.errors('tool_arguments_error')}") return ToolUsageError(f"{I18N_DEFAULT.errors('tool_arguments_error')}") return ToolCalling( diff --git a/lib/crewai/tests/tools/test_tool_usage.py b/lib/crewai/tests/tools/test_tool_usage.py index 9d61c93a9b..9ca3a271dc 100644 --- a/lib/crewai/tests/tools/test_tool_usage.py +++ b/lib/crewai/tests/tools/test_tool_usage.py @@ -25,7 +25,7 @@ ) from crewai.tools import BaseTool from crewai.tools.tool_calling import ToolCalling -from crewai.tools.tool_usage import ToolUsage +from crewai.tools.tool_usage import ToolUsage, ToolUsageError from crewai.utilities.tool_utils import execute_tool_and_check_finality from pydantic import BaseModel, Field import pytest @@ -559,6 +559,58 @@ def test_validate_tool_input_large_json_content(): assert arguments == expected_arguments +def test_original_tool_calling_raises_tool_usage_error_for_non_dict_arguments(): + """A non-dict tool input must surface as ToolUsageError, not RuntimeError. + + Regression test for the bare ``raise`` in ``_original_tool_calling``: when + the parsed arguments are not a dict, ``raise_error=True`` previously hit a + ``raise`` statement outside any active ``except`` block, producing + ``RuntimeError: No active exception to reraise`` instead of the intended + ``ToolUsageError``. + """ + tool_usage = ToolUsage( + tools_handler=MagicMock(), + tools=[], + task=MagicMock(), + function_calling_llm=MagicMock(), + agent=MagicMock(), + action=MagicMock(), + ) + + with patch.object(tool_usage, "_select_tool", return_value=MagicMock(name="tool")): + with patch.object( + ToolUsage, + "_validate_tool_input", + return_value=["not", "a", "dict"], + ): + with pytest.raises(ToolUsageError): + tool_usage._original_tool_calling("tool_string", raise_error=True) + + +def test_original_tool_calling_returns_tool_usage_error_for_non_dict_without_raise(): + """When ``raise_error=False`` a non-dict input returns a ToolUsageError.""" + tool_usage = ToolUsage( + tools_handler=MagicMock(), + tools=[], + task=MagicMock(), + function_calling_llm=MagicMock(), + agent=MagicMock(), + action=MagicMock(), + ) + + with patch.object(tool_usage, "_select_tool", return_value=MagicMock(name="tool")): + with patch.object( + ToolUsage, + "_validate_tool_input", + return_value=["not", "a", "dict"], + ): + result = tool_usage._original_tool_calling( + "tool_string", raise_error=False + ) + + assert isinstance(result, ToolUsageError) + + def test_tool_selection_error_event_direct(): """Test tool selection error event emission directly from ToolUsage class.""" mock_agent = MagicMock()