main
py 172 lines 6.15 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 """Unit tests for ResponsesRequest.to_sampling_params() parameter mapping."""
5
6 import pytest
7 import torch
8 from openai.types.responses.response_format_text_json_schema_config import (
9 ResponseFormatTextJSONSchemaConfig,
10 )
11 from pydantic import ValidationError
12
13 from vllm.entrypoints.openai.responses.protocol import (
14 ResponsesRequest,
15 ResponseTextConfig,
16 )
17 from vllm.exceptions import VLLMValidationError
18 from vllm.sampling_params import StructuredOutputsParams
19
20
21 class TestResponsesRequestSamplingParams:
22 """Test that ResponsesRequest correctly maps parameters to SamplingParams."""
23
24 def test_basic_sampling_params(self):
25 """Test basic sampling parameters are correctly mapped."""
26 request = ResponsesRequest(
27 model="test-model",
28 input="test input",
29 temperature=0.8,
30 top_p=0.95,
31 top_k=50,
32 max_output_tokens=100,
33 )
34
35 sampling_params = request.to_sampling_params(default_max_tokens=1000)
36
37 assert sampling_params.temperature == 0.8
38 assert sampling_params.top_p == 0.95
39 assert sampling_params.top_k == 50
40 assert sampling_params.max_tokens == 100
41
42 def test_extra_sampling_params(self):
43 """Test extra sampling parameters are correctly mapped."""
44 request = ResponsesRequest(
45 model="test-model",
46 input="test input",
47 repetition_penalty=1.2,
48 seed=42,
49 stop=["END", "STOP"],
50 ignore_eos=True,
51 vllm_xargs={"custom": "value"},
52 )
53
54 sampling_params = request.to_sampling_params(default_max_tokens=1000)
55
56 assert sampling_params.repetition_penalty == 1.2
57 assert sampling_params.seed == 42
58 assert sampling_params.stop == ["END", "STOP"]
59 assert sampling_params.ignore_eos is True
60 assert sampling_params.extra_args == {"custom": "value"}
61
62 def test_stop_string_conversion(self):
63 """Test that single stop string is converted to list."""
64 request = ResponsesRequest(
65 model="test-model",
66 input="test input",
67 stop="STOP",
68 )
69
70 sampling_params = request.to_sampling_params(default_max_tokens=1000)
71
72 assert sampling_params.stop == ["STOP"]
73
74 def test_default_values(self):
75 """Test default values for optional parameters."""
76 request = ResponsesRequest(
77 model="test-model",
78 input="test input",
79 )
80
81 sampling_params = request.to_sampling_params(default_max_tokens=1000)
82
83 assert sampling_params.repetition_penalty == 1.0 # None → 1.0
84 assert sampling_params.stop == [] # Empty list
85 assert sampling_params.extra_args == {} # Empty dict
86
87 def test_seed_bounds_validation(self):
88 """Test that seed values outside torch.long bounds are rejected."""
89 # Test seed below minimum
90 with pytest.raises(ValidationError) as exc_info:
91 ResponsesRequest(
92 model="test-model",
93 input="test input",
94 seed=torch.iinfo(torch.long).min - 1,
95 )
96 assert "greater_than_equal" in str(exc_info.value).lower()
97
98 # Test seed above maximum
99 with pytest.raises(ValidationError) as exc_info:
100 ResponsesRequest(
101 model="test-model",
102 input="test input",
103 seed=torch.iinfo(torch.long).max + 1,
104 )
105 assert "less_than_equal" in str(exc_info.value).lower()
106
107 # Test valid seed at boundaries
108 request_min = ResponsesRequest(
109 model="test-model",
110 input="test input",
111 seed=torch.iinfo(torch.long).min,
112 )
113 assert request_min.seed == torch.iinfo(torch.long).min
114
115 request_max = ResponsesRequest(
116 model="test-model",
117 input="test input",
118 seed=torch.iinfo(torch.long).max,
119 )
120 assert request_max.seed == torch.iinfo(torch.long).max
121
122 def test_structured_outputs_passed_through(self):
123 """Test that structured_outputs field is passed to SamplingParams."""
124 structured_outputs = StructuredOutputsParams(grammar="root ::= 'hello'")
125 request = ResponsesRequest(
126 model="test-model",
127 input="test input",
128 structured_outputs=structured_outputs,
129 )
130
131 sampling_params = request.to_sampling_params(default_max_tokens=1000)
132
133 assert sampling_params.structured_outputs is not None
134 assert sampling_params.structured_outputs.grammar == "root ::= 'hello'"
135
136 def test_text_format_json_object_enables_structured_outputs(self):
137 """text.format json_object enables structured outputs for sampling."""
138 request = ResponsesRequest(
139 model="test-model",
140 input="test input",
141 text=ResponseTextConfig.model_validate({"format": {"type": "json_object"}}),
142 )
143
144 sampling_params = request.to_sampling_params(default_max_tokens=1000)
145
146 assert sampling_params.structured_outputs is not None
147 assert sampling_params.structured_outputs.json_object is True
148 assert sampling_params.structured_outputs.json is None
149 assert request.structured_outputs is None
150
151 def test_structured_outputs_and_json_schema_conflict(self):
152 """Test that specifying both structured_outputs and json_schema raises."""
153 structured_outputs = StructuredOutputsParams(grammar="root ::= 'hello'")
154 text_config = ResponseTextConfig()
155 text_config.format = ResponseFormatTextJSONSchemaConfig(
156 type="json_schema",
157 name="test",
158 schema={"type": "object"},
159 )
160 request = ResponsesRequest(
161 model="test-model",
162 input="test input",
163 structured_outputs=structured_outputs,
164 text=text_config,
165 )
166
167 with pytest.raises(VLLMValidationError) as exc_info:
168 request.to_sampling_params(default_max_tokens=1000)
169
170 assert "Cannot specify both structured_outputs and text.format" in str(
171 exc_info.value
172 )