Skip to content

fix(streaming): assemble chat completions content parts in content index order - #4236

Merged
seratch merged 1 commit into
openai:mainfrom
adityasingh2400:fix-stream-content-part-index-order
Aug 6, 2026
Merged

fix(streaming): assemble chat completions content parts in content index order#4236
seratch merged 1 commit into
openai:mainfrom
adityasingh2400:fix-stream-content-part-index-order

Conversation

@adityasingh2400

@adityasingh2400 adityasingh2400 commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

ChatCmplStreamHandler assigns content indexes in the order the parts open: a refusal delta that arrives before any text delta gets content_index=0 and the later text gets content_index=1. The completed assistant message, however, always appended the text part first and the refusal second, so response.completed contradicted the content_part.added, output_text.delta, and refusal.delta events consumers had already received. A consumer that uses content_index to index into the finished message's content list reads the wrong part.

The fix sorts the two parts by their already-assigned content index before appending them. Text-first streams are unchanged because the text part already holds index 0 there, so this only affects the refusal-before-text ordering that was inconsistent to begin with.

tests/models/test_openai_chatcompletions_stream.py::test_stream_handler_places_text_after_existing_refusal_part already streams a refusal chunk followed by a text chunk and already asserts that the text part is announced at content_index == 1. It then asserted the contradictory completed layout, which is the bug rather than intended behavior, so this updates it to require the completed content to line up with the announced indexes and adds the matching assertion that the refusal was announced at index 0. It fails on main with AssertionError: assert False, where False = isinstance(ResponseOutputText(...), ResponseOutputRefusal) and passes with this change.

make lint and make typecheck (mypy and pyright, 0 errors) are green, and the full suite is 6672 passed with 32 skipped. This is a refile of #3822 by @felmonon, which the stale bot closed for inactivity rather than on the merits. It is also the ordering issue @AmirF194 explicitly scoped out of #3757, calling it a separate pre-existing bug on main, so #4177 landing did not cover it.

…dex order

When a Chat Completions stream emits a refusal delta before any text delta, the
handler announces the refusal at content_index 0 and the text at content_index 1,
but the completed assistant message always appended the text part first. The
finished message then contradicted the indexes consumers had already received,
so replaying the raw events produced a different content layout from
response.completed. Sort the two parts by their content index before appending.
Text-first streams are unaffected because the text part already holds index 0.
@seratch seratch added this to the 0.20.x milestone Aug 6, 2026
@seratch
seratch merged commit 2de0178 into openai:main Aug 6, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants