Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions lib/crewai/src/crewai/tools/tool_usage.py
Original file line number Diff line number Diff line change
Expand Up @@ -849,9 +849,12 @@ def _original_tool_calling(
return ToolUsageError(f"{I18N_DEFAULT.errors('tool_arguments_error')}")

if not isinstance(arguments, dict):
tool_arguments_error = ToolUsageError(
f"{I18N_DEFAULT.errors('tool_arguments_error')}"
)
if raise_error:
raise
return ToolUsageError(f"{I18N_DEFAULT.errors('tool_arguments_error')}")
raise tool_arguments_error
return tool_arguments_error

return ToolCalling(
tool_name=sanitize_tool_name(tool.name),
Expand Down
65 changes: 64 additions & 1 deletion lib/crewai/tests/tools/test_tool_usage.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -689,6 +689,69 @@ def event_handler(source, event):
assert "must be a valid dictionary" in event.error


def _build_tool_usage_for_original_calling() -> ToolUsage:
"""Build a minimal ToolUsage instance for exercising _original_tool_calling."""
mock_agent = MagicMock()
mock_agent.key = "test_key"
mock_agent.role = "test_role"
mock_agent.verbose = False

class TestTool(BaseTool):
name: str = "Test Tool"
description: str = "A test tool"

def _run(self, input: dict) -> str:
return "test result"

return ToolUsage(
tools_handler=MagicMock(),
tools=[TestTool()],
task=MagicMock(),
function_calling_llm=None,
agent=mock_agent,
action=MagicMock(tool="test_tool", tool_input='{"key": "value"}'),
)


def test_original_tool_calling_valid_dict_returns_tool_calling():
"""A valid dict from _validate_tool_input yields a ToolCalling result."""
tool_usage = _build_tool_usage_for_original_calling()

result = tool_usage._original_tool_calling("tool_string", raise_error=False)

assert isinstance(result, ToolCalling)
assert result.tool_name == "test_tool"
assert result.arguments == {"key": "value"}


def test_original_tool_calling_non_dict_raises_tool_usage_error():
"""Non-dict validation result with raise_error=True raises ToolUsageError.

Regression test: previously this path used a bare ``raise`` outside any
``except`` block, producing ``RuntimeError: No active exception to re-raise``
instead of a meaningful error.
"""
tool_usage = _build_tool_usage_for_original_calling()

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_non_dict_returns_error_when_not_raising():
"""Non-dict validation result with raise_error=False returns a ToolUsageError."""
tool_usage = _build_tool_usage_for_original_calling()

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_usage_finished_event_with_result():
"""Test that ToolUsageFinishedEvent is emitted with correct result attributes."""
mock_agent = MagicMock()
Expand Down