Repository navigation
[Bugfix][Responses API] Reuse streamed item ids in final harmony response - #59859
Conversation
…onse When streaming with gpt-oss (harmony), responses_full_generator rebuilds the output items from the parsed harmony messages and mints fresh ids, so the response.completed event disagrees with the streamed output_item.added/done events. The simple path already reuses the streamed items (vllm-project#59307); do the equivalent for harmony by collecting the done items during streaming and overlaying their ids onto the rebuilt output, matched by (type, name) in order. Items without a streamed counterpart (zero-delta items whose done event is suppressed) keep their fresh id and stay in the output. Fixes vllm-project#59834 Signed-off-by: Yufeng He <40085740+he-yufeng@users.noreply.github.com>
Replace the cross-message (type, name) scan with per-message pairing. The harmony stream now records the items sent in output_item.done for each completed message, and the final response reuses their id/call_id only for the rebuilt items of that same message. A built-in python call (streamed as code_interpreter_call, rebuilt as reasoning) no longer takes the streamed id of the next reasoning item. Streamed harmony reasoning items now use the rs_ id prefix, matching the non-streaming and non-harmony paths. Signed-off-by: sfeng33 <4florafeng@gmail.com>
sfeng33
left a comment
There was a problem hiding this comment.
Thanks @he-yufeng for tracking down the harmony path. I pushed a commit that switches the id overlay from a (type, name) scan across all messages to per-message pairing. The scan can give an item another item's id: a built-in python call streams as code_interpreter_call but is rebuilt as reasoning, so it took the streamed id of the next reasoning item. Each completed harmony message now records its own output_item.done items, and only that message's rebuilt items reuse those ids.
|
/ci run --allow-stale |
|
✅ Triggered Buildkite CI #92773 for commit
|
CI selector (shadow): 3 test steps (3 jobs) instead of 38 (51 jobs)Shadow mode: this changes nothing about what CI runs. It shows what the evidence-based selector would pick for this PR, next to today's rules. How it works. Feedback welcome: reply here if it would skip a step this change needs, or runs something unrelated.
Selector would run (3)
Would skip (today's rules run them) (35)
Would add (today's rules do not run them) (0)none AMD mirrors: would skip (34)
AMD mirrors: would add (0)none 5 changed files · base |
Purpose
Fixes the harmony (gpt-oss) part of #59834; #59307 fixed the non-harmony path. When
/v1/responsesstreams,response.completedrebuilds its output from the harmony messages withharmony_to_response_output. That rebuild mints a new itemidand functioncall_id, which no longer match the ones sent inresponse.output_item.added/done, so strict clients abort._process_harmony_streaming_eventsrecords, for each completed harmony message, the items sent in itsoutput_item.doneevents (HarmonyContext.streamed_items_by_message).responses_full_generatormatches each message to its own record by object identity.reuse_streamed_item_idsthen copiesid/call_idonto that message's rebuilt items by type. Content andincompletestatus still come from the rebuild. Items that were never streamed (zero-delta) keep a new id.rs_prefix instead ofmsg_, matching the non-streaming path and the non-harmony path.Matching per message matters. A built-in
pythoncall streams ascode_interpreter_callbut is rebuilt asreasoning. If ids were matched across messages, that call would take the streamed id of the next reasoning item.Testing
vllm serve openai/gpt-oss-20b --no-enable-prefix-caching(1x H200), then the request from the issue:main (5f30fc7):
completedchanges every id.This PR:
completedkeeps the streamed ids.I ran the same check, 3 times each, on a forced tool call, a reasoning + text answer, and a
max_output_tokens=40cut-off (incomplete). Results: main had id mismatches in 9/9 runs, and this PR in 0/9.Other tests:
tests/entrypoints/openai/responses/test_serving_responses.py: 64 passed. The 2 new tests fail on main.tests/entrypoints/openai/responses/test_harmony.py(gpt-oss-20b e2e): 37 passed, 2 skipped, 1 xpassed on both main and this PR.No change to generated content: only item ids change, so no eval is needed.
Pull Request Checklist
I used vLLM's
/pr-checklistskill. (Mandatory for agents, optional for humans).AI assistance was used during the creation of this PR.
Design Fit: Minimizes impact on core components, reuses existing functionality, and justifies added complexity.
Testing and Validation: Validates the change and ensures any added tests are meaningful and reliable, with CI coverage or documented CI resource constraints and validation performed outside CI.
Code Quality and Style: Keeps code and comments clear and concise, and updates relevant documentation and examples.
Pull Request Contents: Includes a brief summary and relevant links, supports claims with evidence, explains root causes and implementation trade-offs, and follows the contributing guide.