From 9b3190d07bc1cd65d6b01560548c49a738985ca4 Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Mon, 2 Nov 2026 16:05:01 -0800 Subject: [PATCH] fix(responses): keep a background poll's first terminal status and take a first-chunk error code from its HTTP status --- .../response_polling/background_streaming.py | 10 +++---- .../test_response_polling_handler.py | 27 +++++++++++++++++++ 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/litellm/proxy/response_polling/background_streaming.py b/litellm/proxy/response_polling/background_streaming.py index 73190ad8df8..76f713ae963 100644 --- a/litellm/proxy/response_polling/background_streaming.py +++ b/litellm/proxy/response_polling/background_streaming.py @@ -111,7 +111,8 @@ def _first_chunk_error(response: object) -> Mapping[str, object] | None: return None if response.status_code < HTTPStatus.BAD_REQUEST: return None - return responses_error_fields(sse_error_object(bytes(response.body)) or {"code": response.status_code}) + error_obj: Final = sse_error_object(bytes(response.body)) or {} + return responses_error_fields({**error_obj, "code": error_obj.get("code") or str(response.status_code)}) async def _never_receive() -> Message: @@ -387,12 +388,7 @@ async def background_streaming_task( status="in_progress", ) - elif event_type in ( - "response.completed", - "response.failed", - "response.incomplete", - "response.cancelled", - ): + elif terminal_status is None and event_type in _event_to_status: # Terminal event - extract all ResponsesAPIResponse fields # https://platform.openai.com/docs/api-reference/responses-streaming finished_at = perf_counter() diff --git a/tests/unit/proxy/response_polling/test_response_polling_handler.py b/tests/unit/proxy/response_polling/test_response_polling_handler.py index 752dfc5f124..01fb8eac842 100644 --- a/tests/unit/proxy/response_polling/test_response_polling_handler.py +++ b/tests/unit/proxy/response_polling/test_response_polling_handler.py @@ -1736,6 +1736,26 @@ class TestBackgroundStreamingTerminalEvents: None, None, ), + ( + ( + 'data: {"type": "response.completed", "response": {"status": "completed", "usage": {"input_tokens": 3}}}\n\n', + 'data: {"type": "response.failed", "response": {"status": "failed", ' + '"error": {"code": "server_error", "message": "late"}}}\n\n', + ), + "completed", + None, + {"input_tokens": 3}, + ), + ( + ( + 'data: {"type": "response.failed", "response": {"status": "failed", ' + '"error": {"code": "server_error", "message": "first"}, "usage": {"input_tokens": 3}}}\n\n', + 'data: {"type": "response.completed", "response": {"status": "completed", "usage": {"input_tokens": 9}}}\n\n', + ), + "failed", + {"code": "server_error", "message": "first"}, + {"input_tokens": 3}, + ), ], ids=[ "error_frame_after_a_delta", @@ -1753,6 +1773,8 @@ class TestBackgroundStreamingTerminalEvents: "done_behind_a_delta_in_one_chunk_then_completed", "completed_then_failed_in_one_chunk", "event_line_sent_as_a_data_line_of_one_event", + "completed_then_failed_in_the_next_chunk", + "failed_then_completed_in_the_next_chunk", ], ) async def test_a_poll_stores_what_its_stream_ended_with( @@ -1837,6 +1859,11 @@ class TestBackgroundStreamingTerminalEvents: json.dumps({"error": {"type": "guardrail_scan_error", "code": 403}}), {"code": "permission_error", "message": "Forbidden"}, ), + ( + 404, + json.dumps({"error": {"message": "model not found", "type": "invalid_request_error", "param": None}}), + {"code": "not_found_error", "message": "model not found"}, + ), ], ) async def test_a_first_chunk_error_polls_failed_like_a_late_one(