Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions tests/tool_use/test_responses_request_validations.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a standard field in the Responses API?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes - "The detail level of the image to be sent to the model. One of high, low, or auto. Defaults to auto." from https://github.com/openresponses/openresponses/blob/92c12d96d7b61d6d15e2214daa5e9c6000ab6e1c/schema/components/schemas/InputImageContent.json#L32-L35 in the OpenResponses spec. It's also present and documented in the dominant SaaS implementation of this API.


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
Expand Down
38 changes: 36 additions & 2 deletions vllm/entrypoints/openai/responses/protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -49,6 +49,7 @@
from openai.types.shared import Metadata, Reasoning
from openai_harmony import Message as OpenAIHarmonyMessage
from pydantic import (
BeforeValidator,
Field,
ValidationError,
field_serializer,
Expand Down Expand Up @@ -135,6 +136,31 @@ class ResponseRawMessageAndToken(OpenAIBaseModel):
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
Expand All @@ -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
Expand Down
Loading