From b4ae04e5b1cab57e66bab02893b2aa6a80cfeefe Mon Sep 17 00:00:00 2001 From: Justin Beckwith Date: Fri, 16 Oct 2026 21:13:55 +0000 Subject: [PATCH] feat(api): share Decisions cache breakpoint parameter type Castiron-Internal-PR: https://github.com/openai/openai-python-internal/pull/262 Castiron-Source-SHA: 885a3077d71810f12cb6f77ec421b0c6d4535403 Castiron-Public-Base-SHA: 6ea95a6d1f250840eda4cb99737a90fef31c7a14 --- .castiron.stats.yml | 14 +-- api.md | 1 + api_reference/openapi.transformed.yml | 53 ++++----- scripts/castiron/generated-files.json | 12 ++- src/openai/types/__init__.py | 3 + .../types/decision_input_image_param.py | 4 +- src/openai/types/decision_input_text_param.py | 4 +- .../decision_prompt_cache_breakpoint_param.py | 17 +++ tests/test_decision_prompt_cache.py | 102 ++++++++++++++++++ 9 files changed, 165 insertions(+), 45 deletions(-) create mode 100644 src/openai/types/decision_prompt_cache_breakpoint_param.py create mode 100644 tests/test_decision_prompt_cache.py diff --git a/.castiron.stats.yml b/.castiron.stats.yml index 6c938570fe..de3f450dca 100644 --- a/.castiron.stats.yml +++ b/.castiron.stats.yml @@ -1,8 +1,8 @@ schema_version: 1 -generation_id: faf10787-3bdc-4568-b380-0861d1d6e36a -openapi_spec_hash: e767920c3fa8361ad29a90c8ea8397f2 -openapi_transformed_spec_hash: 0ed231f6f1bee5e6180d6d4e4691cf92 -config_hash: 83d6d59dd355a0913b94b00a8e37347f -codegen_sha: 1723773fd709b9e05fe6b2a0dba6bd89a0e7b644 -codegen_hash: 209068bfadf82bbebdd982054c230b05718b15083751c01b498c1eb01bd4de3b -public_codegen_sha: e91edab5b26cd158367b0faa4b3a13d323988180 +generation_id: 87b7588c-bf2a-41c6-9369-25b7fac9406c +openapi_spec_hash: 5dc81283fba93dcfeaeca3da5226d157 +openapi_transformed_spec_hash: 42319a087893dfea0fdd818494eb2a8d +config_hash: c1867d38e1fb20037598907749312f81 +codegen_sha: 0312112d44322208df3d1587c5b6eac1867be196 +codegen_hash: c6efe506a6d3c692825695e888a088db61e9f7c1115801a7138ba4b3ec8b1d00 +public_codegen_sha: 54359420e01e63cc0b61838459b07fcf8cc3a7bd diff --git a/api.md b/api.md index df44205d62..d5492a193a 100644 --- a/api.md +++ b/api.md @@ -34,6 +34,7 @@ from openai.types import ( DecisionInputMessageParam, DecisionInputPartUnionParam, DecisionInputTextParam, + DecisionPromptCacheBreakpointParam, ) ``` diff --git a/api_reference/openapi.transformed.yml b/api_reference/openapi.transformed.yml index df012a833d..bb4e956d40 100644 --- a/api_reference/openapi.transformed.yml +++ b/api_reference/openapi.transformed.yml @@ -81847,22 +81847,9 @@ components: minLength: 0 maxLength: 10485760 prompt_cache_breakpoint: - type: - - object - - 'null' - properties: - mode: - type: string - enum: - - explicit - default: explicit - x-stainless-const: true - description: The breakpoint mode. Always `explicit`. - required: - - mode - additionalProperties: false - title: Prompt cache breakpoint - description: Marks the exact end of a reusable prompt prefix. The breakpoint inherits its TTL from the request's `prompt_cache_options.ttl`; the boundary is not rounded to a token block. + anyOf: + - $ref: '#/components/schemas/DecisionPromptCacheBreakpoint' + - type: 'null' required: - type - text @@ -81890,22 +81877,9 @@ components: description: The image detail level, using the selected model's image profile. Defaults to auto. default: auto prompt_cache_breakpoint: - type: - - object - - 'null' - properties: - mode: - type: string - enum: - - explicit - default: explicit - x-stainless-const: true - description: The breakpoint mode. Always `explicit`. - required: - - mode - additionalProperties: false - title: Prompt cache breakpoint - description: Marks the exact end of a reusable prompt prefix. The breakpoint inherits its TTL from the request's `prompt_cache_options.ttl`; the boundary is not rounded to a token block. + anyOf: + - $ref: '#/components/schemas/DecisionPromptCacheBreakpoint' + - type: 'null' type: type: string enum: @@ -107370,6 +107344,21 @@ components: - type - name - prompt + DecisionPromptCacheBreakpoint: + type: object + properties: + mode: + type: string + enum: + - explicit + default: explicit + x-stainless-const: true + description: The breakpoint mode. Always `explicit`. + required: + - mode + additionalProperties: false + title: Prompt cache breakpoint + description: Marks the exact end of a reusable prompt prefix. The breakpoint inherits its TTL from the request's `prompt_cache_options.ttl`; the boundary is not rounded to a token block. OAuthErrorCode: anyOf: - enum: diff --git a/scripts/castiron/generated-files.json b/scripts/castiron/generated-files.json index a42ea5cb9b..61062e1ff7 100644 --- a/scripts/castiron/generated-files.json +++ b/scripts/castiron/generated-files.json @@ -11,7 +11,7 @@ }, "api.md": { "mode": "100644", - "sha256": "f0d379b2373527bc8541d45b4f7297a3cebcf996d73da40cbc2b891f6f9af5eb" + "sha256": "cb21d922f4599e1d2d05cd937ac10072d3b3dbcbac4e73c9903cea2125944cef" }, "scripts/castiron/CUSTOM_CODE.md": { "mode": "100644", @@ -755,7 +755,7 @@ }, "src/openai/types/__init__.py": { "mode": "100644", - "sha256": "d9737e5373f40a8eae97a51a3ad158bcbd2cda4bb4d3e6f7b53b4ea21065e43d" + "sha256": "5caaa29a79510a0a3e4a92f7b59d3ccb3f8f6651706f658738cb5e2a22b91481" }, "src/openai/types/admin/__init__.py": { "mode": "100644", @@ -4067,7 +4067,7 @@ }, "src/openai/types/decision_input_image_param.py": { "mode": "100644", - "sha256": "2a823bb0b630ac1c1d3572eda41023686b15cb515e990b43a5892119daf55440" + "sha256": "ea10f5cc6ca0126d19b4f5a0b2acd06386d09fe44515ceda473fe920bdc0211d" }, "src/openai/types/decision_input_message_param.py": { "mode": "100644", @@ -4079,7 +4079,11 @@ }, "src/openai/types/decision_input_text_param.py": { "mode": "100644", - "sha256": "4555a371d1a82cadc4076b9c08b2d45ec4c31162a112aee319e1912243d6acf2" + "sha256": "96810356758e0082d22d20bc76e9dab61360ca37be3f2f9ad15ae01c473b470f" + }, + "src/openai/types/decision_prompt_cache_breakpoint_param.py": { + "mode": "100644", + "sha256": "5317301f25465c321182e02dfe32b7ab5718e3ddf51d4ae5fccf01cbc7380a68" }, "src/openai/types/deleted_skill.py": { "mode": "100644", diff --git a/src/openai/types/__init__.py b/src/openai/types/__init__.py index 2bc26e4916..24bc681ca5 100644 --- a/src/openai/types/__init__.py +++ b/src/openai/types/__init__.py @@ -138,6 +138,9 @@ from .content_provenance_check_create_params import ( ContentProvenanceCheckCreateParams as ContentProvenanceCheckCreateParams, ) +from .decision_prompt_cache_breakpoint_param import ( + DecisionPromptCacheBreakpointParam as DecisionPromptCacheBreakpointParam, +) from .eval_stored_completions_data_source_config import ( EvalStoredCompletionsDataSourceConfig as EvalStoredCompletionsDataSourceConfig, ) diff --git a/src/openai/types/decision_input_image_param.py b/src/openai/types/decision_input_image_param.py index fd8d7f81b2..54fc06ea56 100644 --- a/src/openai/types/decision_input_image_param.py +++ b/src/openai/types/decision_input_image_param.py @@ -5,6 +5,8 @@ from typing import Optional from typing_extensions import Literal, Required, TypedDict +from .decision_prompt_cache_breakpoint_param import DecisionPromptCacheBreakpointParam + __all__ = ["DecisionInputImageParam", "PromptCacheBreakpoint"] @@ -37,7 +39,7 @@ class DecisionInputImageParam(TypedDict, total=False): Defaults to auto. """ - prompt_cache_breakpoint: Optional[PromptCacheBreakpoint] + prompt_cache_breakpoint: Optional[DecisionPromptCacheBreakpointParam] """Marks the exact end of a reusable prompt prefix. The breakpoint inherits its TTL from the request's `prompt_cache_options.ttl`; diff --git a/src/openai/types/decision_input_text_param.py b/src/openai/types/decision_input_text_param.py index 0eec5ca1c3..7398dfcfeb 100644 --- a/src/openai/types/decision_input_text_param.py +++ b/src/openai/types/decision_input_text_param.py @@ -5,6 +5,8 @@ from typing import Optional from typing_extensions import Literal, Required, TypedDict +from .decision_prompt_cache_breakpoint_param import DecisionPromptCacheBreakpointParam + __all__ = ["DecisionInputTextParam", "PromptCacheBreakpoint"] @@ -23,7 +25,7 @@ class DecisionInputTextParam(TypedDict, total=False): type: Required[Literal["input_text"]] - prompt_cache_breakpoint: Optional[PromptCacheBreakpoint] + prompt_cache_breakpoint: Optional[DecisionPromptCacheBreakpointParam] """Marks the exact end of a reusable prompt prefix. The breakpoint inherits its TTL from the request's `prompt_cache_options.ttl`; diff --git a/src/openai/types/decision_prompt_cache_breakpoint_param.py b/src/openai/types/decision_prompt_cache_breakpoint_param.py new file mode 100644 index 0000000000..4b41519c06 --- /dev/null +++ b/src/openai/types/decision_prompt_cache_breakpoint_param.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Castiron. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["DecisionPromptCacheBreakpointParam"] + + +class DecisionPromptCacheBreakpointParam(TypedDict, total=False): + """Marks the exact end of a reusable prompt prefix. + + The breakpoint inherits its TTL from the request's `prompt_cache_options.ttl`; the boundary is not rounded to a token block. + """ + + mode: Required[Literal["explicit"]] + """The breakpoint mode. Always `explicit`.""" diff --git a/tests/test_decision_prompt_cache.py b/tests/test_decision_prompt_cache.py new file mode 100644 index 0000000000..cdb1b1256e --- /dev/null +++ b/tests/test_decision_prompt_cache.py @@ -0,0 +1,102 @@ +from __future__ import annotations + +import json +from typing import TYPE_CHECKING, Any, Optional, get_type_hints +from typing_extensions import Literal, Required + +import httpx2 +import pytest + +from openai import OpenAI, AsyncOpenAI +from openai.types.decision_input_text_param import PromptCacheBreakpoint as TextBreakpoint, DecisionInputTextParam +from openai.types.decision_input_image_param import PromptCacheBreakpoint as ImageBreakpoint, DecisionInputImageParam +from openai.types.decision_prompt_cache_breakpoint_param import DecisionPromptCacheBreakpointParam + +if TYPE_CHECKING: + old_text: TextBreakpoint = {"mode": "explicit"} + old_image: ImageBreakpoint = {"mode": "explicit"} + shared_from_text: DecisionPromptCacheBreakpointParam = old_text + shared_from_image: DecisionPromptCacheBreakpointParam = old_image + text: DecisionInputTextParam = { + "type": "input_text", + "text": "synthetic", + "prompt_cache_breakpoint": shared_from_image, + } + image: DecisionInputImageParam = { + "type": "input_image", + "image_url": "https://example.test/image.png", + "prompt_cache_breakpoint": shared_from_text, + } + + +def test_legacy_breakpoint_exports() -> None: + for cls in (TextBreakpoint, ImageBreakpoint, DecisionPromptCacheBreakpointParam): + assert cls(mode="explicit") == {"mode": "explicit"} + assert get_type_hints(cls, include_extras=True)["mode"] == Required[Literal["explicit"]] + assert TextBreakpoint.__module__ == "openai.types.decision_input_text_param" + assert ImageBreakpoint.__module__ == "openai.types.decision_input_image_param" + + +def test_shared_breakpoint_annotations() -> None: + for cls in (DecisionInputTextParam, DecisionInputImageParam): + assert get_type_hints(cls)["prompt_cache_breakpoint"] == Optional[DecisionPromptCacheBreakpointParam] + + +def request_body(mode: str) -> dict[str, Any]: + content: list[dict[str, Any]] = [ + {"type": "input_text", "text": "synthetic"}, + {"type": "input_image", "image_url": "https://example.test/image.png"}, + ] + body: dict[str, Any] = { + "model": "synthetic-model", + "input": [{"role": "user", "content": content}], + "questions": [{"instructions": "Synthetic question", "type": "predicate"}], + } + if mode != "omitted": + body["prompt_cache_key"] = None if mode == "nulls" else "synthetic-key" + body["prompt_cache_options"] = None if mode == "nulls" else {"mode": mode, "ttl": "30m"} + for part in content: + part["prompt_cache_breakpoint"] = None if mode == "nulls" else {"mode": "explicit"} + return body + + +def capture_request(request: httpx2.Request, captured: list[dict[str, Any]]) -> httpx2.Response: + assert request.method == "POST" + assert str(request.url) == "https://example.test/decisions" + captured.append(json.loads(request.content)) + return httpx2.Response( + 200, json={"id": "decision_synthetic", "answers": [], "model": "synthetic-model", "usage": {}} + ) + + +@pytest.mark.parametrize("mode", ["explicit", "implicit", "nulls", "omitted"]) +def test_sync_request(mode: str) -> None: + captured: list[dict[str, Any]] = [] + body = request_body(mode) + with OpenAI( + api_key="fake-test-key", + base_url="https://example.test", + max_retries=0, + http_client=httpx2.Client( + transport=httpx2.MockTransport(lambda request: capture_request(request, captured)), trust_env=False + ), + ) as client: + client.decisions.create(**body) + assert captured == [body] + + +@pytest.mark.asyncio +@pytest.mark.parametrize("mode", ["explicit", "implicit", "nulls", "omitted"]) +async def test_async_request(mode: str) -> None: + captured: list[dict[str, Any]] = [] + body = request_body(mode) + async with AsyncOpenAI( + api_key="fake-test-key", + base_url="https://example.test", + max_retries=0, + http_client=httpx2.AsyncClient( + transport=httpx2.MockTransport(lambda request: capture_request(request, captured)), trust_env=False + ), + ) as client: + await client.decisions.create(**body) + assert captured == [body]