A high-throughput and memory-efficient inference and serving engine for LLMs
Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git
Synced Nov 8, 08:45 UTC
[Frontend] Enable generic structured_outputs for responses API (#33709)
Signed-off-by: Alec Solder <alecs@fb.com> Co-authored-by: Alec Solder <alecs@fb.com>
Alec S committed
Feb 12, 2026 at 19:15 UTC
be7370daf3596da71776375b9aba6dd712646fdc
2 files changed
+58
-7
tests/entrypoints/openai/responses/test_sampling_params.py
+47
-4
@@ -4,8 +4,17 @@
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
8
-from vllm.entrypoints.openai.responses.protocol import ResponsesRequest
13
+from vllm.entrypoints.openai.responses.protocol import (
14
+ ResponsesRequest,
15
+ ResponseTextConfig,
16
+)
17
+from vllm.sampling_params import StructuredOutputsParams
18
19
20
class TestResponsesRequestSamplingParams:
@@ -76,9 +85,6 @@ class TestResponsesRequestSamplingParams:
85
86
def test_seed_bounds_validation(self):
87
"""Test that seed values outside torch.long bounds are rejected."""
79
- import torch
80
- from pydantic import ValidationError
81
-
88
# Test seed below minimum
89
with pytest.raises(ValidationError) as exc_info:
90
ResponsesRequest(
@@ -111,3 +117,40 @@ class TestResponsesRequestSamplingParams:
117
seed=torch.iinfo(torch.long).max,
118
)
119
assert request_max.seed == torch.iinfo(torch.long).max
120
+
121
+ def test_structured_outputs_passed_through(self):
122
+ """Test that structured_outputs field is passed to SamplingParams."""
123
+ structured_outputs = StructuredOutputsParams(grammar="root ::= 'hello'")
124
+ request = ResponsesRequest(
125
+ model="test-model",
126
+ input="test input",
127
+ structured_outputs=structured_outputs,
128
+ )
129
+
130
+ sampling_params = request.to_sampling_params(default_max_tokens=1000)
131
+
132
+ assert sampling_params.structured_outputs is not None
133
+ assert sampling_params.structured_outputs.grammar == "root ::= 'hello'"
134
+
135
+ def test_structured_outputs_and_json_schema_conflict(self):
136
+ """Test that specifying both structured_outputs and json_schema raises."""
137
+ structured_outputs = StructuredOutputsParams(grammar="root ::= 'hello'")
138
+ text_config = ResponseTextConfig()
139
+ text_config.format = ResponseFormatTextJSONSchemaConfig(
140
+ type="json_schema",
141
+ name="test",
142
+ schema={"type": "object"},
143
+ )
144
+ request = ResponsesRequest(
145
+ model="test-model",
146
+ input="test input",
147
+ structured_outputs=structured_outputs,
148
+ text=text_config,
149
+ )
150
+
151
+ with pytest.raises(ValueError) as exc_info:
152
+ request.to_sampling_params(default_max_tokens=1000)
153
+
154
+ assert "Cannot specify both structured_outputs and text.format" in str(
155
+ exc_info.value
156
+ )
vllm/entrypoints/openai/responses/protocol.py
+11
-3
@@ -233,6 +233,10 @@ class ResponsesRequest(OpenAIBaseModel):
233
# this cannot be used in conjunction with previous_response_id
234
# TODO: consider supporting non harmony messages as well
235
previous_input_messages: list[OpenAIHarmonyMessage | dict] | None = None
236
+ structured_outputs: StructuredOutputsParams | None = Field(
237
+ default=None,
238
+ description="Additional kwargs for structured outputs",
239
+ )
240
241
repetition_penalty: float | None = None
242
seed: int | None = Field(None, ge=_LONG_INFO.min, le=_LONG_INFO.max)
@@ -319,8 +323,14 @@ class ResponsesRequest(OpenAIBaseModel):
323
stop_token_ids = default_sampling_params.get("stop_token_ids")
324
325
# Structured output
322
- structured_outputs = None
326
+ structured_outputs = self.structured_outputs
327
+
328
+ # Also check text.format for OpenAI-style json_schema
329
if self.text is not None and self.text.format is not None:
330
+ if structured_outputs is not None:
331
+ raise ValueError(
332
+ "Cannot specify both structured_outputs and text.format"
333
+ )
334
response_format = self.text.format
335
if (
336
response_format.type == "json_schema"
@@ -329,8 +339,6 @@ class ResponsesRequest(OpenAIBaseModel):
339
structured_outputs = StructuredOutputsParams(
340
json=response_format.schema_
341
)
332
- elif response_format.type == "json_object":
333
- raise NotImplementedError("json_object is not supported")
342
343
stop = self.stop if self.stop else []
344
if isinstance(stop, str):