[Bugfix] Default missing detail for Responses API input images (#57241)

Signed-off-by: Yifan Zong <yzong@redhat.com> Co-authored-by: Ben Browning <56071+bbrowning@users.noreply.github.com>

yzong-rh committed Sep 25, 2026 at 17:08 UTC b6761e8ded57ef85b708f34af8cab1649eae1069
2 files changed +57 -2
tests/tool_use/test_responses_request_validations.py
+21
@@ -187,6 +187,27 @@ def test_responses_request_empty_tools_named_tool_choice():
187 )
188
189
190 +@pytest.mark.parametrize("detail", [None, "low"], ids=["default", "explicit"])
191 +def test_input_image_detail_defaults_or_preserves(detail: str | None) -> None:
192 + image = {
193 + "type": "input_image",
194 + "image_url": "https://example.com/image.png",
195 + }
196 + if detail is not None:
197 + image["detail"] = detail
198 +
199 + item = {
200 + "role": "user",
201 + "content": [image],
202 + }
203 +
204 + request = ResponsesRequest.model_validate({"input": [item]})
205 +
206 + assert request.input[0]["content"][0]["detail"] == (
207 + "auto" if detail is None else detail
208 + )
209 +
210 +
211 # Regression tests for parallel_tool_calls=null crash in Responses API
212 # (from_request() passed None to ResponsesResponse.parallel_tool_calls,
213 # a non-optional bool field, causing a Pydantic ValidationError during
vllm/entrypoints/openai/responses/protocol.py
+36 -2
@@ -4,7 +4,7 @@
4 # Adapted from
5 # https://github.com/lm-sys/FastChat/blob/168ccc29d3f7edc50823016105c024fe2282732a/fastchat/protocol/openai_api_protocol.py
6 import time
7 -from typing import Any, Literal, TypeAlias
7 +from typing import Annotated, Any, Literal, TypeAlias
8
9 from openai.types.responses import (
10 ResponseCodeInterpreterCallCodeDeltaEvent,
@@ -49,6 +49,7 @@ from openai.types.responses.tool import Tool
49 from openai.types.shared import Metadata, Reasoning
50 from openai_harmony import Message as OpenAIHarmonyMessage
51 from pydantic import (
52 + BeforeValidator,
53 Field,
54 ValidationError,
55 field_serializer,
@@ -135,6 +136,31 @@ ResponseInputOutputMessage: TypeAlias = (
136 ResponseInputOutputItem: TypeAlias = ResponseInputItemParam | ResponseOutputItem
137
138
139 +def _default_input_image_details(value: Any) -> Any:
140 + """Set the API default for input images before SDK type validation."""
141 + if not isinstance(value, dict):
142 + return value
143 +
144 + content = value.get("content")
145 + if not isinstance(content, list):
146 + return value
147 +
148 + new_content = []
149 + changed = False
150 + for part in content:
151 + new_part = part
152 + if (
153 + isinstance(part, dict)
154 + and part.get("type") == "input_image"
155 + and "detail" not in part
156 + ):
157 + new_part = {**part, "detail": "auto"}
158 + changed = True
159 + new_content.append(new_part)
160 +
161 + return {**value, "content": new_content} if changed else value
162 +
163 +
164 class ResponsesRequest(OpenAIBaseModel):
165 # Ordered by official OpenAI API documentation
166 # https://platform.openai.com/docs/api-reference/responses/create
@@ -152,7 +178,15 @@ class ResponsesRequest(OpenAIBaseModel):
178 ]
179 | None
180 ) = None
155 - input: str | list[ResponseInputOutputItem]
181 + input: (
182 + str
183 + | list[
184 + Annotated[
185 + ResponseInputOutputItem,
186 + BeforeValidator(_default_input_image_details),
187 + ]
188 + ]
189 + )
190 instructions: str | None = None
191 max_output_tokens: int | None = None
192 max_tool_calls: int | None = None