[Bugfix] Add validation for tool requests when tool_parser is unavailable (#30613)

Signed-off-by: majiayu000 <1835304752@qq.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
lif 2025-12-19 18:23:28 +08:00 committed by GitHub
parent 9187de9fac
commit 086b96339f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 89 additions and 9 deletions

View File

@ -15,6 +15,7 @@ from vllm.entrypoints.openai.parser.harmony_utils import get_encoding
from vllm.entrypoints.openai.protocol import ( from vllm.entrypoints.openai.protocol import (
ChatCompletionRequest, ChatCompletionRequest,
ChatCompletionResponse, ChatCompletionResponse,
ErrorResponse,
RequestResponseMetadata, RequestResponseMetadata,
) )
from vllm.entrypoints.openai.serving_chat import OpenAIServingChat from vllm.entrypoints.openai.serving_chat import OpenAIServingChat
@ -1444,3 +1445,69 @@ class TestServingChatWithHarmony:
}, },
], ],
) )
@pytest.mark.asyncio
async def test_tool_choice_validation_without_parser():
"""Test that tool_choice='required' or named tool without tool_parser
returns an appropriate error message."""
mock_engine = MagicMock(spec=AsyncLLM)
mock_engine.get_tokenizer.return_value = get_tokenizer(MODEL_NAME)
mock_engine.errored = False
mock_engine.model_config = MockModelConfig()
mock_engine.input_processor = MagicMock()
mock_engine.io_processor = MagicMock()
models = OpenAIServingModels(
engine_client=mock_engine,
base_model_paths=BASE_MODEL_PATHS,
)
# Create serving_chat without tool_parser (enable_auto_tools=False)
serving_chat = OpenAIServingChat(
mock_engine,
models,
response_role="assistant",
chat_template=CHAT_TEMPLATE,
chat_template_content_format="auto",
request_logger=None,
enable_auto_tools=False, # No tool parser
)
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get the weather in a given location",
"parameters": {
"type": "object",
"properties": {"location": {"type": "string"}},
"required": ["location"],
},
},
}
]
# Test tool_choice="required" without tool_parser
req_required = ChatCompletionRequest(
model=MODEL_NAME,
messages=[{"role": "user", "content": "What's the weather?"}],
tools=tools,
tool_choice="required",
)
response_required = await serving_chat.create_chat_completion(req_required)
assert isinstance(response_required, ErrorResponse)
assert "tool_choice" in response_required.error.message
assert "--tool-call-parser" in response_required.error.message
# Test named tool_choice without tool_parser
req_named = ChatCompletionRequest(
model=MODEL_NAME,
messages=[{"role": "user", "content": "What's the weather?"}],
tools=tools,
tool_choice={"type": "function", "function": {"name": "get_weather"}},
)
response_named = await serving_chat.create_chat_completion(req_named)
assert isinstance(response_named, ErrorResponse)
assert "tool_choice" in response_named.error.message
assert "--tool-call-parser" in response_named.error.message

View File

@ -253,18 +253,31 @@ class OpenAIServingChat(OpenAIServing):
truncate_tool_call_ids(request) truncate_tool_call_ids(request)
validate_request_params(request) validate_request_params(request)
if ( # Check if tool parsing is unavailable (common condition)
request.tool_choice == "auto" tool_parsing_unavailable = (
and not (self.enable_auto_tools and tool_parser is not None) tool_parser is None
and not isinstance(tokenizer, MistralTokenizer) and not isinstance(tokenizer, MistralTokenizer)
and not self.use_harmony and not self.use_harmony
)
# Validate tool_choice when tool parsing is required but unavailable
if tool_parsing_unavailable and request.tool_choice not in (
None,
"none",
): ):
# for hf tokenizers, "auto" tools requires if request.tool_choice == "auto" and not self.enable_auto_tools:
# --enable-auto-tool-choice and --tool-call-parser # for hf tokenizers, "auto" tools requires
return self.create_error_response( # --enable-auto-tool-choice and --tool-call-parser
'"auto" tool choice requires ' return self.create_error_response(
"--enable-auto-tool-choice and --tool-call-parser to be set" '"auto" tool choice requires '
) "--enable-auto-tool-choice and --tool-call-parser to be set"
)
elif request.tool_choice != "auto":
# "required" or named tool requires tool parser
return self.create_error_response(
f'tool_choice="{request.tool_choice}" requires '
"--tool-call-parser to be set"
)
if request.tools is None or ( if request.tools is None or (
request.tool_choice == "none" request.tool_choice == "none"