From 2df14238bdec98a55480a7f29dc584b0c7d3d586 Mon Sep 17 00:00:00 2001
From: markstuart-oai <323302876+markstuart-oai@users.noreply.github.com>
Date: Fri, 30 Oct 2026 18:52:10 +0000
Subject: [PATCH] feat(api): support response format updates and local output
types
Castiron-Internal-PR: https://github.com/openai/openai-python-internal/pull/365
Castiron-Source-SHA: 2d22a5018f49c135bb0ee85d0b8db74f1b52f5f3
Castiron-Public-Base-SHA: 68b962c7368c2c955771c731a769e5ef15e8f781
---
.castiron.stats.yml | 12 +-
api_reference/openapi.transformed.yml | 78 +++++--
helpers.md | 35 +++
scripts/castiron/generated-files.json | 28 +--
src/openai/lib/_response_resources.py | 50 ++++-
src/openai/lib/responses_tool_runner.py | 7 +-
.../resources/beta/responses/responses.py | 12 +-
src/openai/resources/live/forks.py | 12 +-
src/openai/resources/live/live.py | 12 +-
src/openai/resources/live/sideband.py | 12 +-
src/openai/resources/realtime/realtime.py | 12 +-
.../realtime/translations/translations.py | 12 +-
src/openai/resources/responses/api.md | 2 +-
src/openai/resources/responses/responses.py | 204 +++++++++++++++++-
...beta_response_configuration_update_item.py | 37 +++-
...esponse_configuration_update_item_param.py | 35 ++-
...e_configuration_update_item_param_param.py | 40 +++-
.../response_configuration_update_item.py | 31 ++-
...esponse_configuration_update_item_param.py | 29 ++-
...e_configuration_update_item_param_param.py | 26 ++-
tests/lib/responses/test_parse_as.py | 127 +++++++++++
tests/lib/test_responses_tool_runner.py | 28 +++
22 files changed, 748 insertions(+), 93 deletions(-)
create mode 100644 tests/lib/responses/test_parse_as.py
diff --git a/.castiron.stats.yml b/.castiron.stats.yml
index 43732832aa..ac5d1917c3 100644
--- a/.castiron.stats.yml
+++ b/.castiron.stats.yml
@@ -1,7 +1,7 @@
schema_version: 1
-generation_id: e511c488-31b9-4833-baf5-cfb49bbba7ac
-openapi_spec_hash: d57efec7c76a156447c9ab739b446dc1
-openapi_transformed_spec_hash: b34c897c8cec867ec4ac8e22727c5975
-config_hash: 8da38e8d8f706786c2d8586691168746
-codegen_sha: e46410d5a9751f068b9afce57ecf25abda75de09
-codegen_hash: 33536449fa4898b45941d5fa4b5a958f88e2701a4b85fae21c372b75614198cd
+generation_id: 7b7feee1-6807-4c20-98e0-610aab05b2da
+openapi_spec_hash: 51b199ef7fd5542bc4910765f9becd60
+openapi_transformed_spec_hash: 73822d924c15a6ed58dcfe8f616bd4be
+config_hash: b707b209c892562880c415693d111ad6
+codegen_sha: e1054c07fb03a8025411f0ff6ee0a5ff17e09109
+codegen_hash: 73c66bd2bff47ee2bbd2d04a174184dec767ebe300a3bd41443938d914013ef0
diff --git a/api_reference/openapi.transformed.yml b/api_reference/openapi.transformed.yml
index 1593241a6e..b17e12fee9 100644
--- a/api_reference/openapi.transformed.yml
+++ b/api_reference/openapi.transformed.yml
@@ -65425,8 +65425,8 @@ components:
ResponseConfigurationUpdate:
type: object
description: |
- A configuration update that applies to subsequent responses until it is
- replaced by another configuration update.
+ A configuration update for subsequent responses. Each setting stays in effect
+ until a later update replaces that setting.
properties:
id:
type: string
@@ -65448,15 +65448,27 @@ components:
description: |
The reasoning effort used for subsequent responses until another
configuration update replaces it.
+ text:
+ type: object
+ description: The text output configuration for subsequent responses.
+ properties:
+ format:
+ description: Use a JSON schema for structured output or text to return to plain text.
+ discriminator:
+ propertyName: type
+ anyOf:
+ - $ref: '#/components/schemas/ResponseFormatText'
+ - $ref: '#/components/schemas/TextResponseFormatJsonSchema'
+ required:
+ - format
required:
- id
- type
ResponseConfigurationUpdateItemParam:
type: object
description: |
- An update to the conversation's response configuration. The configuration
- remains in effect for subsequent responses until it is replaced by another
- configuration update.
+ An update to the conversation's response configuration. Supply reasoning,
+ text, or both. Each setting stays in effect until a later update replaces it.
properties:
id:
anyOf:
@@ -65480,6 +65492,19 @@ components:
description: |
The reasoning effort to use for subsequent responses until another
configuration update replaces it.
+ text:
+ type: object
+ description: The text output configuration for subsequent responses.
+ properties:
+ format:
+ description: Use a JSON schema for structured output or text to return to plain text.
+ discriminator:
+ propertyName: type
+ anyOf:
+ - $ref: '#/components/schemas/ResponseFormatText'
+ - $ref: '#/components/schemas/TextResponseFormatJsonSchema'
+ required:
+ - format
required:
- type
ResponseContentPartAddedEvent:
@@ -71066,13 +71091,13 @@ components:
Setting to `{ "type": "json_object" }` enables the older JSON mode, which
ensures the message the model generates is valid JSON. Using `json_schema`
is preferred for models that support it.
+ discriminator:
+ propertyName: type
anyOf:
- $ref: '#/components/schemas/ResponseFormatText'
- $ref: '#/components/schemas/TextResponseFormatJsonSchema'
x-openai-ruby-json-schema-input: true
- $ref: '#/components/schemas/ResponseFormatJsonObject'
- discriminator:
- propertyName: type
TextResponseFormatJsonSchema:
x-openai-structured-output: json_schema_default_strict
type: object
@@ -99478,12 +99503,12 @@ components:
Setting to `{ "type": "json_object" }` enables the older JSON mode, which
ensures the message the model generates is valid JSON. Using `json_schema`
is preferred for models that support it.
+ discriminator:
+ propertyName: type
anyOf:
- $ref: '#/components/schemas/BetaResponseFormatText'
- $ref: '#/components/schemas/BetaTextResponseFormatJsonSchema'
- $ref: '#/components/schemas/BetaResponseFormatJsonObject'
- discriminator:
- propertyName: type
BetaResponseFormatJsonObject:
type: object
title: JSON object
@@ -102546,9 +102571,8 @@ components:
BetaResponseConfigurationUpdateItemParam:
type: object
description: |
- An update to the conversation's response configuration. The configuration
- remains in effect for subsequent responses until it is replaced by another
- configuration update.
+ An update to the conversation's response configuration. Supply reasoning,
+ text, or both. Each setting stays in effect until a later update replaces it.
properties:
agent:
anyOf:
@@ -102577,6 +102601,19 @@ components:
description: |
The reasoning effort to use for subsequent responses until another
configuration update replaces it.
+ text:
+ type: object
+ description: The text output configuration for subsequent responses.
+ properties:
+ format:
+ description: Use a JSON schema for structured output or text to return to plain text.
+ discriminator:
+ propertyName: type
+ anyOf:
+ - $ref: '#/components/schemas/BetaResponseFormatText'
+ - $ref: '#/components/schemas/BetaTextResponseFormatJsonSchema'
+ required:
+ - format
required:
- type
BetaAdditionalToolsItemParam:
@@ -105303,8 +105340,8 @@ components:
BetaResponseConfigurationUpdate:
type: object
description: |
- A configuration update that applies to subsequent responses until it is
- replaced by another configuration update.
+ A configuration update for subsequent responses. Each setting stays in effect
+ until a later update replaces that setting.
properties:
agent:
$ref: '#/components/schemas/BetaAgentTag'
@@ -105329,6 +105366,19 @@ components:
description: |
The reasoning effort used for subsequent responses until another
configuration update replaces it.
+ text:
+ type: object
+ description: The text output configuration for subsequent responses.
+ properties:
+ format:
+ description: Use a JSON schema for structured output or text to return to plain text.
+ discriminator:
+ propertyName: type
+ anyOf:
+ - $ref: '#/components/schemas/BetaResponseFormatText'
+ - $ref: '#/components/schemas/BetaTextResponseFormatJsonSchema'
+ required:
+ - format
required:
- id
- type
diff --git a/helpers.md b/helpers.md
index c979675254..8be574b2ce 100644
--- a/helpers.md
+++ b/helpers.md
@@ -63,6 +63,41 @@ output into a Pydantic model. The same parsing rules apply to
Use `output_parsed` for the structured result and retain `output` when replaying
messages so their phases are preserved.
+When the output schema changes during a conversation, pass `parse_as` to
+`client.responses.parse()` to select the local output type. For example, if a
+previous turn switched to `DetailedAnswer`, continue with this call.
+
+```py
+response = client.responses.parse(
+ model=model,
+ previous_response_id=previous.id,
+ input="Explain the evidence.",
+ parse_as=DetailedAnswer,
+)
+```
+
+The SDK parses the answer as `DetailedAnswer`. The request inherits its output
+format from the previous turn. If you also pass `text_format=ShortAnswer`,
+the request sends that class's schema as the top-level format. Pass
+`parse_as=None` to read unparsed text when the previous turn reset the format.
+You can also pass a format update directly in `input`. For example, given a
+JSON schema format named `detailed_format`:
+
+```py
+response = client.responses.parse(
+ model=model,
+ input=[
+ {"type": "configuration_update", "text": {"format": detailed_format}},
+ {"role": "user", "content": "Explain the evidence."},
+ ],
+ parse_as=DetailedAnswer,
+)
+```
+
+To switch back to plain text, send `{"type": "text"}` as the update's format.
+A reasoning-only update keeps the active format. Retrieved configuration items
+include `item.text.format` when the input supplied it.
+
## Auto-parsing function tool calls
The `.parse()` method will also automatically parse `function` tool calls if:
diff --git a/scripts/castiron/generated-files.json b/scripts/castiron/generated-files.json
index f9333d51f2..b336368297 100644
--- a/scripts/castiron/generated-files.json
+++ b/scripts/castiron/generated-files.json
@@ -379,7 +379,7 @@
},
"src/openai/resources/beta/responses/responses.py": {
"mode": "100644",
- "sha256": "043ff25a6a7f078bb0b11f6368ee136d7f3bb371c65360c8eb608afaf86de023"
+ "sha256": "effa8db6d9a676f1f264f0b6acb3252d8dcd5f75db251bfd8b5d45ea49f0f533"
},
"src/openai/resources/beta/threads/__init__.py": {
"mode": "100644",
@@ -559,11 +559,11 @@
},
"src/openai/resources/live/forks.py": {
"mode": "100644",
- "sha256": "eda42fc4ed92675d23eb62905f154a5b115567c158dd4b0fe5cf3d0824e2bd9d"
+ "sha256": "7fe78c144680df54628773683f9dd5b06c5241d0218b4f4c745890ddae225c92"
},
"src/openai/resources/live/live.py": {
"mode": "100644",
- "sha256": "a42b9133f64a4421eafbb1d0d10a9cddcefdc872270518f7bdf61ce192265985"
+ "sha256": "86be3e074d011a750fa52a4d216a7e87ee5c8e82f6ccec00102c03a539199882"
},
"src/openai/resources/live/sessions.py": {
"mode": "100644",
@@ -571,7 +571,7 @@
},
"src/openai/resources/live/sideband.py": {
"mode": "100644",
- "sha256": "7ed377e577ce3b229692bd112059914700741cbff042a7a92cc2a581ec2ac1f8"
+ "sha256": "d19149c042afcd8c8efca3d7794092acc1c2f76a4bcb4f6f620547314a78e59b"
},
"src/openai/resources/models.py": {
"mode": "100644",
@@ -599,7 +599,7 @@
},
"src/openai/resources/realtime/realtime.py": {
"mode": "100644",
- "sha256": "563d955afa8c0b023f4fcb353565db0b4348394f36e6a02659db6c2885286149"
+ "sha256": "d45ef5f2d1e25e6f8761592a9fb5739d7a344c6e64a4e7a4006418c16f05a540"
},
"src/openai/resources/realtime/translations/__init__.py": {
"mode": "100644",
@@ -611,7 +611,7 @@
},
"src/openai/resources/realtime/translations/translations.py": {
"mode": "100644",
- "sha256": "8f45807c74c477a1d13df9979c6274a089c380004b943fcb6d9342b1a7ac3ef1"
+ "sha256": "4a6f072cea78d5afab3fe1c995b939ff8c79f449665346c729abb5c516c53f21"
},
"src/openai/resources/responses/__init__.py": {
"mode": "100644",
@@ -619,7 +619,7 @@
},
"src/openai/resources/responses/api.md": {
"mode": "100644",
- "sha256": "e7815b1278aa2780bb885ac5a249fe00859dff67d3899b708d25d2968e734785"
+ "sha256": "b635f37005fb7126cc69a7b69e3f14a9df135d9523271cc4407b1ffcb9bd5c8c"
},
"src/openai/resources/responses/input_items.py": {
"mode": "100644",
@@ -631,7 +631,7 @@
},
"src/openai/resources/responses/responses.py": {
"mode": "100644",
- "sha256": "6e536e48d77d9ed583bd0c86a4f189b664e8636ae46a52a1c80fac28ffc3b4ec"
+ "sha256": "f844db6ec37b49de883ab2c507570b94c29e8c2609ee93cf45c0aec19fd671d3"
},
"src/openai/resources/safety/__init__.py": {
"mode": "100644",
@@ -2523,15 +2523,15 @@
},
"src/openai/types/beta/beta_response_configuration_update_item.py": {
"mode": "100644",
- "sha256": "4bd97e32e710edbd688bf8c6eac0dd8f24d1d0a64da60efda522ffefa0790046"
+ "sha256": "eebab46ab26a7650a94cf353d3b98c86fc12378284a9431cd4379808515ba6b0"
},
"src/openai/types/beta/beta_response_configuration_update_item_param.py": {
"mode": "100644",
- "sha256": "eb06f107b27ef1fa3a9266bfaf8921b68384faf7c0dd0d300e554767bb7aed30"
+ "sha256": "c10511a06c2bd67530ce2f6ab2e94b63e49602e4e29df8cfb64e4bb73359e3a9"
},
"src/openai/types/beta/beta_response_configuration_update_item_param_param.py": {
"mode": "100644",
- "sha256": "3c9bcd2d7e730b102b0674424e56cbd35e6bed0664a58acc1830466eea28d2be"
+ "sha256": "269015eac1b4dd3a8bfbd1053218e64c9a2fd3cac4b50db961089f3813c6bbd0"
},
"src/openai/types/beta/beta_response_container_reference.py": {
"mode": "100644",
@@ -6167,15 +6167,15 @@
},
"src/openai/types/responses/response_configuration_update_item.py": {
"mode": "100644",
- "sha256": "4d07aca0f5dbb6f87f1db31fc16dbba2e4a49d0670edbd46256907066605a703"
+ "sha256": "89a39d84322be94f3805145e1ca70bae0c2b0500f89102ea93918700ecc65a02"
},
"src/openai/types/responses/response_configuration_update_item_param.py": {
"mode": "100644",
- "sha256": "c8eb1b18f489c4d820bc17c10e505e97a4da8059592e903882aae5ed3ca919ad"
+ "sha256": "fb7a2cb3b72d2a5dd73fc78ad6194bce51eb4e1b805d9ccc85c2725e3c0bab3f"
},
"src/openai/types/responses/response_configuration_update_item_param_param.py": {
"mode": "100644",
- "sha256": "8e177dbfa309934a8aedbc5bd278b48bddfa3365a42cfb661ead8677aa924a9b"
+ "sha256": "d7e43648f155a90a35d2f57d31e8f8481c463ada28805dd810d17db00304ce23"
},
"src/openai/types/responses/response_container_reference.py": {
"mode": "100644",
diff --git a/src/openai/lib/_response_resources.py b/src/openai/lib/_response_resources.py
index 27795b7a8d..0d0050d740 100644
--- a/src/openai/lib/_response_resources.py
+++ b/src/openai/lib/_response_resources.py
@@ -8,7 +8,7 @@
import httpx2
from openai._types import NOT_GIVEN, Body, Omit, Query, Headers, NotGiven, omit
-from openai._utils import is_given, maybe_transform
+from openai._utils import is_dict, is_list, is_given, maybe_transform
from openai._streaming import Stream
from openai.lib._tools import PydanticFunctionTool, ResponsesPydanticFunctionTool
from openai._base_client import make_request_options
@@ -36,10 +36,41 @@
from openai.resources.responses.responses import Responses, AsyncResponses
+def _output_text_parser(
+ *,
+ input: object,
+ text: ResponseTextConfigParam | Omit,
+ text_format: type[TextFormatT] | Omit,
+ parse_as: type[TextFormatT] | None | Omit,
+) -> type[TextFormatT] | Omit:
+ if parse_as is None:
+ return omit
+
+ if is_list(input):
+ for item in reversed(input):
+ if not is_dict(item) or item.get("type") != "configuration_update":
+ continue
+ update = item.get("text")
+ if not is_dict(update) or not is_dict(fmt := update.get("format")):
+ continue
+ if fmt.get("type") == "text":
+ if is_given(parse_as):
+ raise TypeError("Cannot use parse_as with a plain-text format update")
+ return omit
+ if is_given(text_format) and not is_given(parse_as) and (not is_given(text) or fmt != text.get("format")):
+ raise TypeError(
+ "Pass parse_as for the updated output schema, or parse_as=None to read plain output_text"
+ )
+ break
+
+ return parse_as if is_given(parse_as) else text_format
+
+
def parse(
resource: Responses,
*,
text_format: type[TextFormatT] | Omit = omit,
+ parse_as: type[TextFormatT] | None | Omit = omit,
access_programs: response_create_params.AccessPrograms | Omit = omit,
background: Optional[bool] | Omit = omit,
context_management: Optional[Iterable[response_create_params.ContextManagement]] | Omit = omit,
@@ -89,12 +120,18 @@ def parse(
text = copy(text)
text["format"] = _type_to_text_format_param(text_format)
+ output_format = _output_text_parser(
+ input=extra_body.get("input", input) if is_dict(extra_body) else input,
+ text=text,
+ text_format=text_format,
+ parse_as=parse_as,
+ )
tools = _make_tools(tools)
def parser(raw_response: Response) -> ParsedResponse[TextFormatT]:
return parse_response(
input_tools=tools,
- text_format=text_format,
+ text_format=output_format,
response=raw_response,
)
@@ -156,6 +193,7 @@ async def async_parse(
resource: AsyncResponses,
*,
text_format: type[TextFormatT] | Omit = omit,
+ parse_as: type[TextFormatT] | None | Omit = omit,
access_programs: response_create_params.AccessPrograms | Omit = omit,
background: Optional[bool] | Omit = omit,
context_management: Optional[Iterable[response_create_params.ContextManagement]] | Omit = omit,
@@ -205,12 +243,18 @@ async def async_parse(
text = copy(text)
text["format"] = _type_to_text_format_param(text_format)
+ output_format = _output_text_parser(
+ input=extra_body.get("input", input) if is_dict(extra_body) else input,
+ text=text,
+ text_format=text_format,
+ parse_as=parse_as,
+ )
tools = _make_tools(tools)
def parser(raw_response: Response) -> ParsedResponse[TextFormatT]:
return parse_response(
input_tools=tools,
- text_format=text_format,
+ text_format=output_format,
response=raw_response,
)
diff --git a/src/openai/lib/responses_tool_runner.py b/src/openai/lib/responses_tool_runner.py
index 09b5b70298..59a3b9e6f4 100644
--- a/src/openai/lib/responses_tool_runner.py
+++ b/src/openai/lib/responses_tool_runner.py
@@ -49,6 +49,7 @@
from ..types.responses.tool_param import ToolParam, FunctionToolParam
from .streaming.responses._events import ResponseStreamEvent
from .streaming.responses._responses import ResponseStream, AsyncResponseStream
+from ..types.responses.response_input import ResponseInput
from ..types.responses.response_input_param import ComputerCallOutput, FunctionCallOutput, ResponseInputParam
from ..types.responses.response_output_item import McpCall, McpApprovalRequest, ResponseOutputItem
from ..types.responses.response_create_params import ResponseCreateParamsStreaming, ResponseCreateParamsNonStreaming
@@ -506,8 +507,9 @@ def _history_calls(history: ResponseInputParam, tools: list[ToolParam], *, budge
# Modern computer calls can omit the legacy checklist; validate these with
# the same action parser as dispatch, without changing the saved input.
try:
+ # The input models alias wire fields such as "schema" that are reserved by Pydantic v1.
validate_type(
- type_=ResponseInputParam,
+ type_=ResponseInput,
value=[item for item in history if item.get("type") not in ("computer_call", "computer_call_output")],
)
except pydantic.ValidationError:
@@ -522,6 +524,9 @@ def _history_calls(history: ResponseInputParam, tools: list[ToolParam], *, budge
answered: set[str] = set()
for index, item in enumerate(cast(list[dict[str, object]], history)):
kind = item.get("type", "message")
+ if kind == "configuration_update":
+ # Already schema-validated above. There is no tool call to pair.
+ continue
if budget_only:
if kind == "compaction_trigger":
if index != len(history) - 1:
diff --git a/src/openai/resources/beta/responses/responses.py b/src/openai/resources/beta/responses/responses.py
index 940cd3a84b..aa5aa1507d 100644
--- a/src/openai/resources/beta/responses/responses.py
+++ b/src/openai/resources/beta/responses/responses.py
@@ -5276,7 +5276,11 @@ async def __aexit__(
self, exc_type: type[BaseException] | None, exc: BaseException | None, exc_tb: TracebackType | None
) -> None:
if self.__connection is not None:
- await self.__connection.close()
+ try:
+ await self.__connection.close()
+ except BaseException:
+ if exc is None:
+ raise
class ResponsesConnection(WebSocketConnectionBase):
@@ -5690,7 +5694,11 @@ def __exit__(
self, exc_type: type[BaseException] | None, exc: BaseException | None, exc_tb: TracebackType | None
) -> None:
if self.__connection is not None:
- self.__connection.close()
+ try:
+ self.__connection.close()
+ except BaseException:
+ if exc is None:
+ raise
class BaseResponsesConnectionResource:
diff --git a/src/openai/resources/live/forks.py b/src/openai/resources/live/forks.py
index 8f5dcd0d28..7ca2d8420b 100644
--- a/src/openai/resources/live/forks.py
+++ b/src/openai/resources/live/forks.py
@@ -531,7 +531,11 @@ async def __aexit__(
self, exc_type: type[BaseException] | None, exc: BaseException | None, exc_tb: TracebackType | None
) -> None:
if self.__connection is not None:
- await self.__connection.close()
+ try:
+ await self.__connection.close()
+ except BaseException:
+ if exc is None:
+ raise
class ForksConnection(WebSocketConnectionBase):
@@ -953,7 +957,11 @@ def __exit__(
self, exc_type: type[BaseException] | None, exc: BaseException | None, exc_tb: TracebackType | None
) -> None:
if self.__connection is not None:
- self.__connection.close()
+ try:
+ self.__connection.close()
+ except BaseException:
+ if exc is None:
+ raise
class BaseForksConnectionResource:
diff --git a/src/openai/resources/live/live.py b/src/openai/resources/live/live.py
index 9a474b8da6..82d95aaaa5 100644
--- a/src/openai/resources/live/live.py
+++ b/src/openai/resources/live/live.py
@@ -744,7 +744,11 @@ async def __aexit__(
self, exc_type: type[BaseException] | None, exc: BaseException | None, exc_tb: TracebackType | None
) -> None:
if self.__connection is not None:
- await self.__connection.close()
+ try:
+ await self.__connection.close()
+ except BaseException:
+ if exc is None:
+ raise
class LiveConnection(WebSocketConnectionBase):
@@ -1158,7 +1162,11 @@ def __exit__(
self, exc_type: type[BaseException] | None, exc: BaseException | None, exc_tb: TracebackType | None
) -> None:
if self.__connection is not None:
- self.__connection.close()
+ try:
+ self.__connection.close()
+ except BaseException:
+ if exc is None:
+ raise
class BaseLiveConnectionResource:
diff --git a/src/openai/resources/live/sideband.py b/src/openai/resources/live/sideband.py
index f0fd8d20b7..f9e767805c 100644
--- a/src/openai/resources/live/sideband.py
+++ b/src/openai/resources/live/sideband.py
@@ -543,7 +543,11 @@ async def __aexit__(
self, exc_type: type[BaseException] | None, exc: BaseException | None, exc_tb: TracebackType | None
) -> None:
if self.__connection is not None:
- await self.__connection.close()
+ try:
+ await self.__connection.close()
+ except BaseException:
+ if exc is None:
+ raise
class SidebandConnection(WebSocketConnectionBase):
@@ -974,7 +978,11 @@ def __exit__(
self, exc_type: type[BaseException] | None, exc: BaseException | None, exc_tb: TracebackType | None
) -> None:
if self.__connection is not None:
- self.__connection.close()
+ try:
+ self.__connection.close()
+ except BaseException:
+ if exc is None:
+ raise
class BaseSidebandConnectionResource:
diff --git a/src/openai/resources/realtime/realtime.py b/src/openai/resources/realtime/realtime.py
index eec545bda9..4180fc06af 100644
--- a/src/openai/resources/realtime/realtime.py
+++ b/src/openai/resources/realtime/realtime.py
@@ -750,7 +750,11 @@ async def __aexit__(
self, exc_type: type[BaseException] | None, exc: BaseException | None, exc_tb: TracebackType | None
) -> None:
if self.__connection is not None:
- await self.__connection.close()
+ try:
+ await self.__connection.close()
+ except BaseException:
+ if exc is None:
+ raise
class RealtimeConnection(WebSocketConnectionBase):
@@ -1199,7 +1203,11 @@ def __exit__(
self, exc_type: type[BaseException] | None, exc: BaseException | None, exc_tb: TracebackType | None
) -> None:
if self.__connection is not None:
- self.__connection.close()
+ try:
+ self.__connection.close()
+ except BaseException:
+ if exc is None:
+ raise
class BaseRealtimeConnectionResource:
diff --git a/src/openai/resources/realtime/translations/translations.py b/src/openai/resources/realtime/translations/translations.py
index 117e571311..37c8058780 100644
--- a/src/openai/resources/realtime/translations/translations.py
+++ b/src/openai/resources/realtime/translations/translations.py
@@ -615,7 +615,11 @@ async def __aexit__(
self, exc_type: type[BaseException] | None, exc: BaseException | None, exc_tb: TracebackType | None
) -> None:
if self.__connection is not None:
- await self.__connection.close()
+ try:
+ await self.__connection.close()
+ except BaseException:
+ if exc is None:
+ raise
class TranslationsConnection(WebSocketConnectionBase):
@@ -1035,7 +1039,11 @@ def __exit__(
self, exc_type: type[BaseException] | None, exc: BaseException | None, exc_tb: TracebackType | None
) -> None:
if self.__connection is not None:
- self.__connection.close()
+ try:
+ self.__connection.close()
+ except BaseException:
+ if exc is None:
+ raise
class BaseTranslationsConnectionResource:
diff --git a/src/openai/resources/responses/api.md b/src/openai/resources/responses/api.md
index 9ef00fb631..c45a2b2ae7 100644
--- a/src/openai/resources/responses/api.md
+++ b/src/openai/resources/responses/api.md
@@ -180,7 +180,7 @@ Methods:
- client.responses.delete(response_id) -> None
- client.responses.cancel(response_id) -> Response
- client.responses.compact(\*\*params) -> CompactedResponse
-- client.responses.parse(\*, text_format=..., access_programs=..., background=..., context_management=..., conversation=..., include=..., input=..., instructions=..., max_output_tokens=..., max_tool_calls=..., metadata=..., model=..., moderation=..., parallel_tool_calls=..., previous_response_id=..., prompt=..., prompt_cache_key=..., prompt_cache_options=..., prompt_cache_retention=..., reasoning=..., safety_identifier=..., service_tier=..., store=..., stream=..., stream_options=..., temperature=..., text=..., tool_choice=..., tools=..., top_logprobs=..., top_p=..., truncation=..., user=..., verbosity=..., extra_headers=..., extra_query=..., extra_body=..., timeout=...) -> ParsedResponse[TextFormatT]
+- client.responses.parse(\*, text_format=..., parse_as=..., access_programs=..., background=..., context_management=..., conversation=..., include=..., input=..., instructions=..., max_output_tokens=..., max_tool_calls=..., metadata=..., model=..., moderation=..., parallel_tool_calls=..., previous_response_id=..., prompt=..., prompt_cache_key=..., prompt_cache_options=..., prompt_cache_retention=..., reasoning=..., safety_identifier=..., service_tier=..., store=..., stream=..., stream_options=..., temperature=..., text=..., tool_choice=..., tools=..., top_logprobs=..., top_p=..., truncation=..., user=..., verbosity=..., extra_headers=..., extra_query=..., extra_body=..., timeout=...) -> ParsedResponse[Any]
- client.responses.stream(\*, response_id=..., input=..., model=..., access_programs=..., background=..., context_management=..., text_format=..., tools=..., conversation=..., include=..., instructions=..., max_output_tokens=..., max_tool_calls=..., metadata=..., parallel_tool_calls=..., moderation=..., previous_response_id=..., prompt=..., prompt_cache_key=..., prompt_cache_options=..., prompt_cache_retention=..., reasoning=..., safety_identifier=..., service_tier=..., store=..., stream_options=..., temperature=..., text=..., tool_choice=..., top_logprobs=..., top_p=..., truncation=..., user=..., starting_after=..., extra_headers=..., extra_query=..., extra_body=..., timeout=...) -> ResponseStreamManager[TextFormatT]
## InputItems
diff --git a/src/openai/resources/responses/responses.py b/src/openai/resources/responses/responses.py
index 4ac7102343..4e03633b7f 100644
--- a/src/openai/resources/responses/responses.py
+++ b/src/openai/resources/responses/responses.py
@@ -114,10 +114,57 @@ def with_streaming_response(self) -> ResponsesWithStreamingResponse:
"""
return ResponsesWithStreamingResponse(self)
+ @overload
+ def parse(
+ self,
+ *,
+ text_format: type | Omit = omit,
+ parse_as: type[TextFormatT] | None,
+ access_programs: response_create_params.AccessPrograms | Omit = omit,
+ background: Optional[bool] | Omit = omit,
+ context_management: Optional[Iterable[response_create_params.ContextManagement]] | Omit = omit,
+ conversation: Optional[response_create_params.Conversation] | Omit = omit,
+ include: Optional[List[ResponseIncludable]] | Omit = omit,
+ input: Union[str, ResponseInputParam] | Omit = omit,
+ instructions: Optional[str] | Omit = omit,
+ max_output_tokens: Optional[int] | Omit = omit,
+ max_tool_calls: Optional[int] | Omit = omit,
+ metadata: Optional[Metadata] | Omit = omit,
+ model: ResponsesModel | Omit = omit,
+ moderation: Optional[response_create_params.Moderation] | Omit = omit,
+ parallel_tool_calls: Optional[bool] | Omit = omit,
+ previous_response_id: Optional[str] | Omit = omit,
+ prompt: Optional[ResponsePromptParam] | Omit = omit,
+ prompt_cache_key: Optional[str] | Omit = omit,
+ prompt_cache_options: response_create_params.PromptCacheOptions | Omit = omit,
+ prompt_cache_retention: Optional[Literal["in_memory", "24h"]] | Omit = omit,
+ reasoning: Optional[Reasoning] | Omit = omit,
+ safety_identifier: Optional[str] | Omit = omit,
+ service_tier: Optional[ServiceTier] | Omit = omit,
+ store: Optional[bool] | Omit = omit,
+ stream: Optional[Literal[False]] | Literal[True] | Omit = omit,
+ stream_options: Optional[response_create_params.StreamOptions] | Omit = omit,
+ temperature: Optional[float] | Omit = omit,
+ text: ResponseTextConfigParam | Omit = omit,
+ tool_choice: response_create_params.ToolChoice | Omit = omit,
+ tools: Iterable[ParseableToolParam] | Omit = omit,
+ top_logprobs: Optional[int] | Omit = omit,
+ top_p: Optional[float] | Omit = omit,
+ truncation: Optional[Literal["auto", "disabled"]] | Omit = omit,
+ user: str | Omit = omit,
+ verbosity: Optional[Literal["low", "medium", "high"]] | Omit = omit,
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx2.Timeout | None | NotGiven = not_given,
+ ) -> ParsedResponse[TextFormatT]: ...
+
+ @overload
def parse(
self,
*,
text_format: type[TextFormatT] | Omit = omit,
+ parse_as: Omit = omit,
access_programs: response_create_params.AccessPrograms | Omit = omit,
background: Optional[bool] | Omit = omit,
context_management: Optional[Iterable[response_create_params.ContextManagement]] | Omit = omit,
@@ -155,11 +202,56 @@ def parse(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx2.Timeout | None | NotGiven = not_given,
- ) -> ParsedResponse[TextFormatT]:
- """parse"""
+ ) -> ParsedResponse[TextFormatT]: ...
+
+ def parse(
+ self,
+ *,
+ text_format: type | Omit = omit,
+ parse_as: type | None | Omit = omit,
+ access_programs: response_create_params.AccessPrograms | Omit = omit,
+ background: Optional[bool] | Omit = omit,
+ context_management: Optional[Iterable[response_create_params.ContextManagement]] | Omit = omit,
+ conversation: Optional[response_create_params.Conversation] | Omit = omit,
+ include: Optional[List[ResponseIncludable]] | Omit = omit,
+ input: Union[str, ResponseInputParam] | Omit = omit,
+ instructions: Optional[str] | Omit = omit,
+ max_output_tokens: Optional[int] | Omit = omit,
+ max_tool_calls: Optional[int] | Omit = omit,
+ metadata: Optional[Metadata] | Omit = omit,
+ model: ResponsesModel | Omit = omit,
+ moderation: Optional[response_create_params.Moderation] | Omit = omit,
+ parallel_tool_calls: Optional[bool] | Omit = omit,
+ previous_response_id: Optional[str] | Omit = omit,
+ prompt: Optional[ResponsePromptParam] | Omit = omit,
+ prompt_cache_key: Optional[str] | Omit = omit,
+ prompt_cache_options: response_create_params.PromptCacheOptions | Omit = omit,
+ prompt_cache_retention: Optional[Literal["in_memory", "24h"]] | Omit = omit,
+ reasoning: Optional[Reasoning] | Omit = omit,
+ safety_identifier: Optional[str] | Omit = omit,
+ service_tier: Optional[ServiceTier] | Omit = omit,
+ store: Optional[bool] | Omit = omit,
+ stream: Optional[Literal[False]] | Literal[True] | Omit = omit,
+ stream_options: Optional[response_create_params.StreamOptions] | Omit = omit,
+ temperature: Optional[float] | Omit = omit,
+ text: ResponseTextConfigParam | Omit = omit,
+ tool_choice: response_create_params.ToolChoice | Omit = omit,
+ tools: Iterable[ParseableToolParam] | Omit = omit,
+ top_logprobs: Optional[int] | Omit = omit,
+ top_p: Optional[float] | Omit = omit,
+ truncation: Optional[Literal["auto", "disabled"]] | Omit = omit,
+ user: str | Omit = omit,
+ verbosity: Optional[Literal["low", "medium", "high"]] | Omit = omit,
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx2.Timeout | None | NotGiven = not_given,
+ ) -> ParsedResponse[Any]:
+ """Parse output with `parse_as` when supplied. Only `text_format` sets the request schema."""
return parse(
self,
text_format=text_format,
+ parse_as=parse_as,
access_programs=access_programs,
background=background,
context_management=context_management,
@@ -1940,10 +2032,57 @@ def with_streaming_response(self) -> AsyncResponsesWithStreamingResponse:
"""
return AsyncResponsesWithStreamingResponse(self)
+ @overload
+ async def parse(
+ self,
+ *,
+ text_format: type | Omit = omit,
+ parse_as: type[TextFormatT] | None,
+ access_programs: response_create_params.AccessPrograms | Omit = omit,
+ background: Optional[bool] | Omit = omit,
+ context_management: Optional[Iterable[response_create_params.ContextManagement]] | Omit = omit,
+ conversation: Optional[response_create_params.Conversation] | Omit = omit,
+ include: Optional[List[ResponseIncludable]] | Omit = omit,
+ input: Union[str, ResponseInputParam] | Omit = omit,
+ instructions: Optional[str] | Omit = omit,
+ max_output_tokens: Optional[int] | Omit = omit,
+ max_tool_calls: Optional[int] | Omit = omit,
+ metadata: Optional[Metadata] | Omit = omit,
+ model: ResponsesModel | Omit = omit,
+ moderation: Optional[response_create_params.Moderation] | Omit = omit,
+ parallel_tool_calls: Optional[bool] | Omit = omit,
+ previous_response_id: Optional[str] | Omit = omit,
+ prompt: Optional[ResponsePromptParam] | Omit = omit,
+ prompt_cache_key: Optional[str] | Omit = omit,
+ prompt_cache_options: response_create_params.PromptCacheOptions | Omit = omit,
+ prompt_cache_retention: Optional[Literal["in_memory", "24h"]] | Omit = omit,
+ reasoning: Optional[Reasoning] | Omit = omit,
+ safety_identifier: Optional[str] | Omit = omit,
+ service_tier: Optional[ServiceTier] | Omit = omit,
+ store: Optional[bool] | Omit = omit,
+ stream: Optional[Literal[False]] | Literal[True] | Omit = omit,
+ stream_options: Optional[response_create_params.StreamOptions] | Omit = omit,
+ temperature: Optional[float] | Omit = omit,
+ text: ResponseTextConfigParam | Omit = omit,
+ tool_choice: response_create_params.ToolChoice | Omit = omit,
+ tools: Iterable[ParseableToolParam] | Omit = omit,
+ top_logprobs: Optional[int] | Omit = omit,
+ top_p: Optional[float] | Omit = omit,
+ truncation: Optional[Literal["auto", "disabled"]] | Omit = omit,
+ user: str | Omit = omit,
+ verbosity: Optional[Literal["low", "medium", "high"]] | Omit = omit,
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx2.Timeout | None | NotGiven = not_given,
+ ) -> ParsedResponse[TextFormatT]: ...
+
+ @overload
async def parse(
self,
*,
text_format: type[TextFormatT] | Omit = omit,
+ parse_as: Omit = omit,
access_programs: response_create_params.AccessPrograms | Omit = omit,
background: Optional[bool] | Omit = omit,
context_management: Optional[Iterable[response_create_params.ContextManagement]] | Omit = omit,
@@ -1981,11 +2120,56 @@ async def parse(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx2.Timeout | None | NotGiven = not_given,
- ) -> ParsedResponse[TextFormatT]:
- """parse"""
+ ) -> ParsedResponse[TextFormatT]: ...
+
+ async def parse(
+ self,
+ *,
+ text_format: type | Omit = omit,
+ parse_as: type | None | Omit = omit,
+ access_programs: response_create_params.AccessPrograms | Omit = omit,
+ background: Optional[bool] | Omit = omit,
+ context_management: Optional[Iterable[response_create_params.ContextManagement]] | Omit = omit,
+ conversation: Optional[response_create_params.Conversation] | Omit = omit,
+ include: Optional[List[ResponseIncludable]] | Omit = omit,
+ input: Union[str, ResponseInputParam] | Omit = omit,
+ instructions: Optional[str] | Omit = omit,
+ max_output_tokens: Optional[int] | Omit = omit,
+ max_tool_calls: Optional[int] | Omit = omit,
+ metadata: Optional[Metadata] | Omit = omit,
+ model: ResponsesModel | Omit = omit,
+ moderation: Optional[response_create_params.Moderation] | Omit = omit,
+ parallel_tool_calls: Optional[bool] | Omit = omit,
+ previous_response_id: Optional[str] | Omit = omit,
+ prompt: Optional[ResponsePromptParam] | Omit = omit,
+ prompt_cache_key: Optional[str] | Omit = omit,
+ prompt_cache_options: response_create_params.PromptCacheOptions | Omit = omit,
+ prompt_cache_retention: Optional[Literal["in_memory", "24h"]] | Omit = omit,
+ reasoning: Optional[Reasoning] | Omit = omit,
+ safety_identifier: Optional[str] | Omit = omit,
+ service_tier: Optional[ServiceTier] | Omit = omit,
+ store: Optional[bool] | Omit = omit,
+ stream: Optional[Literal[False]] | Literal[True] | Omit = omit,
+ stream_options: Optional[response_create_params.StreamOptions] | Omit = omit,
+ temperature: Optional[float] | Omit = omit,
+ text: ResponseTextConfigParam | Omit = omit,
+ tool_choice: response_create_params.ToolChoice | Omit = omit,
+ tools: Iterable[ParseableToolParam] | Omit = omit,
+ top_logprobs: Optional[int] | Omit = omit,
+ top_p: Optional[float] | Omit = omit,
+ truncation: Optional[Literal["auto", "disabled"]] | Omit = omit,
+ user: str | Omit = omit,
+ verbosity: Optional[Literal["low", "medium", "high"]] | Omit = omit,
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx2.Timeout | None | NotGiven = not_given,
+ ) -> ParsedResponse[Any]:
+ """Parse output with `parse_as` when supplied. Only `text_format` sets the request schema."""
return await async_parse(
self,
text_format=text_format,
+ parse_as=parse_as,
access_programs=access_programs,
background=background,
context_management=context_management,
@@ -4281,7 +4465,11 @@ async def __aexit__(
self, exc_type: type[BaseException] | None, exc: BaseException | None, exc_tb: TracebackType | None
) -> None:
if self.__connection is not None:
- await self.__connection.close()
+ try:
+ await self.__connection.close()
+ except BaseException:
+ if exc is None:
+ raise
class ResponsesConnection(WebSocketConnectionBase):
@@ -4696,7 +4884,11 @@ def __exit__(
self, exc_type: type[BaseException] | None, exc: BaseException | None, exc_tb: TracebackType | None
) -> None:
if self.__connection is not None:
- self.__connection.close()
+ try:
+ self.__connection.close()
+ except BaseException:
+ if exc is None:
+ raise
class BaseResponsesConnectionResource:
diff --git a/src/openai/types/beta/beta_response_configuration_update_item.py b/src/openai/types/beta/beta_response_configuration_update_item.py
index a6d1780521..e0779ef6d3 100644
--- a/src/openai/types/beta/beta_response_configuration_update_item.py
+++ b/src/openai/types/beta/beta_response_configuration_update_item.py
@@ -1,11 +1,13 @@
# File generated from our OpenAPI spec by Castiron. See CONTRIBUTING.md for details.
-from typing import Optional
-from typing_extensions import Literal
+from typing import Union, Optional
+from typing_extensions import Literal, Annotated, TypeAlias
+from ..._utils import PropertyInfo
from ..._models import BaseModel
+from .beta_response_format_text_json_schema_config import BetaResponseFormatTextJSONSchemaConfig
-__all__ = ["BetaResponseConfigurationUpdateItem", "Agent", "Reasoning"]
+__all__ = ["BetaResponseConfigurationUpdateItem", "Agent", "Reasoning", "Text", "TextFormat", "TextFormatText"]
class Agent(BaseModel):
@@ -25,10 +27,30 @@ class Reasoning(BaseModel):
"""
+class TextFormatText(BaseModel):
+ """Default response format. Used to generate text responses."""
+
+ type: Literal["text"]
+ """The type of response format being defined. Always `text`."""
+
+
+TextFormat: TypeAlias = Annotated[
+ Union[TextFormatText, BetaResponseFormatTextJSONSchemaConfig], PropertyInfo(discriminator="type")
+]
+
+
+class Text(BaseModel):
+ """The text output configuration for subsequent responses."""
+
+ format: TextFormat
+ """Use a JSON schema for structured output or text to return to plain text."""
+
+
class BetaResponseConfigurationUpdateItem(BaseModel):
- """
- A configuration update that applies to subsequent responses until it is
- replaced by another configuration update.
+ """A configuration update for subsequent responses.
+
+ Each setting stays in effect
+ until a later update replaces that setting.
"""
id: str
@@ -42,3 +64,6 @@ class BetaResponseConfigurationUpdateItem(BaseModel):
reasoning: Optional[Reasoning] = None
"""The reasoning configuration applied by this update."""
+
+ text: Optional[Text] = None
+ """The text output configuration for subsequent responses."""
diff --git a/src/openai/types/beta/beta_response_configuration_update_item_param.py b/src/openai/types/beta/beta_response_configuration_update_item_param.py
index 36090be07d..2f6e759918 100644
--- a/src/openai/types/beta/beta_response_configuration_update_item_param.py
+++ b/src/openai/types/beta/beta_response_configuration_update_item_param.py
@@ -1,11 +1,13 @@
# File generated from our OpenAPI spec by Castiron. See CONTRIBUTING.md for details.
-from typing import Optional
-from typing_extensions import Literal
+from typing import Union, Optional
+from typing_extensions import Literal, Annotated, TypeAlias
+from ..._utils import PropertyInfo
from ..._models import BaseModel
+from .beta_response_format_text_json_schema_config import BetaResponseFormatTextJSONSchemaConfig
-__all__ = ["BetaResponseConfigurationUpdateItemParam", "Agent", "Reasoning"]
+__all__ = ["BetaResponseConfigurationUpdateItemParam", "Agent", "Reasoning", "Text", "TextFormat", "TextFormatText"]
class Agent(BaseModel):
@@ -25,12 +27,30 @@ class Reasoning(BaseModel):
"""
+class TextFormatText(BaseModel):
+ """Default response format. Used to generate text responses."""
+
+ type: Literal["text"]
+ """The type of response format being defined. Always `text`."""
+
+
+TextFormat: TypeAlias = Annotated[
+ Union[TextFormatText, BetaResponseFormatTextJSONSchemaConfig], PropertyInfo(discriminator="type")
+]
+
+
+class Text(BaseModel):
+ """The text output configuration for subsequent responses."""
+
+ format: TextFormat
+ """Use a JSON schema for structured output or text to return to plain text."""
+
+
class BetaResponseConfigurationUpdateItemParam(BaseModel):
"""An update to the conversation's response configuration.
- The configuration
- remains in effect for subsequent responses until it is replaced by another
- configuration update.
+ Supply reasoning,
+ text, or both. Each setting stays in effect until a later update replaces it.
"""
type: Literal["configuration_update"]
@@ -44,3 +64,6 @@ class BetaResponseConfigurationUpdateItemParam(BaseModel):
reasoning: Optional[Reasoning] = None
"""Updates to reasoning configuration. Only effort is supported."""
+
+ text: Optional[Text] = None
+ """The text output configuration for subsequent responses."""
diff --git a/src/openai/types/beta/beta_response_configuration_update_item_param_param.py b/src/openai/types/beta/beta_response_configuration_update_item_param_param.py
index 1c9b060f83..4b233c90c6 100644
--- a/src/openai/types/beta/beta_response_configuration_update_item_param_param.py
+++ b/src/openai/types/beta/beta_response_configuration_update_item_param_param.py
@@ -2,10 +2,19 @@
from __future__ import annotations
-from typing import Optional
-from typing_extensions import Literal, Required, TypedDict
+from typing import Union, Optional
+from typing_extensions import Literal, Required, TypeAlias, TypedDict
-__all__ = ["BetaResponseConfigurationUpdateItemParamParam", "Agent", "Reasoning"]
+from .beta_response_format_text_json_schema_config_param import BetaResponseFormatTextJSONSchemaConfigParam
+
+__all__ = [
+ "BetaResponseConfigurationUpdateItemParamParam",
+ "Agent",
+ "Reasoning",
+ "Text",
+ "TextFormat",
+ "TextFormatText",
+]
class Agent(TypedDict, total=False):
@@ -25,12 +34,28 @@ class Reasoning(TypedDict, total=False):
"""
+class TextFormatText(TypedDict, total=False):
+ """Default response format. Used to generate text responses."""
+
+ type: Required[Literal["text"]]
+ """The type of response format being defined. Always `text`."""
+
+
+TextFormat: TypeAlias = Union[TextFormatText, BetaResponseFormatTextJSONSchemaConfigParam]
+
+
+class Text(TypedDict, total=False):
+ """The text output configuration for subsequent responses."""
+
+ format: Required[TextFormat]
+ """Use a JSON schema for structured output or text to return to plain text."""
+
+
class BetaResponseConfigurationUpdateItemParamParam(TypedDict, total=False):
"""An update to the conversation's response configuration.
- The configuration
- remains in effect for subsequent responses until it is replaced by another
- configuration update.
+ Supply reasoning,
+ text, or both. Each setting stays in effect until a later update replaces it.
"""
type: Required[Literal["configuration_update"]]
@@ -44,3 +69,6 @@ class BetaResponseConfigurationUpdateItemParamParam(TypedDict, total=False):
reasoning: Reasoning
"""Updates to reasoning configuration. Only effort is supported."""
+
+ text: Text
+ """The text output configuration for subsequent responses."""
diff --git a/src/openai/types/responses/response_configuration_update_item.py b/src/openai/types/responses/response_configuration_update_item.py
index 5d14e6091d..9224cb3000 100644
--- a/src/openai/types/responses/response_configuration_update_item.py
+++ b/src/openai/types/responses/response_configuration_update_item.py
@@ -1,12 +1,15 @@
# File generated from our OpenAPI spec by Castiron. See CONTRIBUTING.md for details.
-from typing import Optional
-from typing_extensions import Literal
+from typing import Union, Optional
+from typing_extensions import Literal, Annotated, TypeAlias
+from ..._utils import PropertyInfo
from ..._models import BaseModel
from ..shared.reasoning_effort import ReasoningEffort
+from ..shared.response_format_text import ResponseFormatText
+from .response_format_text_json_schema_config import ResponseFormatTextJSONSchemaConfig
-__all__ = ["ResponseConfigurationUpdateItem", "Reasoning"]
+__all__ = ["ResponseConfigurationUpdateItem", "Reasoning", "Text", "TextFormat"]
class Reasoning(BaseModel):
@@ -19,10 +22,23 @@ class Reasoning(BaseModel):
"""
+TextFormat: TypeAlias = Annotated[
+ Union[ResponseFormatText, ResponseFormatTextJSONSchemaConfig], PropertyInfo(discriminator="type")
+]
+
+
+class Text(BaseModel):
+ """The text output configuration for subsequent responses."""
+
+ format: TextFormat
+ """Use a JSON schema for structured output or text to return to plain text."""
+
+
class ResponseConfigurationUpdateItem(BaseModel):
- """
- A configuration update that applies to subsequent responses until it is
- replaced by another configuration update.
+ """A configuration update for subsequent responses.
+
+ Each setting stays in effect
+ until a later update replaces that setting.
"""
id: str
@@ -33,3 +49,6 @@ class ResponseConfigurationUpdateItem(BaseModel):
reasoning: Optional[Reasoning] = None
"""The reasoning configuration applied by this update."""
+
+ text: Optional[Text] = None
+ """The text output configuration for subsequent responses."""
diff --git a/src/openai/types/responses/response_configuration_update_item_param.py b/src/openai/types/responses/response_configuration_update_item_param.py
index 7a18c2a51d..157dcc31be 100644
--- a/src/openai/types/responses/response_configuration_update_item_param.py
+++ b/src/openai/types/responses/response_configuration_update_item_param.py
@@ -1,12 +1,15 @@
# File generated from our OpenAPI spec by Castiron. See CONTRIBUTING.md for details.
-from typing import Optional
-from typing_extensions import Literal
+from typing import Union, Optional
+from typing_extensions import Literal, Annotated, TypeAlias
+from ..._utils import PropertyInfo
from ..._models import BaseModel
from ..shared.reasoning_effort import ReasoningEffort
+from ..shared.response_format_text import ResponseFormatText
+from .response_format_text_json_schema_config import ResponseFormatTextJSONSchemaConfig
-__all__ = ["ResponseConfigurationUpdateItemParam", "Reasoning"]
+__all__ = ["ResponseConfigurationUpdateItemParam", "Reasoning", "Text", "TextFormat"]
class Reasoning(BaseModel):
@@ -19,12 +22,23 @@ class Reasoning(BaseModel):
"""
+TextFormat: TypeAlias = Annotated[
+ Union[ResponseFormatText, ResponseFormatTextJSONSchemaConfig], PropertyInfo(discriminator="type")
+]
+
+
+class Text(BaseModel):
+ """The text output configuration for subsequent responses."""
+
+ format: TextFormat
+ """Use a JSON schema for structured output or text to return to plain text."""
+
+
class ResponseConfigurationUpdateItemParam(BaseModel):
"""An update to the conversation's response configuration.
- The configuration
- remains in effect for subsequent responses until it is replaced by another
- configuration update.
+ Supply reasoning,
+ text, or both. Each setting stays in effect until a later update replaces it.
"""
type: Literal["configuration_update"]
@@ -35,3 +49,6 @@ class ResponseConfigurationUpdateItemParam(BaseModel):
reasoning: Optional[Reasoning] = None
"""Updates to reasoning configuration. Only effort is supported."""
+
+ text: Optional[Text] = None
+ """The text output configuration for subsequent responses."""
diff --git a/src/openai/types/responses/response_configuration_update_item_param_param.py b/src/openai/types/responses/response_configuration_update_item_param_param.py
index ed7e5949ec..f14f79b344 100644
--- a/src/openai/types/responses/response_configuration_update_item_param_param.py
+++ b/src/openai/types/responses/response_configuration_update_item_param_param.py
@@ -2,12 +2,14 @@
from __future__ import annotations
-from typing import Optional
-from typing_extensions import Literal, Required, TypedDict
+from typing import Union, Optional
+from typing_extensions import Literal, Required, TypeAlias, TypedDict
from ..shared.reasoning_effort import ReasoningEffort
+from ..shared_params.response_format_text import ResponseFormatText
+from .response_format_text_json_schema_config_param import ResponseFormatTextJSONSchemaConfigParam
-__all__ = ["ResponseConfigurationUpdateItemParamParam", "Reasoning"]
+__all__ = ["ResponseConfigurationUpdateItemParamParam", "Reasoning", "Text", "TextFormat"]
class Reasoning(TypedDict, total=False):
@@ -20,12 +22,21 @@ class Reasoning(TypedDict, total=False):
"""
+TextFormat: TypeAlias = Union[ResponseFormatText, ResponseFormatTextJSONSchemaConfigParam]
+
+
+class Text(TypedDict, total=False):
+ """The text output configuration for subsequent responses."""
+
+ format: Required[TextFormat]
+ """Use a JSON schema for structured output or text to return to plain text."""
+
+
class ResponseConfigurationUpdateItemParamParam(TypedDict, total=False):
"""An update to the conversation's response configuration.
- The configuration
- remains in effect for subsequent responses until it is replaced by another
- configuration update.
+ Supply reasoning,
+ text, or both. Each setting stays in effect until a later update replaces it.
"""
type: Required[Literal["configuration_update"]]
@@ -36,3 +47,6 @@ class ResponseConfigurationUpdateItemParamParam(TypedDict, total=False):
reasoning: Reasoning
"""Updates to reasoning configuration. Only effort is supported."""
+
+ text: Text
+ """The text output configuration for subsequent responses."""
diff --git a/tests/lib/responses/test_parse_as.py b/tests/lib/responses/test_parse_as.py
new file mode 100644
index 0000000000..dd3bba194e
--- /dev/null
+++ b/tests/lib/responses/test_parse_as.py
@@ -0,0 +1,127 @@
+from __future__ import annotations
+
+import json
+from typing_extensions import assert_type
+
+import pytest
+from pydantic import BaseModel
+
+from openai import OpenAI, AsyncOpenAI
+from tests.respx2 import MockRouter
+from openai.types.responses import ResponseConfigurationUpdateItem, ResponseConfigurationUpdateItemParamParam
+from openai.lib._parsing._responses import type_to_text_format_param
+
+from ...conftest import base_url
+
+
+class ShortAnswer(BaseModel):
+ answer: str
+
+
+class DetailedAnswer(ShortAnswer):
+ evidence: str
+
+
+def _detailed_update() -> ResponseConfigurationUpdateItemParamParam:
+ fmt = type_to_text_format_param(DetailedAnswer)
+ assert fmt["type"] == "json_schema"
+ return {"type": "configuration_update", "text": {"format": fmt}}
+
+
+@pytest.mark.respx2(base_url=base_url)
+@pytest.mark.parametrize("client,async_client", [(False, False)], indirect=True)
+@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"])
+async def test_parse_as_on_continuation(
+ client: OpenAI, async_client: AsyncOpenAI, respx2_mock: MockRouter, sync: bool
+) -> None:
+ text = '{"answer":"yes","evidence":"the receipt"}'
+ route = respx2_mock.post("/responses").respond(
+ json={"output": [{"type": "message", "content": [{"type": "output_text", "text": text}]}]}
+ )
+ if sync:
+ response = client.responses.parse(
+ previous_response_id="resp_previous", input="Explain.", text_format=ShortAnswer, parse_as=DetailedAnswer
+ )
+ else:
+ response = await async_client.responses.parse(
+ previous_response_id="resp_previous", input="Explain.", text_format=ShortAnswer, parse_as=DetailedAnswer
+ )
+
+ assert_type(response.output_parsed, DetailedAnswer | None)
+ assert response.output_parsed == DetailedAnswer(answer="yes", evidence="the receipt")
+ body = json.loads(route.calls.last.request.content)
+ assert body["text"]["format"] == type_to_text_format_param(ShortAnswer)
+ assert body["previous_response_id"] == "resp_previous"
+ assert "parse_as" not in body
+
+
+@pytest.mark.respx2(base_url=base_url)
+@pytest.mark.parametrize("client", [False], indirect=True)
+def test_parse_as_does_not_send_a_top_level_schema(client: OpenAI, respx2_mock: MockRouter) -> None:
+ update = _detailed_update()
+ text = '{"answer":"yes","evidence":"the receipt"}'
+ route = respx2_mock.post("/responses").respond(
+ json={"output": [{"type": "message", "content": [{"type": "output_text", "text": text}]}]}
+ )
+ response = client.responses.parse(input=[update], parse_as=DetailedAnswer)
+
+ assert response.output_parsed == DetailedAnswer(answer="yes", evidence="the receipt")
+ assert json.loads(route.calls.last.request.content) == {"input": [update]}
+
+
+@pytest.mark.respx2(base_url=base_url)
+@pytest.mark.parametrize("client", [False], indirect=True)
+def test_visible_format_updates(client: OpenAI, respx2_mock: MockRouter) -> None:
+ detailed = _detailed_update()
+ reasoning: ResponseConfigurationUpdateItemParamParam = {
+ "type": "configuration_update",
+ "reasoning": {"effort": "high"},
+ }
+ reset: ResponseConfigurationUpdateItemParamParam = {
+ "type": "configuration_update",
+ "text": {"format": {"type": "text"}},
+ }
+ route = respx2_mock.post("/responses").respond(
+ json={"output": [{"type": "message", "content": [{"type": "output_text", "text": "plain text"}]}]}
+ )
+ with pytest.raises(TypeError, match="Pass parse_as"):
+ client.responses.parse(text_format=ShortAnswer, input=[detailed, reasoning])
+ assert not route.called
+
+ response = client.responses.parse(text_format=ShortAnswer, input=[detailed, reset, reasoning])
+ assert response.output_text == "plain text"
+ assert response.output_parsed is None
+
+ with pytest.raises(TypeError, match="plain-text format update"):
+ client.responses.parse(text_format=ShortAnswer, parse_as=DetailedAnswer, input=[reset])
+ assert route.call_count == 1
+
+ response_text = client.responses.parse(previous_response_id="resp_reset", text_format=ShortAnswer, parse_as=None)
+ assert_type(response_text.output_parsed, None)
+ assert response_text.output_parsed is None
+
+
+@pytest.mark.respx2(base_url=base_url)
+def test_list_input_items_preserves_format_updates(client: OpenAI, respx2_mock: MockRouter) -> None:
+ items = [
+ {"id": "cnfu_schema", **_detailed_update()},
+ {
+ "id": "cnfu_reset",
+ "type": "configuration_update",
+ "reasoning": {"effort": "high"},
+ "text": {"format": {"type": "text"}},
+ },
+ {"id": "cnfu_reason", "type": "configuration_update", "reasoning": {"effort": "low"}},
+ ]
+ respx2_mock.get("/responses/resp_test/input_items").respond(
+ json={"object": "list", "data": items, "first_id": "cnfu_schema", "last_id": "cnfu_reason", "has_more": False}
+ )
+
+ detailed, reset, reasoning = client.responses.input_items.list("resp_test").data
+ assert isinstance(detailed, ResponseConfigurationUpdateItem) and detailed.text is not None
+ assert detailed.text.format.type == "json_schema"
+ assert detailed.text.format.name == "DetailedAnswer"
+ assert isinstance(reset, ResponseConfigurationUpdateItem) and reset.text is not None
+ assert reset.text.format.type == "text"
+ assert isinstance(reasoning, ResponseConfigurationUpdateItem) and reasoning.text is None
+ assert [item.to_dict() for item in (detailed, reset, reasoning)] == items
diff --git a/tests/lib/test_responses_tool_runner.py b/tests/lib/test_responses_tool_runner.py
index 336d2b66e4..d1593f26ef 100644
--- a/tests/lib/test_responses_tool_runner.py
+++ b/tests/lib/test_responses_tool_runner.py
@@ -2022,6 +2022,34 @@ async def test_replay_reasoning_requires_native_fields(lane: str, mode: str, fie
assert requests == []
+@pytest.mark.parametrize("lane", ["sync", "async"])
+@pytest.mark.parametrize("mode", ["ledger", "budget"])
+async def test_replay_preserves_configuration_updates(lane: str, mode: str) -> None:
+ history: ResponseInputParam = [
+ {
+ "type": "configuration_update",
+ "text": {"format": {"type": "json_schema", "name": "answer", "schema": {"type": "object"}}},
+ },
+ {"role": "user", "content": "Continue"},
+ {"type": "configuration_update", "text": {"format": {"type": "text"}}},
+ ]
+ requests: list[dict[str, Any]] = []
+ records: list[ResponsesToolCheckpoint] = []
+ await run(
+ lane,
+ None,
+ [resp([fn("record", "one", text="once")], "r"), FINAL],
+ requests,
+ params={**PARAMS, "input": history},
+ save=records.append if mode == "ledger" else None,
+ context_budget=ContextBudget(max_input_bytes=10000, max_input_images=0) if mode == "budget" else None,
+ )
+ assert requests[0]["input"] == history
+ assert requests[1]["input"][: len(history)] == history
+ if mode == "ledger":
+ assert records[0]["history"] == history
+
+
@pytest.mark.parametrize("lane", ["sync", "async"])
async def test_replay_reasoning_preserves_native_fields(lane: str) -> None:
reasoning = {