[Bugfix] Raise VLLMValidationError for non-integer logit_bias keys (#46612)

Signed-off-by: muhammadfawaz1 <135441198+muhammadfawaz1@users.noreply.github.com> Co-authored-by: Mahad Durrani <114791389+mahadrehmann@users.noreply.github.com>

Muhammad Fawaz committed Jun 30, 2026 at 11:18 UTC 97b5ce5c3931173b3efebca58e597b8e58730cd3
2 files changed +82 -6
tests/entrypoints/openai/chat_completion/test_chat_logit_bias_validation.py
+57
@@ -76,3 +76,60 @@ async def test_chat_logit_bias_invalid(client):
76 assert error.status_code == 400
77 assert str(invalid_token_id) in error_message
78 assert str(vocab_size) in error_message
79 +
80 +
81 +@pytest.mark.asyncio
82 +async def test_chat_logit_bias_non_integer_key(client):
83 + """Test that a non-integer logit_bias key is rejected with a clean,
84 + informative error instead of a raw 'invalid literal for int()' message."""
85 + with pytest.raises(openai.BadRequestError) as excinfo:
86 + await client.chat.completions.create(
87 + model=MODEL_NAME,
88 + messages=[{"role": "user", "content": "Testing invalid logit bias key"}],
89 + max_tokens=5,
90 + logit_bias={"not_a_token_id": 50},
91 + )
92 +
93 + error = excinfo.value
94 + error_message = str(error)
95 +
96 + assert error.status_code == 400
97 + assert "not_a_token_id" in error_message
98 + assert "logit_bias" in error_message
99 +
100 +
101 +@pytest.mark.asyncio
102 +async def test_chat_logit_bias_non_numeric_value(client):
103 + """Test that a non-numeric logit_bias value is rejected with a message
104 + that names the specific offending token, not just a generic TypeError."""
105 + with pytest.raises(openai.BadRequestError) as excinfo:
106 + await client.chat.completions.create(
107 + model=MODEL_NAME,
108 + messages=[{"role": "user", "content": "Testing invalid logit bias value"}],
109 + max_tokens=5,
110 + logit_bias={"1": "not_a_number"},
111 + )
112 +
113 + error = excinfo.value
114 + error_message = str(error)
115 +
116 + assert error.status_code == 400
117 + assert "logit_bias" in error_message
118 +
119 +
120 +@pytest.mark.asyncio
121 +async def test_chat_logit_bias_multiple_non_integer_keys(client):
122 + """Test that ALL invalid logit_bias keys are reported together,
123 + not just the first one encountered."""
124 + with pytest.raises(openai.BadRequestError) as excinfo:
125 + await client.chat.completions.create(
126 + model=MODEL_NAME,
127 + messages=[{"role": "user", "content": "Testing multiple bad keys"}],
128 + max_tokens=5,
129 + logit_bias={"bad1": 50.0, "bad2": 20.0},
130 + )
131 +
132 + error_message = str(excinfo.value)
133 + assert excinfo.value.status_code == 400
134 + assert "bad1" in error_message
135 + assert "bad2" in error_message
vllm/sampling_params.py
+25 -6
@@ -385,12 +385,31 @@ class SamplingParams(
385 repetition_detection: RepetitionDetectionParams | None = None,
386 ) -> "SamplingParams":
387 if logit_bias is not None:
388 - # Convert token_id to integer
389 - # Clamp the bias between -100 and 100 per OpenAI API spec
390 - logit_bias = {
391 - int(token): min(100.0, max(-100.0, bias))
392 - for token, bias in logit_bias.items()
393 - }
388 + # Fast path uses a dict comprehension; on failure we iterate once
389 + # to identify the exact offending entry for the error message.
390 + try:
391 + logit_bias = {
392 + int(token): min(100.0, max(-100.0, bias))
393 + for token, bias in logit_bias.items()
394 + }
395 + except (ValueError, TypeError):
396 + invalid_keys = []
397 + converted_logit_bias = {}
398 + for token, bias in logit_bias.items():
399 + try:
400 + token_id = int(token)
401 + except (ValueError, TypeError):
402 + invalid_keys.append(token)
403 + continue
404 + converted_logit_bias[token_id] = min(100.0, max(-100.0, bias))
405 + if invalid_keys:
406 + raise VLLMValidationError(
407 + f"logit_bias contains key(s) that cannot be "
408 + f"converted to integer token IDs: {invalid_keys!r}",
409 + parameter="logit_bias",
410 + value=invalid_keys,
411 + ) from None
412 + logit_bias = converted_logit_bias
413
414 return SamplingParams(
415 n=1 if n is None else n,