Skip to content

build: upgrade openai to 2.48.0 - #70

Merged
dariero merged 2 commits into
mainfrom
codex/bump-openai
Jul 27, 2026
Merged

build: upgrade openai to 2.48.0#70
dariero merged 2 commits into
mainfrom
codex/bump-openai

Conversation

@dariero

@dariero dariero commented Jul 26, 2026

Copy link
Copy Markdown
Owner

Upgrades openai from 2.45.0 to 2.48.0. Lock-only change; pyproject.toml is untouched because openai>=2.45.0,<3 already admits 2.48.0.

This is the largest runtime delta in the audit and the one the free suite cannot detect anything about. That is stated up front rather than buried.

Version delta

Package Before After Delta Constraint
openai 2.45.0 2.48.0 minor >=2.45.0,<3 (pyproject.toml:11), unchanged

Intervening stable releases: 2.46.0, 2.47.0, 2.48.0. No patch releases exist between them. All three release notes and CHANGELOG.md at v2.48.0 were read; none carries a "BREAKING CHANGES" heading or a migration link.

The >=2.45.0 floor is deliberately left alone. A floor is not a pin, and raising it would be an unrequested constraint change.

Lock regenerated with the first form of .agents/skills/upgrade-dependencies/SKILL.md step 5, targeting only this package:

$ uvx --from uv==0.11.30 uv lock --upgrade-package openai --python 3.14 --prerelease disallow
Resolved 56 packages in 200ms
Updated openai v2.45.0 -> v2.48.0

No transitive churn. openai's core dependency list is byte-identical across the range (anyio, distro, httpx, jiter, pydantic, sniffio, tqdm, typing-extensions), and its requires-python is still >= 3.9:

added:   []
removed: []
moved:   {'openai': ('2.45.0', '2.48.0')}
total packages: 56 -> 56

The exact SDK surface this repository binds

Anchored so the filtering below can be checked rather than trusted:

Anchor Surface
pipeline.py:28 from openai import OpenAI
pipeline.py:716-718 OpenAI(max_retries=0, timeout=120.0)
pipeline.py:130-133 client.embeddings.create(model=, input=, encoding_format="float")
pipeline.py:135-140 response.data, item.index, item.embedding
pipeline.py:184-187 client.chat.completions.create(model=, temperature=, max_completion_tokens=300, messages=)
pipeline.py:158-168 response.choices, choice.finish_reason, choice.message.content, getattr(choice.message, "refusal", None)

That is the whole surface. Nine call sites, all in pipeline.py.

Breaking changes, and which touch this repository

None of them. And the basis for that is file identity, not the absence of a changelog mention. The five SDK files that define this repository's entire read surface are blob-SHA identical between the two tags:

$ for f in <the five files>; do compare blob SHAs at v2.45.0 and v2.48.0; done
src/openai/resources/embeddings.py                      v2.45.0=a51936d809fc v2.48.0=a51936d809fc  IDENTICAL
src/openai/types/embedding.py                           v2.45.0=fbffec01e00d v2.48.0=fbffec01e00d  IDENTICAL
src/openai/types/create_embedding_response.py           v2.45.0=314a7f9afce1 v2.48.0=314a7f9afce1  IDENTICAL
src/openai/types/chat/chat_completion.py                v2.45.0=2c4a78cd3521 v2.48.0=2c4a78cd3521  IDENTICAL
src/openai/types/chat/chat_completion_message.py        v2.45.0=3f88f776b948 v2.48.0=3f88f776b948  IDENTICAL

None of the five appears in the v2.45.0...v2.48.0 changed-file list at all.

The announced changes, classified:

