[Bugfix][Frontend] Reject stop strings on --tokens-only servers instead of silently ignoring them (#57058)
Signed-off-by: Shimi Bandiel <shimib@google.com> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Cyrus Leung <tlleungac@connect.ust.hk> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Shimi Bandiel committed
Sep 17, 2026 at 15:02 UTC
e8a2a0a8bbe675885ced6efddd49c1da0ff0a218
2 files changed
+35
tests/entrypoints/scale_out/token_in_token_out/test_serving_tokens.py
+25
@@ -422,6 +422,31 @@ async def test_stop_string_workflow(client, tokenizer, messages):
422
assert generate_res == completions_res
423
424
425
+@pytest.mark.asyncio
426
+@pytest.mark.skipif(
427
+ envs.VLLM_USE_RUST_FRONTEND,
428
+ reason="--tokens-only is not supported by the Rust frontend",
429
+)
430
+@pytest.mark.parametrize("server", [["--tokens-only"]], indirect=True)
431
+async def test_stop_strings_rejected_when_tokens_only(client):
432
+ """--tokens-only forces detokenize=False after request validation, so
433
+ stop strings must be rejected explicitly rather than silently ignored."""
434
+ sampling_params = {"max_tokens": 5, "stop": ["never"]}
435
+ payload = {
436
+ "model": MODEL_NAME,
437
+ "token_ids": [1, 2, 3],
438
+ "sampling_params": sampling_params,
439
+ "stream": False,
440
+ }
441
+ resp = await client.post(GEN_ENDPOINT, json=payload)
442
+ assert resp.status_code == 400
443
+ assert "stop strings" in resp.json()["error"]["message"]
444
+
445
+ sampling_params["stop"] = None
446
+ resp = await client.post(GEN_ENDPOINT, json=payload)
447
+ resp.raise_for_status()
448
+
449
+
450
@pytest.mark.asyncio
451
@pytest.mark.parametrize(
452
"server",
vllm/entrypoints/scale_out/token_in_token_out/serving.py
+10
@@ -134,6 +134,16 @@ class ServingTokens(GenerateBaseServing):
134
f"sampling_params.n must be at most the server's max_num_seqs "
135
f"({max_num_seqs}), got {sampling_params.n}."
136
)
137
+ if self.force_no_detokenize and sampling_params.stop:
138
+ # SamplingParams rejects stop with detokenize=False at request
139
+ # validation, but this server forces detokenize=False afterwards,
140
+ # so the combination must be rejected here or stop strings are
141
+ # silently never applied.
142
+ return self.create_error_response(
143
+ "stop strings are not supported on a --tokens-only server "
144
+ "because detokenization is disabled. Check stop strings on "
145
+ "the coordinator, or use stop_token_ids."
146
+ )
147
try:
148
msgspec.msgpack.encode(
149
(