fix(responses): keep a background poll's first terminal status and take a first-chunk error code from its HTTP status

This commit is contained in:
mateo-berri 2026-11-02 16:05:01 -08:00
parent 5c59269e02
commit 9b3190d07b
2 changed files with 30 additions and 7 deletions

View file

@ -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()

View file

@ -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(