fix: send tool output to model when function tool raises exception#3373
Draft
bbiiggjjuu wants to merge 4 commits into
Draft
fix: send tool output to model when function tool raises exception#3373bbiiggjjuu wants to merge 4 commits into
bbiiggjjuu wants to merge 4 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR ensures realtime sessions don’t leave the model waiting indefinitely when a known function tool call (or a handoff tool call) raises an exception. It mirrors the “unknown tool” handling added in #3287 by sending a RealtimeModelSendToolOutput(start_response=True) to the model before re-raising the exception.
Changes:
- Wrap known function tool invocation in a
try/exceptand send model-visible error tool output before re-raising. - Apply the same protection to the handoff invocation path.
- Update session tests to expect a tool output to be sent when timeouts/exceptions are raised.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
src/agents/realtime/session.py |
Sends a model-visible tool output when a known tool/handoff raises, before re-raising. |
tests/realtime/test_session.py |
Updates expectations to assert a tool output is sent for raised timeouts/exceptions. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+703
to
+707
| except Exception: | ||
| await self._model.send_event( | ||
| RealtimeModelSendToolOutput( | ||
| tool_call=event, | ||
| output=f"Tool '{event.name}' failed", |
Comment on lines
+744
to
+748
| except Exception: | ||
| await self._model.send_event( | ||
| RealtimeModelSendToolOutput( | ||
| tool_call=event, | ||
| output=f"Handoff '{event.name}' failed", |
| await session._handle_tool_call(tool_call_event) | ||
|
|
||
| assert len(mock_model.sent_tool_outputs) == 0 | ||
| assert len(mock_model.sent_tool_outputs) == 1 |
| assert isinstance(session._stored_exception, ToolTimeoutError) | ||
| assert session._stored_exception.tool_name == "slow_tool" | ||
| assert len(mock_model.sent_tool_outputs) == 0 | ||
| assert len(mock_model.sent_tool_outputs) == 1 |
| # But no tool output should have been sent and no end event queued | ||
| assert len(mock_model.sent_tool_outputs) == 0 | ||
| # An error tool output should be sent to the model before re-raising | ||
| assert len(mock_model.sent_tool_outputs) == 1 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
When a known function tool invocation raises an exception (e.g.
ToolTimeoutErrorwith
timeout_behavior="raise_exception", or user-raisedValueError), the sessionnow sends
RealtimeModelSendToolOutputwith an error message andstart_response=Trueback to the model before re-raising the exception.
This mirrors the behavior introduced for unknown tool calls in #3287, and ensures
the model is not left waiting for a function call result that will never arrive.
The same protection is also applied to the handoff branch where
handoff.on_invoke_handoffmay raise.Tests
make format,make lint,'make tests' passmypyandpyrightpass on changed filesuv run pytest tests/realtime/test_session.py -k "tool_timeout or tool_exception" -v— 5/5 passmake tests— 4524 passed.The single failure (
tests/test_trace_processor.py::test_tracing_atexit_cleanup_timeout_preserves_process_exit_code_on_504)is a pre-existing environment-specific test unrelated to this change: it spawns a child process that exercises
httpxretry behavior during trace export, and the child process timed out undersubprocess.run(timeout=3.0)in this environment.It belongs to the tracing subsystem (
tests/test_trace_processor.py) and is not affected by changes tosrc/agents/realtime/session.pyortests/realtime/test_session.py,and even if I haven’t modified the code, this test doesn’t run on my local environment.Issue number
Closes #3356
Checks
make lintandmake format