Skip to content
Draft
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
10 changes: 5 additions & 5 deletions .castiron.stats.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
schema_version: 1
generation_id: edefdf16-46c7-4f93-94a6-10028a087b3e
openapi_spec_hash: 0c25fc15ac5b26e2054196d579cc67a0
openapi_transformed_spec_hash: 651cea16581547a33af56bd631894a03
generation_id: 5dfacb1b-1abf-4d69-9083-e6586471a6f6
openapi_spec_hash: 53e44788ffb3382947f27b9cf1d14cfe
openapi_transformed_spec_hash: 1385724889e077e80e65b0e8da123eca
config_hash: 8a684aec06907ff8ced7809753e16225
codegen_sha: a1dd47dd01ce07be1ecbc39e2f66f2156d89c6f3
codegen_hash: 2c7827fd0ccd38edcd80a97839ae5c10cf89f8da20ea468d4b20c41468607b81
codegen_sha: 11cd26ec903a4168e95565cd46fb7f33f657afca
codegen_hash: 5a9715f0fcf2bbdc00a4d9ad8b7c749a51e4d0531ee776d1ae7707881e0adf2a
78 changes: 64 additions & 14 deletions api_reference/openapi.transformed.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64749,8 +64749,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
Expand All @@ -64772,15 +64772,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:
Expand All @@ -64804,6 +64816,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:
Expand Down Expand Up @@ -70390,13 +70415,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
Expand Down Expand Up @@ -98392,12 +98417,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
Expand Down Expand Up @@ -101460,9 +101485,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:
Expand Down Expand Up @@ -101491,6 +101515,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:
Expand Down Expand Up @@ -104217,8 +104254,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'
Expand All @@ -104243,6 +104280,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
Expand Down
35 changes: 35 additions & 0 deletions helpers.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
16 changes: 8 additions & 8 deletions scripts/castiron/generated-files.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -631,7 +631,7 @@
},
"src/openai/resources/responses/responses.py": {
"mode": "100644",
"sha256": "b9a2ef1b61d33450318babc49e2b25f604628983804e7ebf4291f8802f372aee"
"sha256": "e74696203a015c8a87a6e1117d5e73449a1dd4b591c57d4c68d1cfda2641934a"
},
"src/openai/resources/safety/__init__.py": {
"mode": "100644",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
50 changes: 47 additions & 3 deletions src/openai/lib/_response_resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
)

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
)

Expand Down
7 changes: 6 additions & 1 deletion src/openai/lib/responses_tool_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand All @@ -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:
Expand Down
Loading
Loading