From 603c3b2745cb0e36b5730be6bab7e8ca3feff56a Mon Sep 17 00:00:00 2001 From: Pratik Dhanave Date: Tue, 13 Oct 2026 18:19:03 +0530 Subject: [PATCH] Fix: readable TypeError for union response_type mismatch (no __name__ crash) send_request_info_response built the mismatch message with event.response_type.__name__. response_type may legitimately be a union (is_instance_of accepts type | UnionType | Any, and callers pass e.g. int | str). On Python 3.10/3.11 types.UnionType has no __name__, so the error path itself raised AttributeError instead of the intended TypeError; on 3.12+ it only yields the unhelpful "Union". Use response_type.__name__ for plain classes and str(response_type) for unions/generics, so the message names the actual type ("int | str", "list[int]") on every supported Python version. Add a regression test (response_type=int | str, mismatched response) asserting the message names the union. --- .../_workflows/_runner_context.py | 10 ++++++++- .../test_request_info_event_rehydrate.py | 21 +++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/python/packages/core/agent_framework/_workflows/_runner_context.py b/python/packages/core/agent_framework/_workflows/_runner_context.py index edbbfea50c9..205d3bd9538 100644 --- a/python/packages/core/agent_framework/_workflows/_runner_context.py +++ b/python/packages/core/agent_framework/_workflows/_runner_context.py @@ -585,9 +585,17 @@ async def send_request_info_response(self, request_id: str, response: Any) -> No # Validate response type if specified if event.response_type and not is_instance_of(response, event.response_type): + # response_type may be a union/generic (e.g. int | str), which has no usable + # __name__ on Python 3.10/3.11 (AttributeError) and only yields "Union" on 3.12+. + # Use the readable repr for anything that isn't a plain class. + expected = ( + event.response_type.__name__ + if isinstance(event.response_type, type) + else str(event.response_type) + ) raise TypeError( f"Response type mismatch for request_id {request_id}: " - f"expected {event.response_type.__name__}, got {type(response).__name__}" + f"expected {expected}, got {type(response).__name__}" ) source_executor_id = event.source_executor_id diff --git a/python/packages/core/tests/workflow/test_request_info_event_rehydrate.py b/python/packages/core/tests/workflow/test_request_info_event_rehydrate.py index 1be0a75f58e..88cb3dd7049 100644 --- a/python/packages/core/tests/workflow/test_request_info_event_rehydrate.py +++ b/python/packages/core/tests/workflow/test_request_info_event_rehydrate.py @@ -532,3 +532,24 @@ async def test_apply_checkpoint_preserves_streaming_flag() -> None: await target.apply_checkpoint(checkpoint) assert target.is_streaming() is True + + +async def test_send_request_info_response_union_type_mismatch_message() -> None: + """A union response_type must produce a readable TypeError, not crash on __name__. + + ``types.UnionType`` (e.g. ``int | str``) has no ``__name__`` on Python 3.10/3.11 + (AttributeError) and only yields the unhelpful ``"Union"`` on 3.12+. The mismatch + message should name the union itself. + """ + event = WorkflowEvent.request_info( + request_id="union-req", + source_executor_id="gateway", + request_data=MockRequest(), + response_type=int | str, + ) + + runner_context = InProcRunnerContext(InMemoryCheckpointStorage()) + await runner_context.add_request_info_event(event) + + with pytest.raises(TypeError, match=r"expected int \| str, got float"): + await runner_context.send_request_info_response("union-req", 3.14)