Release Change Touches this repository
2.48.0 prompt_cache_key and safety_identifier widen from str to Optional[str] on all four chat.completions.create overloads No. The only commit in the range reaching Chat Completions, and this repository passes neither keyword. A widening cannot break a caller regardless.
2.48.0 New admin spend_limit resources No. Purely additive files under resources/admin/organization/; the admin surface is never imported.
2.47.0 Experimental HTTPX2 runtime support; openai.__all__ gains DefaultHttpx2Client, DefaultAsyncHttpx2Client No. __init__.py changes are additive only; OpenAI is still re-exported from ._client. _httpx2.py never imports httpx2 at module scope - the real import sits inside _require_httpx2(), and detection uses sys.modules.get. With httpx2 absent, normalize_httpx_timeout is the identity on a plain float and timeout_exceptions() / status_exceptions() / stream_consumed_exceptions() return single-element tuples of the same httpx classes as before.
2.47.0 aiohttp extra now requires aiohttp>=3.14.1 under python_version >= '3.10'; _DefaultAioHttpClient gated behind sys.version_info >= (3, 10) No. No openai extras are installed, and the sub-3.10 branch is unreachable on 3.14.
2.47.0 _response.py / _legacy_response.py isinstance broadening on the response-construction path No. Semantically identical with a single-element tuple. The reworded ValueError fires only when a caller passes an httpx.Response subclass as cast_to, which never happens here.
2.46.0 Admin API restructuring: project service-accounts became a package with an api_keys subresource, moving type exports; audit_logs params/response widened; eleven usage_*_response types widened No. Every path is under resources/admin/organization/** or types/admin/organization/**. This repository touches two endpoints and never imports client.admin.
2.46.0 Beta compatibility aliases removed from three types/beta/beta_response_input_* modules, then re-added in the same release No. The Responses beta API is never imported. Net effect across the range in those files is nil.
2.46.0 Webhooks surface churn; a webhook event type added then removed No. No webhook receiver; openai.types.webhooks and client.webhooks.unwrap are never imported.
2.47.0 stlc configurable CI runner and private-production-repo support No. Modifies the SDK's own .github/workflows and scripts/; nothing ships in the wheel.

Two precision notes, because "byte-identical" was worth checking rather than repeating:

  1. src/openai/_client.py is not byte-identical (66d03b23dd4e vs aeea9907a873). What is byte-identical is the two parameter declarations this repository binds: timeout: float | Timeout | None | NotGiven = not_given and max_retries: int = DEFAULT_MAX_RETRIES. The one differing statement in the constructor region is the WorkloadIdentityAuth construction moving after super().__init__(), and it sits behind elif workload_identity is not None - a branch this repository never enters, since it passes only max_retries and timeout.
  2. Unannounced changes found only by diffing, each verified unreachable: base_url now passes through normalize_httpx_url (this repository never passes base_url); the http_client TypeError text now reads "httpx.Client or httpx2.Client"; the cast_to ValueError text changed; a verbosity docstring gained "The default is medium".

The environment condition that keeps every 2.47.0 path inert, verified in the installed venv rather than assumed:

$ .venv/bin/python -c 'import importlib.util as u; print("httpx2 spec:", u.find_spec("httpx2"))'
httpx2 spec: None
$ .venv/bin/python -c 'import importlib.util as u; print("aiohttp spec:", u.find_spec("aiohttp"))'
aiohttp spec: None

Prediction recorded before running anything

A clean, behaviourally neutral upgrade, with the confidence resting on file identity rather than on the changelog being quiet. Specifically:

  1. Both locks change by exactly one package with no transitive movement.
  2. The free suite passes unchanged, because no test executes SDK code. test_real_client_uses_exact_bounded_policy monkeypatches pipeline_module.OpenAI with a kwargs-capturing fake, and the adapter tests inject SimpleNamespace endpoints. The real OpenAI class is never instantiated under the default selection.
  3. Coverage stays at 96.05% - no SDK code is measured and no repository branch is added or removed.
  4. All twelve hooks pass. .venv is in Ruff's default exclusion set and both hooks additionally pass --force-exclude, so the new src/openai/_httpx2.py is never linted. The only SDK-typed expressions in the mypy scope are the -> OpenAI return annotation at pipeline.py:714 and the constructor call at 716-718, whose signature is unchanged.
  5. The paid paths, which are not run, would also be unaffected.
  6. The residual risk is resolver risk, not API risk.

What actually happened

Exactly the prediction.

Gate Before (main, openai 2.45.0) After (openai 2.48.0)
uv lock --check Resolved 56 packages Resolved 56 packages
Frozen export vs pylock.toml identical identical
uv pip check 54 packages compatible 54 packages compatible
pytest eval/ -q 335 passed, 13 deselected 335 passed, 13 deselected
Coverage gate 96.05% 96.05%
pre-commit validate-config exit 0 exit 0
pre-commit run --all-files 12 hooks passed 12 hooks passed
ruff format --check . 10 files already formatted 10 files already formatted
ruff check . All checks passed All checks passed
mypy strict scope Success, 5 source files Success, 5 source files
agent-policy-symbols exit 0 exit 0
Default selection 13 deselected 13 deselected
benchmark_retrieval.py (imports pipeline, hence openai) 853 MiB peak 854 MiB peak

Free validation command and result:

$ .venv/bin/python -m pytest -m "not openai and not rag_test" --cov --cov-report=term-missing eval/ -q
TOTAL                       642     18    218     16  96.05%
Required test coverage of 95.0% reached. Total coverage: 96.05%
335 passed, 13 deselected in 1.08s

Substitute evidence, since no test can supply it

Because the suite cannot check the SDK surface, it was checked by static introspection of the installed 2.48.0. No client was constructed, no network operation occurred, and no provider was contacted:

openai version: 2.48.0
"OpenAI" in openai.__all__: True
OpenAI.__init__ max_retries: present=True annotation=int default=2
OpenAI.__init__ timeout: present=True annotation=float | Timeout | None | NotGiven default=NOT_GIVEN
embeddings.create params: ['self', 'input', 'model', 'dimensions', 'encoding_format', 'user',
                           'extra_headers', 'extra_query', 'extra_body', 'timeout']
  encoding_format annotation: Literal['float', 'base64'] | Omit
chat.completions.create model: present=True
chat.completions.create temperature: present=True
chat.completions.create max_completion_tokens: present=True
chat.completions.create messages: present=True
CreateEmbeddingResponse fields: ['data', 'model', 'object', 'usage']
Embedding fields: ['embedding', 'index', 'object']
ChatCompletion has 'choices': True
Choice fields: ['finish_reason', 'index', 'logprobs', 'message']
ChatCompletionMessage has content/refusal: True True

Every symbol at every anchored call site is present with a compatible type. max_retries: int accepts 0; timeout: float | ... accepts 120.0; encoding_format: Literal['float','base64'] | Omit accepts "float". This is not equivalent to executing the calls, and it is not claimed to be - it is a static check standing in for a runtime check that policy forbids.

The mutation question

Which behaviours of this dependency does the suite exercise? Precisely one: that openai imports and that the name OpenAI exists at module level. That is pipeline.py:28, executed at collection time. Nothing else.

Every other SDK behaviour is stood in for by fakes. Anchored:

Fake Anchor What it shadows What it validates
_FakeEmbeddingsEndpoint.create(**kwargs) eval/test_verdigrise.py:2745-2759 client.embeddings.create Nothing about the SDK. It accepts any kwargs and returns a SimpleNamespace.
_FakeCompletionsEndpoint.create(**kwargs) eval/test_verdigrise.py:2868-2894 client.chat.completions.create Same.
build_client(**kwargs) replacing pipeline_module.OpenAI eval/test_verdigrise.py:2690-2707 the OpenAI constructor That VerdigrisE passes {"max_retries": 0, "timeout": 120.0}. Not that the SDK accepts them.

Compounding this, OpenAIEmbeddingProvider.__init__, OpenAIAnswerGenerator.__init__ and parse_generation_response all annotate their SDK argument as Any. So mypy --strict also cannot check this surface. Neither the dynamic gate nor the static gate sees it.

For each breaking change that touches a real call site, which test would have failed?

The honest answer is that the table is empty on the left, and that is itself the result:

Hypothetical change Test that would have failed
encoding_format renamed or its Literal narrowed None. _FakeEmbeddingsEndpoint.create(**kwargs) accepts anything.
response.data[].embedding renamed None. The fake returns SimpleNamespace(index=..., embedding=...) regardless of the real model.
choice.message.refusal removed None. getattr(..., "refusal", None) is already tolerant, and the fake supplies the attribute.
max_retries or timeout renamed or retyped None. test_real_client_uses_exact_bounded_policy replaces the constructor entirely; it would still pass while the real SDK rejected the call.
chat.completions.create dropping max_completion_tokens None. _FakeCompletionsEndpoint.create(**kwargs) accepts anything.
from openai import OpenAI failing The entire suite would fail at collection. This is the one thing covered.

The finding, stated plainly and as the headline of this pull request. The openai SDK is a runtime dependency whose real behaviour is verified only by the openai-marked paid tier, which the audit brief forbids running and which CI is designed never to select. Between the provider fakes and the Any annotations, a green free suite on this branch carries almost no information about whether 2.48.0 is compatible - it establishes that the package imports, and nothing more. The confidence in this upgrade comes from blob-level file identity across the three releases and from static introspection of the installed wheel, both documented above. It does not come from the suite.

This is a structural property of the design, not a defect introduced here: AGENTS.md and README:258 both state that the free tier uses provider fakes, and that separation is intentional. But it means every openai bump lands on evidence gathered outside the test suite, and that cost should be explicit each time rather than assumed away by a green checkmark. Per the audit brief, no test was added to close this gap in this pass.

Lock byte-comparison

$ uvx --from uv==0.11.30 uv lock --check --python 3.14 --prerelease disallow
Resolved 56 packages in 4ms
$ uvx --from uv==0.11.30 uv export --frozen --format pylock.toml --all-groups \
    --no-emit-project --python 3.14 --prerelease disallow --no-header --quiet \
    -o "$generated_dir/pylock.generated.toml"
$ cmp pylock.toml "$generated_dir/pylock.generated.toml"
cmp: IDENTICAL (exit 0)
$ shasum -a 256 pylock.toml "$generated_dir/pylock.generated.toml"
4a00eb9287cac74d41c197e7b4ef60f67d6a660c7b384a8555b7120354f37c6d  pylock.toml
4a00eb9287cac74d41c197e7b4ef60f67d6a660c7b384a8555b7120354f37c6d  .../pylock.generated.toml

pylock.toml invariants re-checked after the export:

requires-python = "==3.14.*"
sha256 count: 343
local paths / editable / file:// / git+ : 0
non-PyPI index entries: index = "https://pypi.org/simple"

Clean-clone transcript

Isolated temp directory, both provider key variables unset and confirmed absent by presence check only, no sibling ../RagaliQ reachable, README install commands verbatim with bare uv as documented.

=== provider key presence check (names only, never values) ===
OPENAI_API_KEY: absent
ANTHROPIC_API_KEY: absent
=== clone root: /private/tmp/verdigrise-cleanclone.OqiQNE ===
=== sibling RagaliQ reachable from clone parent? ===
not reachable
cloned HEAD: 18b07690010fed3976976bb328f2b8a740383f25  branch: codex/bump-openai
worktree clean: yes
sibling RagaliQ reachable from repo root? not reachable
uv 0.11.32 (Homebrew 2026-07-23 aarch64-apple-darwin)

$ uv venv --python 3.14
Using CPython 3.14.6 interpreter at: /opt/homebrew/opt/python@3.14/bin/python3.14
$ uv pip sync --preview-features pylock --require-hashes pylock.toml
Installed 54 packages in 154ms
$ uv pip check
Checked 54 packages in 1ms
All installed packages are compatible

$ .venv/bin/python -m pytest eval/ -q
335 passed, 13 deselected in 2.00s

=== clone-to-green wall time: 5s (uv cache WARM: 17G) ===

$ .venv/bin/python -m pytest --cov --cov-report=term-missing eval/ -q
TOTAL                       642     18    218     16  96.05%
Required test coverage of 95.0% reached. Total coverage: 96.05%
335 passed, 13 deselected in 1.49s

$ .venv/bin/ruff format --check .   -> 10 files already formatted (exit 0)
$ .venv/bin/ruff check .            -> All checks passed! (exit 0)
$ .venv/bin/python -m mypy config.py corpus.py models.py pipeline.py eval/ragaliq_adapter.py
Success: no issues found in 5 source files (exit 0)
$ .venv/bin/pre-commit validate-config   -> exit 0
$ .venv/bin/pre-commit run --all-files   -> 12 hooks Passed (exit 0)
$ .venv/bin/python -m pytest eval/ --collect-only -q | tail -1
335/348 tests collected (13 deselected) in 0.70s
$ .venv/bin/python -m eval.check_agent_policy_symbols   -> exit 0

ragaliq 0.2.0 from .../.venv/lib/python3.14/site-packages/ragaliq/__init__.py

The clone-to-green figure is a warm-cache number: the uv cache was already populated (17G), so 5s measures command execution, not first contact. A cold-cache clone would additionally download 54 wheels including the new openai 2.48.0 artifact.

Hidden costs

  • Compatibility with this bump is unproven by execution. It is supported by file identity and static introspection only. Proving it would require the paid openai tier, which needs separate per-invocation approval and real spend.
  • The Any annotation on the SDK client in both adapters is what makes the static gate blind here as well as the dynamic one. Narrowing it to a Protocol would let mypy --strict catch signature drift at zero marginal cost. That is a design change, not an upgrade, and is deliberately not attempted in this pull request.
  • 2.47.0 introduced an httpx2 extra. This bump deliberately does not install it. Leaving httpx2 absent is exactly what keeps every new 2.47.0 code path inert, so adding the extra later is a behaviour change requiring its own evidence, not a free performance option.
  • No provider calls were made at any point in producing this change.

@dariero

dariero commented Jul 26, 2026

Copy link
Copy Markdown
Owner Author

@codex review for deterministic/RagaliQ ownership, dependency reproducibility, Python 3.14 compatibility, public-clone portability, paid-call safety, golden-fixture integrity, marker correctness, public API compatibility, and unintended behaviour changes

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Another round soon, please!

Reviewed commit: 18b0769001

鈩癸笍 About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 馃憤.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@dariero
dariero merged commit 2e36118 into main Jul 27, 2026
4 checks passed
@dariero
dariero deleted the codex/bump-openai branch July 27, 2026 02:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant