Skip to content

fix(openai): capture ResponseFailed errors in stream mode - #1582

Merged
gold-silver-copper merged 5 commits into
0xPlaygrounds:mainfrom
gabrielrondon:fix/1562-stream-error-capture
Apr 2, 2026
Merged

fix(openai): capture ResponseFailed errors in stream mode#1582
gold-silver-copper merged 5 commits into
0xPlaygrounds:mainfrom
gabrielrondon:fix/1562-stream-error-capture

Conversation

@gabrielrondon

Copy link
Copy Markdown
Contributor

Fixes #1562

When OpenAI returns an error during streaming (e.g. context_length_exceeded), the response chunk arrives as ResponseFailed but was silently ignored by the else branch, resulting in an empty FinalResponse with zero tokens.

This change replaces the if-let with an explicit match on ResponseChunkKind:

  • ResponseCompleted: existing behavior (record span, extract usage)
  • ResponseFailed | ResponseIncomplete: extract response.error (code + message) and yield CompletionError::ProviderError, consistent with how SSE errors and the WebSocket implementation handle failures
  • Other variants: continue (no change)

Fixes 0xPlaygrounds#1562

When OpenAI returns an error during streaming (e.g. context_length_exceeded),
the response chunk arrives as ResponseFailed but was silently ignored by
the else branch, resulting in an empty FinalResponse with zero tokens.

Replace the if-let with an explicit match on ResponseChunkKind:
- ResponseCompleted: existing behavior (record span, extract usage)
- ResponseFailed | ResponseIncomplete: extract response.error (code + message)
  and yield CompletionError::ProviderError, consistent with how SSE errors
  and the WebSocket implementation handle failures
- Other variants: continue (no change)
@gold-silver-copper gold-silver-copper self-assigned this Apr 1, 2026
@gold-silver-copper

Copy link
Copy Markdown
Contributor

I pushed a follow-up to keep the streaming path closer to the existing websocket implementation and to add regression coverage for the original bug in #1562.

The core behavior change is the same: when the OpenAI Responses API ends a stream with response.failed or response.incomplete, we now surface that as CompletionError::ProviderError(...) instead of falling through and returning an empty FinalResponse with zero tokens.

I also refactored the streaming-side error formatting into small local helpers so the architecture matches websocket more closely:

  • empty error.code returns just the provider message
  • non-empty error.code returns {code}: {message}
  • response.incomplete uses incomplete_details.reason

I also removed the extra tracing::error! here, since these are provider-declared terminal outcomes rather than transport failures, and the websocket path already returns ProviderError without separately logging them.

Finally, I added streaming regression tests using MockStreamingClient against the real ResponsesCompletionModel::stream() path. The new tests cover:

  • response.failed with an empty error code
  • response.failed with a non-empty code
  • response.incomplete with incomplete_details.reason
  • response.completed still populating final usage after the refactor

I kept the scope local. Preserving structured provider errors (code / message) on CompletionError would be a broader API change, so this PR still follows the existing CompletionError::ProviderError(String) contract while fixing the original stream-mode bug.

@FayCarsons FayCarsons left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM !

@gold-silver-copper
gold-silver-copper added this pull request to the merge queue Apr 2, 2026
Merged via the queue into 0xPlaygrounds:main with commit 31bb067 Apr 2, 2026
6 checks passed
@github-actions github-actions Bot mentioned this pull request Apr 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Errors are not captured when using OpenAI in stream mode.

3 participants