Skip to content
Open
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
2 changes: 1 addition & 1 deletion src/openai/types/responses/response.py
Original file line number Diff line number Diff line change
Expand Up @@ -447,7 +447,7 @@ def output_text(self) -> str:
for output in self.output:
if output.type == "message":
for content in output.content:
if content.type == "output_text":
if content.type == "output_text" and content.text is not None:
texts.append(content.text)

return "".join(texts)
4 changes: 2 additions & 2 deletions src/openai/types/responses/response_input_item_param.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,10 +193,10 @@ class ImageGenerationCall(TypedDict, total=False):
id: Required[str]
"""The unique ID of the image generation call."""

result: Required[Optional[str]]
result: Optional[str]
"""The generated image encoded in base64."""

status: Required[Literal["in_progress", "completed", "generating", "failed"]]
status: Literal["in_progress", "completed", "generating", "failed"]
Comment on lines +196 to +199

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Mirror optional image fields in ResponseInputParam

This only fixes the standalone ResponseInputItemParam; the primary responses.create(..., input=...) overload imports ResponseInputParam from response_input_param.py, which defines a separate ImageGenerationCall union member that still has result: Required[Optional[str]] and status: Required[...]. In the usual responses.create(input=[{"type": "image_generation_call", "id": ...}]) path, type checkers will still require those output-only fields, so the typing bug remains for the main Responses API unless the mirrored type in response_input_param.py is updated too.

Useful? React with 👍 / 👎.

"""The status of the image generation call."""

type: Required[Literal["image_generation_call"]]
Expand Down
74 changes: 74 additions & 0 deletions tests/lib/responses/test_responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@

from openai import OpenAI, AsyncOpenAI
from openai._utils import assert_signatures_in_sync
from openai.types.responses.response import Response
from openai.types.responses.response_output_message import ResponseOutputMessage
from openai.types.responses.response_output_text import ResponseOutputText

from ...conftest import base_url
from ..snapshots import make_snapshot_request
Expand Down Expand Up @@ -41,6 +44,77 @@ def test_output_text(client: OpenAI, respx_mock: MockRouter) -> None:
)


def _make_response_with_text(text: str | None) -> Response:
"""Build a minimal Response via model_construct, bypassing validation to simulate
raw API payloads where `text` may be null."""
content_block = ResponseOutputText.model_construct(
type="output_text",
annotations=[],
logprobs=None,
text=text,
)
message = ResponseOutputMessage.model_construct(
id="msg_test",
type="message",
status="completed",
role="assistant",
content=[content_block],
)
return Response.model_construct(
id="resp_test",
object="response",
created_at=0,
status="completed",
model="gpt-4o-mini",
output=[message],
parallel_tool_calls=True,
text=None,
tool_choice="auto",
tools=[],
truncation="disabled",
)


def test_output_text_null_guard() -> None:
"""output_text must not crash when content items have text=None (issue #3063)."""
# Single null text block -> empty string
response = _make_response_with_text(None)
assert response.output_text == ""

# Valid text block -> the text is returned
response = _make_response_with_text("hello")
assert response.output_text == "hello"

# Mixed: null and valid in the same message -> only valid text joined
null_block = ResponseOutputText.model_construct(
type="output_text", annotations=[], logprobs=None, text=None
)
valid_block = ResponseOutputText.model_construct(
type="output_text", annotations=[], logprobs=None, text="world"
)
message = ResponseOutputMessage.model_construct(
id="msg_mixed",
type="message",
status="completed",
role="assistant",
content=[null_block, valid_block],
)
response = Response.model_construct(
id="resp_mixed",
object="response",
created_at=0,
status="completed",
model="gpt-4o-mini",
output=[message],
parallel_tool_calls=True,
text=None,
tool_choice="auto",
tools=[],
truncation="disabled",
)
assert response.output_text == "world"


@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"])
def test_stream_method_definition_in_sync(sync: bool, client: OpenAI, async_client: AsyncOpenAI) -> None:
checking_client: OpenAI | AsyncOpenAI = client if sync else async_client
Expand Down