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
4 changes: 2 additions & 2 deletions src/openai/_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ def __init__(
if provider_runtime is not None:
base_url = provider_runtime.base_url
elif base_url is None:
base_url = os.environ.get("OPENAI_BASE_URL")
base_url = os.environ.get("OPENAI_BASE_URL") or None
if base_url is None:
base_url = f"https://api.openai.com/v1"

Expand Down Expand Up @@ -844,7 +844,7 @@ def __init__(
if provider_runtime is not None:
base_url = provider_runtime.base_url
elif base_url is None:
base_url = os.environ.get("OPENAI_BASE_URL")
base_url = os.environ.get("OPENAI_BASE_URL") or None
if base_url is None:
base_url = f"https://api.openai.com/v1"

Expand Down
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:

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 Make the null guard type-checkable

In the repository's strict Pyright configuration, ResponseOutputText.text is still annotated as str, so this new content.text is not None check is reported as an always-true comparison and causes scripts/run-pyright to fail. For the null-text scenario this fix is targeting, the model field also needs to be made nullable (or the guard needs a type-safe workaround) so the change can pass CI while representing the API payload accurately.

Useful? React with 👍 / 👎.

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 Propagate optional image fields to ResponseInputParam

When callers use the normal client.responses.create(input=[...]) path, the overloads still refer to ResponseInputParam from response_input_param.py, which contains its own duplicate ImageGenerationCall where result and status remain Required. This change fixes only the standalone ResponseInputItemParam, so the documented image-id input still produces type errors for the primary Responses create API; update the duplicate definition or make ResponseInputParam reuse this item type.

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