[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>
This commit is contained in:
yzong-rh
2026-09-25 14:08:55 -07:00
committed by GitHub
co-authored by Ben Browning
parent 8b47e8b22c
commit b6761e8ded
2 changed files with 57 additions and 2 deletions
@@ -187,6 +187,27 @@ def test_responses_request_empty_tools_named_tool_choice():
)
@pytest.mark.parametrize("detail", [None, "low"], ids=["default", "explicit"])
def test_input_image_detail_defaults_or_preserves(detail: str | None) -> None:
image = {
"type": "input_image",
"image_url": "https://example.com/image.png",
}
if detail is not None:
image["detail"] = detail
item = {
"role": "user",
"content": [image],
}
request = ResponsesRequest.model_validate({"input": [item]})
assert request.input[0]["content"][0]["detail"] == (
"auto" if detail is None else detail
)
# Regression tests for parallel_tool_calls=null crash in Responses API
# (from_request() passed None to ResponsesResponse.parallel_tool_calls,
# a non-optional bool field, causing a Pydantic ValidationError during
+36 -2
View File
@@ -4,7 +4,7 @@
# Adapted from
# https://github.com/lm-sys/FastChat/blob/168ccc29d3f7edc50823016105c024fe2282732a/fastchat/protocol/openai_api_protocol.py
import time
from typing import Any, Literal, TypeAlias
from typing import Annotated, Any, Literal, TypeAlias
from openai.types.responses import (
ResponseCodeInterpreterCallCodeDeltaEvent,
@@ -49,6 +49,7 @@ from openai.types.responses.tool import Tool
from openai.types.shared import Metadata, Reasoning
from openai_harmony import Message as OpenAIHarmonyMessage
from pydantic import (
BeforeValidator,
Field,
ValidationError,
field_serializer,
@@ -135,6 +136,31 @@ ResponseInputOutputMessage: TypeAlias = (
ResponseInputOutputItem: TypeAlias = ResponseInputItemParam | ResponseOutputItem
def _default_input_image_details(value: Any) -> Any:
"""Set the API default for input images before SDK type validation."""
if not isinstance(value, dict):
return value
content = value.get("content")
if not isinstance(content, list):
return value
new_content = []
changed = False
for part in content:
new_part = part
if (
isinstance(part, dict)
and part.get("type") == "input_image"
and "detail" not in part
):
new_part = {**part, "detail": "auto"}
changed = True
new_content.append(new_part)
return {**value, "content": new_content} if changed else value
class ResponsesRequest(OpenAIBaseModel):
# Ordered by official OpenAI API documentation
# https://platform.openai.com/docs/api-reference/responses/create
@@ -152,7 +178,15 @@ class ResponsesRequest(OpenAIBaseModel):
]
| None
) = None
input: str | list[ResponseInputOutputItem]
input: (
str
| list[
Annotated[
ResponseInputOutputItem,
BeforeValidator(_default_input_image_details),
]
]
)
instructions: str | None = None
max_output_tokens: int | None = None
max_tool_calls: int | None = None