mirror of
https://git.datalinker.icu/vllm-project/vllm.git
synced 2026-05-09 08:49:09 +08:00
[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:
parent
9187de9fac
commit
086b96339f
@ -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
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user