Files
vllm-project--vllm/tests/entrypoints/openai/test_tool_choice_content_none.py
wehub-resource-sync 7ce4c8e27e
pre-commit / pre-run-check (push) Has been cancelled
pre-commit / pre-commit (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:55:37 +08:00

204 lines
6.0 KiB
Python

# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
import pytest
from openai.types.chat.chat_completion import ChatCompletion as OpenAIChatCompletion
from openai.types.chat.chat_completion_chunk import ChatCompletionChunk
from vllm.entrypoints.openai.chat_completion.protocol import (
ChatCompletionRequest,
ChatCompletionResponse,
ChatCompletionResponseChoice,
ChatCompletionResponseStreamChoice,
ChatCompletionStreamResponse,
ChatMessage,
)
from vllm.entrypoints.openai.engine.protocol import (
DeltaFunctionCall,
DeltaMessage,
DeltaToolCall,
FunctionCall,
ToolCall,
UsageInfo,
)
from vllm.entrypoints.openai.responses.protocol import ResponsesRequest
from vllm.parser.abstract_parser import DelegatingParser
pytestmark = pytest.mark.skip_global_cleanup
class _DummyDelegatingParser(DelegatingParser):
def is_reasoning_end(self, input_ids: list[int]) -> bool:
return False
def extract_content_ids(self, input_ids: list[int]) -> list[int]:
return input_ids
def extract_reasoning(self, model_output: str, request):
return None, model_output
def extract_reasoning_streaming(
self,
previous_text: str,
current_text: str,
delta_text: str,
previous_token_ids: list[int],
current_token_ids: list[int],
delta_token_ids: list[int],
):
return None
def test_chat_completion_named_tool_choice_with_none_content():
request = ChatCompletionRequest.model_validate(
{
"model": "test-model",
"messages": [{"role": "user", "content": "test"}],
"tools": [
{
"type": "function",
"function": {
"name": "get_weather",
"parameters": {"type": "object", "properties": {}},
},
}
],
"tool_choice": {"type": "function", "function": {"name": "get_weather"}},
}
)
parser = _DummyDelegatingParser(tokenizer=None)
tool_calls, content = parser._extract_tool_calls(
content=None,
request=request,
enable_auto_tools=True,
)
assert content is None
assert tool_calls == []
def test_responses_parser_allows_named_tool_choice_with_none_content():
request = ResponsesRequest.model_validate(
{
"model": "test-model",
"input": "test",
"tools": [
{
"type": "function",
"name": "get_weather",
"parameters": {"type": "object", "properties": {}},
}
],
"tool_choice": {"type": "function", "name": "get_weather"},
}
)
parser = _DummyDelegatingParser(tokenizer=None)
tool_calls, content = parser._extract_tool_calls(
content=None,
request=request,
enable_auto_tools=False,
)
assert content is None
assert tool_calls == []
def _chat_response(message: ChatMessage) -> ChatCompletionResponse:
return ChatCompletionResponse(
model="test-model",
choices=[
ChatCompletionResponseChoice(
index=0,
message=message,
finish_reason="stop",
)
],
usage=UsageInfo(prompt_tokens=1, completion_tokens=1, total_tokens=2),
)
def test_chat_completion_response_omits_empty_tool_calls_payload():
response = _chat_response(ChatMessage(role="assistant", content="done"))
payload = response.model_dump()
payload_exclude_unset = response.model_dump(exclude_unset=True)
assert "tool_calls" not in payload["choices"][0]["message"]
assert "tool_calls" not in payload_exclude_unset["choices"][0]["message"]
parsed = OpenAIChatCompletion.model_validate(payload)
assert parsed.choices[0].message.tool_calls is None
def test_chat_completion_response_keeps_non_empty_tool_calls_payload():
response = _chat_response(
ChatMessage(
role="assistant",
content="",
tool_calls=[
ToolCall(
function=FunctionCall(
name="get_weather",
arguments='{"city": "Beijing"}',
)
)
],
)
)
message = response.model_dump()["choices"][0]["message"]
assert len(message["tool_calls"]) == 1
assert message["tool_calls"][0]["function"]["name"] == "get_weather"
def _stream_response(delta: DeltaMessage) -> ChatCompletionStreamResponse:
return ChatCompletionStreamResponse(
id="chatcmpl-test",
object="chat.completion.chunk",
created=1,
model="test-model",
choices=[
ChatCompletionResponseStreamChoice(
index=0,
delta=delta,
finish_reason=None,
)
],
)
def test_chat_completion_stream_response_omits_empty_tool_calls_payload():
response = _stream_response(DeltaMessage(content="done"))
payload = response.model_dump(exclude_unset=True)
payload_json = response.model_dump_json(exclude_unset=True)
assert "tool_calls" not in payload["choices"][0]["delta"]
parsed = ChatCompletionChunk.model_validate_json(payload_json)
assert parsed.choices[0].delta.tool_calls is None
def test_chat_completion_stream_response_keeps_non_empty_tool_calls_payload():
response = _stream_response(
DeltaMessage(
tool_calls=[
DeltaToolCall(
index=0,
id="call-test",
type="function",
function=DeltaFunctionCall(
name="get_weather",
arguments='{"city": "Beijing"}',
),
)
]
)
)
delta = response.model_dump(exclude_unset=True)["choices"][0]["delta"]
assert len(delta["tool_calls"]) == 1
assert delta["tool_calls"][0]["function"]["name"] == "get_weather"