fix(openai): reject non-numeric logprobs with 400 instead of 500 (#49144)
Signed-off-by: Chenglun Hu <chenglunhu@gmail.com>
hcl committed
Jul 20, 2026 at 18:04 UTC
530ee36a0d99869804c0af68b41007072b609480
4 files changed
+49
tests/entrypoints/openai/chat_completion/test_chat_error.py
+12
@@ -515,3 +515,15 @@ def test_structured_outputs_structural_tag_invalid(structural_tag):
515
messages=[{"role": "user", "content": "hello"}],
516
structured_outputs={"structural_tag": structural_tag},
517
)
518
+
519
+
520
+@pytest.mark.parametrize("field_name", ["prompt_logprobs", "top_logprobs"])
521
+def test_non_numeric_logprobs_rejected(field_name):
522
+ """A non-numeric logprobs value must be a clean 400 validation error, not a
523
+ TypeError from the mode='before' comparison (which surfaces as HTTP 500)."""
524
+ with pytest.raises(ValidationError, match=f"`{field_name}` must be an integer"):
525
+ ChatCompletionRequest(
526
+ model=MODEL_NAME,
527
+ messages=[{"role": "user", "content": "hello"}],
528
+ **{field_name: "2"},
529
+ )
tests/entrypoints/openai/completion/test_completion_error.py
+13
@@ -610,3 +610,16 @@ class TestCompletionPromptListLimit:
610
max_tokens=1,
611
)
612
assert len(request.prompt_embeds) == 5
613
+
614
+
615
+@pytest.mark.parametrize("field_name", ["prompt_logprobs", "logprobs"])
616
+def test_non_numeric_logprobs_rejected(field_name):
617
+ """A non-numeric logprobs value must be a clean 400 validation error, not a
618
+ TypeError from the mode='before' comparison (which surfaces as HTTP 500)."""
619
+ with pytest.raises(ValidationError, match=f"`{field_name}` must be an integer"):
620
+ CompletionRequest(
621
+ model=MODEL_NAME,
622
+ prompt="Test prompt",
623
+ max_tokens=10,
624
+ **{field_name: "2"},
625
+ )
vllm/entrypoints/openai/chat_completion/protocol.py
+12
@@ -757,6 +757,18 @@ class ChatCompletionRequest(OpenAIBaseModel):
757
parameter="logprob_token_ids",
758
)
759
760
+ # These fields are integers, but `mode="before"` runs on the raw
761
+ # request data, so a non-numeric value (e.g. a JSON string) would
762
+ # reach the comparisons below and raise TypeError -> HTTP 500. Reject
763
+ # it here so the client gets a clean 400 instead.
764
+ for field_name in ("prompt_logprobs", "top_logprobs"):
765
+ field_value = data.get(field_name)
766
+ if field_value is not None and not isinstance(field_value, (int, float)):
767
+ raise VLLMValidationError(
768
+ f"`{field_name}` must be an integer.",
769
+ parameter=field_name,
770
+ value=field_value,
771
+ )
772
if (prompt_logprobs := data.get("prompt_logprobs")) is not None:
773
if data.get("stream") and (prompt_logprobs > 0 or prompt_logprobs == -1):
774
raise VLLMValidationError(
vllm/entrypoints/openai/completion/protocol.py
+12
@@ -468,6 +468,18 @@ class CompletionRequest(OpenAIBaseModel):
468
parameter="logprob_token_ids",
469
)
470
471
+ # These fields are integers, but `mode="before"` runs on the raw
472
+ # request data, so a non-numeric value (e.g. a JSON string) would
473
+ # reach the comparisons below and raise TypeError -> HTTP 500. Reject
474
+ # it here so the client gets a clean 400 instead.
475
+ for field_name in ("prompt_logprobs", "logprobs"):
476
+ field_value = data.get(field_name)
477
+ if field_value is not None and not isinstance(field_value, (int, float)):
478
+ raise VLLMValidationError(
479
+ f"`{field_name}` must be an integer.",
480
+ parameter=field_name,
481
+ value=field_value,
482
+ )
483
if (prompt_logprobs := data.get("prompt_logprobs")) is not None:
484
if data.get("stream") and (prompt_logprobs > 0 or prompt_logprobs == -1):
485
raise VLLMValidationError(