Repository navigation
Conversation
|
This pull request has merge conflicts that must be resolved before it can be |
2049693 to
ee46722
Compare
|
Documentation preview: https://vllm--63902.org.readthedocs.build/en/63902/ |
91a782a to
5bc7b88
Compare
|
This pull request has merge conflicts that must be resolved before it can be |
5bc7b88 to
d5b7df3
Compare
|
Thanks, @franciscojavierarceo! I think the overall shape looks good. However, I’d prefer to support streaming first and then implement non-streaming on top of it. In the Rust frontend, all the interfaces have been streaming-first from the start. We’d rather treat non-streaming as a degenerate case of streaming instead of having the two follow different paths. Would you mind if I merged PR 3 into this one so I can rework non-streaming to use the streaming path, rather than the other way around? |
yeah sgtm |
|
This pull request has merge conflicts that must be resolved before it can be |
Add `POST /v1/responses` on top of `ChatLlm::chat`: requests lower into a chat request, and `OutputItemStreamer` turns the chat event stream into Responses output items and SSE lifecycle events. Store-dependent features are rejected or return 404. Non-streaming requests are rejected for now. This squashes the streaming-first part of the original stack: - 9e714a4 [Rust Frontend] Add stateless non-streaming Responses API - f785541 fix: validate Responses controls and report model sampling defaults - 881a178 fix: adapt Responses to current Rust frontend contracts - ef5a082 fix: mark truncated Responses output items incomplete - 5bc7b88 fix: adapt Responses decoding and cache usage to upstream - 6698c5b [Rust Frontend] Add Responses streaming lifecycle - 062c680 test: cover Responses partial output after stream decode failure - cf47141 fix: adapt Responses streaming to current chat events - a2adabf fix: preserve final output item status in Responses streams Its non-streaming path returns in a later commit. Route tests that do not depend on the response mode now run through streaming. Co-authored-by: Canlin Guo <canlinguosdu@gmail.com> Co-authored-by: Codex <noreply@openai.com> Signed-off-by: Francisco Javier Arceo <4163062+franciscojavierarceo@users.noreply.github.com> Signed-off-by: Bugen Zhao <i@bugenzhao.com>
Replace raw JSON probing in the Responses route with typed DTOs that reuse the chat-completions definitions: - Tools are `ResponsesTool::Function(Function)`, the chat-completions `Function` flattened next to `type`, lowered through a shared `Function -> ChatTool` conversion. - `tool_choice` reuses `ToolChoiceValue`, `FunctionChoice`, and `ToolReference`. - Input items deserialize into typed items; a message item may omit `type`, mirroring Python's `input_item_parsing`. Errors keep the item index. - `truncation`, `service_tier`, `reasoning.summary`, and `text.verbosity` are serde enums instead of string allow-lists. Stream events become a typed enum serialized directly into SSE data, with `type` and `sequence_number` added by a wrapper. Lifecycle events share the response through `Arc` instead of converting it to `Value`. `function_call_arguments` events no longer carry a `content_index`, matching the SDK event shape. Unknown tool, tool-choice, and input-item types now fail deserialization, so errors name the JSON path instead of a `param`. The unread `store` field is dropped; `store=true` still executes. Add an async-openai typed-client test for streaming Responses. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Bugen Zhao <i@bugenzhao.com>
Remove logic the Responses route duplicated from lower layers: - Echo `temperature`, `top_p`, `max_output_tokens`, and the presence/frequency penalties from the sampling values sent to the engine, as Python does. `ChatEventStream::sampling()` exposes them, read from the prepared generate request in `ChatLlm::chat`, instead of the handler pre-filling model defaults and repeating vllm-text's precedence. The echoed output limit is the context-clamped one, and an omitted limit or penalty no longer disappears from the response. The `TextRequestProcessor::sampling_hints()` accessor goes away. - Drop the `enable_thinking` template kwarg injection. The HF renderer already derives it from `reasoning_effort`, and native renderers resolve reasoning controls through `ReasoningControl`. - Revert `#[serde(default)]` on `vllm_parser::Tool.parameters`. Wire tools now deserialize into the server `Function` DTO, which owns that default. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Bugen Zhao <i@bugenzhao.com>
The terminal streaming response rebuilt its output from the final chat message with fresh IDs and patched the streamed IDs back by item kind and order, while failed streams used the streamer's completed items instead. Make the streamer's completed items the only source of `output`, and drop the rebuild and the ID reconciliation. This also drops an empty reasoning block from the final output, which streaming never emitted as an item. Derive the next output index and the deferred `output_item.done` from the completed items instead of keeping copies of them, and drop a function-call field that was written but never read. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Bugen Zhao <i@bugenzhao.com>
When generation hits the output limit, the last output item is the truncated one. Message and function-call items were marked `incomplete`, but a reasoning item kept `completed`, so a request that ran out of tokens while reasoning returned an incomplete response whose only item claimed to be complete. Python passes the same `incomplete` status to reasoning items. Extend the length-limit test to two tool calls so it also checks that only the last one is marked, and cover reasoning-only truncation in both modes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CJckCDvqfKYQRRwCYtsCmk Signed-off-by: Bugen Zhao <i@bugenzhao.com>
The Responses request type lacked several vLLM controls that Python declares, so serde dropped them and requests ran with the defaults: `min_p` was ignored, and `spaces_between_special_tokens: false`, `media_io_kwargs`, `mm_processor_kwargs`, and `enable_response_messages: true` were accepted without effect. Forward `min_p` to the shared sampling parameters, and reject the others with 400 as Chat Completions does. Prompt-cache hints and `safety_identifier` stay ignored, which Python documents as no-ops. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CJckCDvqfKYQRRwCYtsCmk Signed-off-by: Bugen Zhao <i@bugenzhao.com>
The response object omitted every unset optional field, so the initial lifecycle objects had no `usage`, completed ones had no `incomplete_details`, and unset echoes such as `instructions` or `reasoning` disappeared. Python and the OpenAI schema keep these fields present with `null`. Serialize them as `null`, and keep omitting `metrics` when it is disabled, as Python does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CJckCDvqfKYQRRwCYtsCmk Signed-off-by: Bugen Zhao <i@bugenzhao.com>
d5b7df3 to
7af1e25
Compare
7af1e25 to
a1d8bd8
Compare
BugenZhao
left a comment
There was a problem hiding this comment.
I pushed several commits to make the DTOs more strongly typed and to share as much code as possible between the non-streaming and streaming paths.
Thanks again @franciscojavierarceo!
|
/ci run |
|
✅ Triggered Buildkite CI #99513 for commit ℹ️ This PR is 5 commits behind upstream |
Add `usage.input_tokens_details.cache_write_tokens`, the prompt tokens newly written to the prefix cache, as the Python frontend has since vllm-project#60973. The OpenAI SDK `Response` type requires the field, so strict validation of Rust responses failed without it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CJckCDvqfKYQRRwCYtsCmk Signed-off-by: Bugen Zhao <i@bugenzhao.com>
|
/ci run |
|
✅ Triggered Buildkite CI #99516 for commit |
…ream test The stateless Responses API (vllm-project#63902) pinned the trailing `}\n</tool_call` text the tool parser leaked at the end of output as an extra message item. With incomplete tool-call markup dropped at the end of output, the stream ends after the function call item. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: richard-for-bugen[bot] <342439978+richard-for-bugen[bot]@users.noreply.github.com>
…the Rust frontend vllm-project#63902 added the stateless Responses API to the Rust frontend. A new 35 GB step (the Python Responses step's device) runs test_basic.py, test_function_call.py, test_structured_output.py and test_simple.py against it; on a GB200 run these pass apart from the excluded cases: output logprobs and enable_response_messages (an explicit 400), namespace tools (rejected at deserialization) and the store-backed test_stateful.py. test_thinking_token_budget_responses_api stays skipped under the Rust frontend; its reason now names the actual gap, since /v1/responses ignores thinking_token_budget. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: richard-for-bugen[bot] <342439978+richard-for-bugen[bot]@users.noreply.github.com>
With VLLM_ENABLE_RESPONSES_API_STORE=1, the Python frontend keeps Responses API responses in memory, so store=true responses can be retrieved and cancelled, background=true runs, and previous_response_id resolves. The Rust frontend serves the stateless Responses API (vllm-project#63902) and keeps no store: it downgrades store=true like Python does without the variable, so a deployment that sets it would silently lose retrieval. Reject a non-zero value at startup instead. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: richard-for-bugen[bot] <342439978+richard-for-bugen[bot]@users.noreply.github.com>
…the Rust frontend vllm-project#63902 added the stateless Responses API to the Rust frontend. A new 35 GB step (the Python Responses step's device) runs test_basic.py, test_function_call.py, test_structured_output.py and test_simple.py against it; on a GB200 run these pass apart from the excluded cases: output logprobs and enable_response_messages (an explicit 400), namespace tools (rejected at deserialization) and the store-backed test_stateful.py. test_thinking_token_budget_responses_api stays skipped under the Rust frontend; its reason now names the actual gap, since /v1/responses ignores thinking_token_budget. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: richard-for-bugen[bot] <342439978+richard-for-bugen[bot]@users.noreply.github.com>
Overview
Add the stateless Responses API (
POST /v1/responses, streaming and non-streaming) to the Rust frontend on top of the existingChatLlmpipeline.The original stack split this endpoint into non-streaming (2/3, this PR) followed by streaming (#63903). The Rust frontend is streaming-first:
ChatEventStreamis the primary interface, and non-streaming Chat Completions is a fold over it. As agreed with @franciscojavierarceo, this PR now carries both layers in that order: streaming first, then non-streaming as a fold over the same state machine. It supersedes #63903. Layer 1/3 (#63901) has merged.Commits
Each commit builds and passes its tests on its own.
da7eeffe37(@franciscojavierarceo) — stateless streaming Responses: request lowering intoChatRequest,OutputItemStreamerfor the item lifecycle, store-dependent features rejected or 404. Squashes the streaming-first part of the original stack (listed in the commit message); non-streaming returns 400 at this point.7e41889ed4— typed wire DTOs and stream events: tools and tool choice reuse the Chat Completions definitions, input items deserialize into typed items (a message item may omittype, like Python), and SSE events are a typed enum withtypeandsequence_numberadded by a wrapper.2ea078d012— defer to shared layers: the response echoes the sampling values the engine actually uses (model defaults applied,max_output_tokensclamped to the context), like Python'sResponsesResponse.from_request(sampling_params), viaChatEventStream::sampling().1c1bbe1fe5— the streamer is the only source ofoutput; output indices and the deferredoutput_item.donederive from completed items.10448da198(co-authored by @franciscojavierarceo) — non-streaming as a fold: drive the sameOutputItemStreamer, discard its events, and return the terminal response. The Responses terminal event carries the complete response, sooutputfollows one set of item rules in both modes. Errors still map to HTTP statuses (engine errors 400/503, a broken stream 500) instead of afailedresponse.8fbae250f3— per-request timingmetrics, as in Python Responses.4da559a6db— a truncated reasoning item is also markedincomplete, as in Python.f28f024629— remaining vLLM request controls: forwardmin_p; rejectspaces_between_special_tokens: false,media_io_kwargs,mm_processor_kwargs, andenable_response_messages: trueinstead of dropping them.a1d8bd8364— unset response fields serialize asnull, as in Python.99446a2d75— reportusage.input_tokens_details.cache_write_tokens, as Python has since [Bugfix][Responses API] Complete cache-write token accounting #60973; the OpenAI SDKResponsetype requires it. Found by the e2e below.The CI bot merged
maininto the branch asbe0cafa775before commit 10.Behavior
input.store: trueexecutes without storing (Python's implicit downgrade);background: trueis 400;previous_response_id, retrieval, and cancellation are 404.max_tool_callslimits built-in tool calls only, so with function tools alone it is accepted and echoed.reasoning.summary,text.verbosity,include: ["reasoning.encrypted_content"], andservice_tierare accepted compatibility inputs (Codex sends them by default) without producing summaries, applying verbosity, encrypting reasoning, or scheduling guarantees.response.failedcarrying the partial output, instead of a generic error frame.allowed_tools, and output logprobs (rejected with 400).Validation
Rebased onto
eb239e34c2.cargo fmt --check,cargo clippy --workspace --all-targets --all-features, and the server Responses tests pass (45 → 52 tests across the stack).cargo test --workspacehas 2483 passed; the same 27 tests also fail onmainin this environment (grammar replay fixtures and two environment-dependent tests).incompleteon the last item only), engine errors mapped to 400/503, 404s for stored-response features, and typed-client (async-openai) streaming.cache_write_tokens: a route assertion fails without the fix and passes with it.E2E on GB200 at
a1d8bd8364(before commit 10), Qwen/Qwen3.5-27B-FP8 on one GPU, 64k context,--language-model-only,qwen3reasoning andqwen3_codertool parsers, Python engine from the matchingmainwheel:temperature/top_pequal the model's generation config when unset; a token limit marks the response and its last itemincomplete; every streamedoutput_item.doneequals the terminaloutputwith matching IDs and contiguoussequence_numbers; function call andfunction_call_outputreplay in both modes; 404 for retrieval andprevious_response_id. The only schema gap was the missingcache_write_tokens, fixed in commit 10./v1/responsesand/v1/chat/completionsproduce identical prompt token counts, reasoning, and text for the same message.cat nonce.txtand replied with the exact nonce, ending inturn.completed. Each second request replayed two leadingdevelopermessages, the user turn, a plaintext reasoning item, thefunction_call, and itsfunction_call_output; all requests returned 200 withresponse.completed.This PR is 17 files, +4478 / -33; route tests are about 1.2k of that.
Original description (@franciscojavierarceo)
Overview
add stateless, non-streaming
POST /v1/responsesthrough the existingvllm-chatpipeline, with input replay, function tools, reasoning, and usage.review order: 1/3: renderer #63901 → 2/3: non-streaming #63902 → 3/3: streaming #63903. all three upstream review PRs target
main; later diffs include earlier layers.this PR includes and depends on #63901. the fork layer shows its isolated diff.
Claims
stream: truewith HTTP 400 onparam: streamuntil layer 3.Validation
Latest upstream synchronization — October 28
current head:
d5b7df30aa; rebased onto upstreamf4fd1ab68e. the fork keeps its native stacked bases; upstream PRs still targetmain.NotFoundandResponseNotFoundretain their existing wire envelopes; the LoRA/gRPC and Responses HTTP regressions pass.cargo test --locked -p vllm-server -p vllm-text --lib -- --test-threads=1: 579 server + 121 text passed, one existing text test ignored.git diff --check: passed.cargo nextestremains unavailable.f4fd1ab68e.the model-backed HTTP/Codex evidence below predates this synchronization (complete-stack head
3db4c9abf0); it was not repeated after this rebase. today's evidence covers compilation and deterministic wire/route/renderer regressions, not new model qualification. CI reruns on these new heads are separate from the earlier successful workflows. agentic-api was not changed or pushed.Earlier review and runtime evidence
reviewed and revised on October 27; non-streaming head:
91a782aa08.incompletestatus but a final message item markedcompleted.cargo test --locked -p vllm-server -p vllm-text --lib -- --test-threads=1: 552 server + 118 text passed, one existing text test ignored.git diff --check: passed.f8f7634cb4; the fork's isolated stack bases are preserved.commands ran from
rust/withOPENSSL_DIRpointing to the local OpenSSL build.cargo nextestwas unavailable. full-stack tests ran serially because of the previously reproduced shared chat audio-metrics collector collision.fresh model-backed HTTP and real Codex checks passed on the revised complete stack, as recorded in #63903. this intermediate branch was tested separately with the in-process HTTP route harness; it does not contain streaming.
Details
Responses owns its wire envelopes and conversions while reusing the chat generation path and matching shared audio/tool primitives. the rebase preserves upstream's other route registrations and error serialization rather than restoring the older envelope.
streamselects non-streaming. omitted/null watermarking and explicit overrides follow current Chat Completions semantics.includeoutputs, and nonzerotop_logprobsare rejected.previous_response_iduses the current sharedNotFoundErrorenvelope: HTTP 404,param: response_id, numericcode: 404.the frontend remains stateless: clients replay history, and agentic-api owns persistence and tool execution. the shared native adapter tracked by vllm-project/agentic-api#359 and vllm-project/agentic-api#382 is still unfinished. accepted reasoning-summary, text-verbosity, and encrypted-reasoning inputs are documented compatibility no-ops; valid service tiers are echoed without scheduling guarantees. persistence, retrieval/cancellation, built-in/custom/namespace tool execution, Harmony rendering, output logprobs, and
allowed_toolsare out of scope. the rendered Responses documentation describes the supported subset.these are the existing #58783 layers opened upstream at @BugenZhao's request, rather than competing implementations. the original authored history and discussion remain in #58783. @gcanlin's #53380 implementation is credited in the endpoint and streaming commits. #56124 covers render endpoints and #51535 covers chat
allowed_tools; neither duplicates this endpoint.this layer is 13 files, +2987 / -1 relative to #63901. tests stay under the test-only route module. agentic-api compatibility follow-ups remain local and unpushed. Codex assistance was used; human review of every changed line and independent validation are required before merge.
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.
BEFORE SUBMITTING, PLEASE READ https://docs.vllm.ai/en/latest/contributing (anything written below this line will be removed by GitHub Actions)