diff --git a/src/agents/tool.py b/src/agents/tool.py index 393db09f56..61ba6d2f42 100644 --- a/src/agents/tool.py +++ b/src/agents/tool.py @@ -1391,6 +1391,21 @@ def _extract_tool_argument_json_error(error: Exception) -> json.JSONDecodeError return _extract_json_decode_error(error) +def _is_tool_argument_input_error(error: Exception) -> bool: + """Return True when an error represents a non-object JSON input for a function tool. + + Only the non-object case is matched here: schema validation failures share the + `Invalid JSON input for tool` prefix and should continue to flow through the + regular validation path rather than be reported as JSON parse errors. + """ + if not isinstance(error, ModelBehaviorError): + return False + text = str(error) + return text.startswith("Invalid JSON input for tool") and text.endswith( + "expected a JSON object" + ) + + def _build_handled_function_tool_error_handler( *, span_message: str, @@ -1411,6 +1426,11 @@ def _on_handled_error( if json_decode_error is not None and span_message_for_json_decode_error is not None: resolved_span_message = span_message_for_json_decode_error span_error_detail = str(json_decode_error) + elif span_message_for_json_decode_error is not None and _is_tool_argument_input_error( + error + ): + resolved_span_message = span_message_for_json_decode_error + span_error_detail = str(error) else: resolved_span_message = span_message span_error_detail = str(error) @@ -1481,6 +1501,12 @@ def default_tool_error_function(ctx: RunContextWrapper[Any], error: Exception) - "Please try again with valid JSON. " f"Error: {json_decode_error}" ) + if _is_tool_argument_input_error(error): + return ( + "An error occurred while parsing tool arguments. " + "Please try again with valid JSON. " + f"Error: {error}" + ) return f"An error occurred while running the tool. Please try again. Error: {str(error)}" diff --git a/tests/test_function_tool.py b/tests/test_function_tool.py index f03996a30e..2f486b5a81 100644 --- a/tests/test_function_tool.py +++ b/tests/test_function_tool.py @@ -759,6 +759,57 @@ def echo(value: str) -> str: ) +@pytest.mark.asyncio +@pytest.mark.parametrize("input_json", ["[]", '"value"', "123", "null", "true"]) +async def test_default_error_function_treats_non_object_input_as_json_error( + input_json: str, +) -> None: + def echo(value: str) -> str: + return value + + tool = function_tool(echo, name_override="echo_tool") + + result = await tool.on_invoke_tool( + ToolContext( + None, + tool_name="echo_tool", + tool_call_id="1", + tool_arguments=input_json, + ), + input_json, + ) + + assert isinstance(result, str) + assert result.startswith("An error occurred while parsing tool arguments.") + assert "expected a JSON object" in result + + +@pytest.mark.asyncio +async def test_default_error_function_does_not_route_schema_validation_as_json_error() -> None: + # Schema validation errors share the `Invalid JSON input for tool` prefix + # but should continue to flow through the generic error path, not the + # "An error occurred while parsing tool arguments" path used for malformed + # JSON / non-object inputs. + def echo(value: str) -> str: + return value + + tool = function_tool(echo, name_override="echo_tool") + + result = await tool.on_invoke_tool( + ToolContext( + None, + tool_name="echo_tool", + tool_call_id="1", + tool_arguments='{"value": 123}', + ), + '{"wrong_field": "x"}', + ) + + assert isinstance(result, str) + assert "Invalid JSON input for tool" in result + assert not result.startswith("An error occurred while parsing tool arguments.") + + @pytest.mark.asyncio async def test_default_failure_error_function_survives_deepcopy() -> None: def boom() -> None: