From cf151f91ff9f73723720c3f5e84a873268317ff7 Mon Sep 17 00:00:00 2001 From: Sihan Sun Date: Sun, 10 May 2026 16:19:59 -0700 Subject: [PATCH] fix: #781 replace assertion in handoff() with UserError (#3339) --- src/agents/handoffs/__init__.py | 8 ++++---- src/agents/realtime/handoffs.py | 3 ++- tests/realtime/test_realtime_handoffs.py | 8 ++++++++ tests/test_handoff_tool.py | 24 ++++++++++++++++++++++++ 4 files changed, 38 insertions(+), 5 deletions(-) diff --git a/src/agents/handoffs/__init__.py b/src/agents/handoffs/__init__.py index 82d2e42c..b318414c 100644 --- a/src/agents/handoffs/__init__.py +++ b/src/agents/handoffs/__init__.py @@ -252,12 +252,12 @@ def handoff( hidden from the LLM at runtime. """ - assert (on_handoff and input_type) or not (on_handoff and input_type), ( - "You must provide either both on_handoff and input_type, or neither" - ) + if input_type is not None and on_handoff is None: + raise UserError("You must provide on_handoff when input_type is provided") type_adapter: TypeAdapter[Any] | None if input_type is not None: - assert callable(on_handoff), "on_handoff must be callable" + if not callable(on_handoff): + raise UserError("on_handoff must be callable") sig = inspect.signature(on_handoff) if len(sig.parameters) != 2: raise UserError("on_handoff must take two arguments: context and input") diff --git a/src/agents/realtime/handoffs.py b/src/agents/realtime/handoffs.py index 776b7c65..5074df72 100644 --- a/src/agents/realtime/handoffs.py +++ b/src/agents/realtime/handoffs.py @@ -92,7 +92,8 @@ def realtime_handoff( raise UserError("You must provide on_handoff when input_type is provided") type_adapter: TypeAdapter[Any] | None if input_type is not None: - assert callable(on_handoff), "on_handoff must be callable" + if not callable(on_handoff): + raise UserError("on_handoff must be callable") sig = inspect.signature(on_handoff) if len(sig.parameters) != 2: raise UserError("on_handoff must take two arguments: context and input") diff --git a/tests/realtime/test_realtime_handoffs.py b/tests/realtime/test_realtime_handoffs.py index fc8f5fbb..41cf7716 100644 --- a/tests/realtime/test_realtime_handoffs.py +++ b/tests/realtime/test_realtime_handoffs.py @@ -137,6 +137,14 @@ def test_realtime_handoff_input_type_requires_on_handoff(): realtime_handoff(rt, input_type=int) # type: ignore[call-overload] +def test_realtime_handoff_non_callable_on_handoff_raises_error(): + """Providing a non-callable on_handoff with input_type should raise UserError.""" + rt = RealtimeAgent(name="x") + + with pytest.raises(UserError, match="on_handoff must be callable"): + realtime_handoff(rt, on_handoff="not_a_function", input_type=int) # type: ignore[call-overload] + + @pytest.mark.asyncio async def test_realtime_handoff_missing_input_json_raises_model_error(): rt = RealtimeAgent(name="x") diff --git a/tests/test_handoff_tool.py b/tests/test_handoff_tool.py index 799418e7..b3622be7 100644 --- a/tests/test_handoff_tool.py +++ b/tests/test_handoff_tool.py @@ -252,6 +252,30 @@ async def test_invalid_on_handoff_raises_error(): handoff(agent, on_handoff=_on_handoff) # type: ignore +def test_input_type_without_on_handoff_raises_error(): + """Providing input_type without on_handoff should raise an error.""" + + class MyInput(BaseModel): + reason: str + + agent = Agent(name="test") + + with pytest.raises(UserError, match="You must provide on_handoff when input_type is provided"): + handoff(agent, input_type=MyInput) # type: ignore + + +def test_non_callable_on_handoff_with_input_type_raises_error(): + """Providing a non-callable on_handoff with input_type should raise an error.""" + + class MyInput(BaseModel): + reason: str + + agent = Agent(name="test") + + with pytest.raises(UserError, match="on_handoff must be callable"): + handoff(agent, on_handoff="not_a_function", input_type=MyInput) # type: ignore + + def test_handoff_input_data(): agent = Agent(name="test")