Skip to content

Commit 1d62417

Browse files
committed
[Bugfix] Add validation for tool requests when tool_parser is unavailable
When tool_choice is "required" or a named tool, but the server was not started with --enable-auto-tool-choice and --tool-call-parser, vLLM would silently degrade instead of returning an error. This fix adds validation to return a clear error message indicating that these flags are required for tool parsing. Fixes #29432
1 parent ace34e3 commit 1d62417

File tree

2 files changed

+82
-0
lines changed

2 files changed

+82
-0
lines changed

tests/entrypoints/openai/test_serving_chat.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1372,3 +1372,71 @@ async def test_non_tool_reasoning_empty_content_list(self, serving_chat):
13721372
},
13731373
],
13741374
)
1375+
1376+
1377+
@pytest.mark.asyncio
1378+
async def test_tool_choice_validation_without_parser():
1379+
"""Test that tool_choice='required' or named tool without tool_parser
1380+
returns an appropriate error message."""
1381+
mock_engine = MagicMock(spec=AsyncLLM)
1382+
mock_engine.get_tokenizer.return_value = get_tokenizer(MODEL_NAME)
1383+
mock_engine.errored = False
1384+
mock_engine.model_config = MockModelConfig()
1385+
mock_engine.input_processor = MagicMock()
1386+
mock_engine.io_processor = MagicMock()
1387+
1388+
models = OpenAIServingModels(
1389+
engine_client=mock_engine,
1390+
base_model_paths=BASE_MODEL_PATHS,
1391+
)
1392+
# Create serving_chat without tool_parser (enable_auto_tools=False)
1393+
serving_chat = OpenAIServingChat(
1394+
mock_engine,
1395+
models,
1396+
response_role="assistant",
1397+
chat_template=CHAT_TEMPLATE,
1398+
chat_template_content_format="auto",
1399+
request_logger=None,
1400+
enable_auto_tools=False, # No tool parser
1401+
)
1402+
1403+
tools = [
1404+
{
1405+
"type": "function",
1406+
"function": {
1407+
"name": "get_weather",
1408+
"description": "Get the weather in a given location",
1409+
"parameters": {
1410+
"type": "object",
1411+
"properties": {"location": {"type": "string"}},
1412+
"required": ["location"],
1413+
},
1414+
},
1415+
}
1416+
]
1417+
1418+
# Test tool_choice="required" without tool_parser
1419+
req_required = ChatCompletionRequest(
1420+
model=MODEL_NAME,
1421+
messages=[{"role": "user", "content": "What's the weather?"}],
1422+
tools=tools,
1423+
tool_choice="required",
1424+
)
1425+
response_required = await serving_chat.create_chat_completion(req_required)
1426+
assert hasattr(response_required, "body")
1427+
error_body = response_required.body.decode()
1428+
assert "tool_choice" in error_body
1429+
assert "--enable-auto-tool-choice" in error_body
1430+
1431+
# Test named tool_choice without tool_parser
1432+
req_named = ChatCompletionRequest(
1433+
model=MODEL_NAME,
1434+
messages=[{"role": "user", "content": "What's the weather?"}],
1435+
tools=tools,
1436+
tool_choice={"type": "function", "function": {"name": "get_weather"}},
1437+
)
1438+
response_named = await serving_chat.create_chat_completion(req_named)
1439+
assert hasattr(response_named, "body")
1440+
error_body = response_named.body.decode()
1441+
assert "tool_choice" in error_body
1442+
assert "--enable-auto-tool-choice" in error_body

vllm/entrypoints/openai/serving_chat.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,20 @@ async def create_chat_completion(
217217
"--enable-auto-tool-choice and --tool-call-parser to be set"
218218
)
219219

220+
# Validate that tool_parser is available when tool_choice requires
221+
# tool parsing (i.e., "required" or a named tool)
222+
if (
223+
request.tool_choice is not None
224+
and request.tool_choice not in ("none", "auto")
225+
and tool_parser is None
226+
and not isinstance(tokenizer, MistralTokenizer)
227+
and not self.use_harmony
228+
):
229+
return self.create_error_response(
230+
f'tool_choice="{request.tool_choice}" requires '
231+
"--enable-auto-tool-choice and --tool-call-parser to be set"
232+
)
233+
220234
if request.tools is None or (
221235
request.tool_choice == "none"
222236
and self.exclude_tools_when_tool_choice_none

0 commit comments

Comments
 (0)