Repository navigation
[Rust Frontend] Reject stream_options with null or unknown keys without streaming like Python - #61
Closed
richard-for-bugen[bot] wants to merge 1 commit into
Closed
richard-for-bugen[bot] wants to merge 1 commit into
richard-for-bugen[bot] wants to merge 1 commit into
Conversation
…ut streaming like Python
Python's chat and completions `validate_stream_options` test the raw JSON
(`if data.get("stream_options") and not data.get("stream")`), so with
streaming off they reject any `stream_options` object with a key, including
`{"include_usage": null}` and keys Python does not model, and accept only
`{}` and `null`. Rust checked the deserialized options, so an all-null or
unknown-key object counted as empty and was accepted.
`StreamOptions` now records at deserialization whether the object had any
key, and `is_empty` reports that.
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>
Owner
|
It’s ugly to distinguish between |
richard-for-bugen
Bot
deleted the
richard/parity-fix-stream-options-null-keys
branch
October 27, 2026 05:58
richard-for-bugen Bot
added a commit
that referenced
this pull request
Nov 5, 2026
…ust frontend vllm-project#65625 (n > 1) fixed the last Rust gaps in test_chat.py, so run it in a new 35 GB step (zephyr-7b-beta, as in the Python chat_completion step), with three relaxations for differences both frontends accept: - the last streamed chunk may carry only the finish_reason, so test_chat_streaming no longer requires content on it (the joined text is still compared with the non-streaming output), and the continuous-usage check lets that chunk repeat the completion-token count, like the usage-only chunk; - the Rust frontend treats an all-null stream_options like an omitted one without streaming (decided on #61), so that one case is skipped under it; - test_long_seed also accepts the Rust deserialization message for an out-of-range seed (the 400 is still asserted). test_invocations is deselected: it calls the SageMaker /invocations route, which the Rust frontend does not serve. 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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Overview
Python's chat and completions
validate_stream_optionscheck the raw JSON body:if data.get("stream_options") and not data.get("stream"). With streaming off, anystream_optionsobject that has a key is a 400, even when every value isnullor the key is one Python does not model; only{}andnullpass. vllm-project#62643 made Rust accept{}like Python, but it decides emptiness on the deserialized options, so{"include_usage": null}and{"include_obfuscation": true}(an OpenAI option neither frontend models) count as empty and are accepted.The OpenAI API agrees with Python on the all-null case: with
stream=false, a live probe returns 400 "The 'stream_options' parameter is only allowed when 'stream' is enabled." for{},{"include_usage": null}and{"include_usage": false}, and 200 for an absent ornullstream_options. This PR keeps Python's acceptance of{}and rejects the rest like both.stream=falseandstream_optionsnull{}{"include_usage": false}{"include_usage": null}{"include_obfuscation": true}/inference/v1/generateis unchanged: Python's generate route has nostream_optionscheck.Ownership and layering
The rule is about the raw request object, so it is captured where that object is parsed:
StreamOptions(the shared HTTP DTO inroutes/openai/utils/types.rs) records at deserialization whether the object had any key, andis_emptyreports that. The three existing consumers ofis_empty(chatvalidate_request_compat, chatvalidate_chat_cross_parameters, completionsvalidate_request_compat) are unchanged. The customDeserializereads the object into a JSON map once and derives the two typed fields from it, the same shape as the existingChatMessagedeserializer.Validation
Commands from
.buildkite/scripts/run-rust-frontend-cargo-ci.sh(style-clippyandtest), run from the repo root with a per-worktree target dir:validate_request_compat_rejects_stream_options_without_streaminggain{"include_usage": null}and{"include_obfuscation": true}; the chat test runs the request through both chat checks. Both tests fail with theStreamOptionschange reverted.validate_request_compat_accepts_empty_stream_options_without_streaming(chat and completions) still pins{}.validate_request_compat_accepts_stream_options_with_streaming(chat and completions) pins that the same null and unknown-key objects pass whenstream=true.StreamOptionsas struct literals; they now parse it from JSON, since emptiness is a property of the request object.Python test pinning the behavior:
tests/entrypoints/openai/chat_completion/test_chat.py::test_chat_completion_stream_options(not yet in Rust CI; the file has other open Rust gaps).A wrongly typed option, such as
{"include_usage": "yes"}, still fails body deserialization; the typed fields are parsed by a derivedDeserializefrom the same map.Codex review (
gpt-6.1-sol, high): approve with nits. It confirmed the gap against the Python request models and a temporary HTTP probe (base returns 200 forstream=falsewith{"include_usage": null}; all 60 probe combinations across chat, completions, both render routes and generate pass at the head).accepts_stream_options_with_streamingtests. They parse the whole request from JSON, which is where such a deserializer would fail, so they stay at the validator level instead of the HTTP route tests.Not a duplicate: no open upstream PR changes this check; vllm-project#61275 adds a new Python option and does not touch the validation.