From 448b79a12c47b1c3a7be6ac6e4a7cc468c69b3d2 Mon Sep 17 00:00:00 2001 From: Junhyuk Lee Date: Wed, 29 Apr 2026 15:22:53 +0000 Subject: [PATCH] Advance OSS contribution for usage.prompt_tokens_details=None Nightly Codex produced a focused contribution for https://github.com/openai/openai-python/issues/2544. Constraint: Automated nightly run; keep changes small and reviewable. Confidence: medium Scope-risk: narrow Tested: See uploaded nightly artifacts and workflow logs. Not-tested: Maintainer CI beyond this workflow. --- NIGHTLY_CODEX_FINAL_ATTEMPT_1.md | 15 +++++++++++++++ README.md | 1 + tests/test_models.py | 31 +++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+) create mode 100644 NIGHTLY_CODEX_FINAL_ATTEMPT_1.md diff --git a/NIGHTLY_CODEX_FINAL_ATTEMPT_1.md b/NIGHTLY_CODEX_FINAL_ATTEMPT_1.md new file mode 100644 index 0000000000..54485d46e0 --- /dev/null +++ b/NIGHTLY_CODEX_FINAL_ATTEMPT_1.md @@ -0,0 +1,15 @@ +Implemented a small related contribution for openai/openai-python issue #2544. + +Changed: +- [tests/test_models.py](/home/runner/work/oss-nightly-control/oss-nightly-control/target/tests/test_models.py): added a regression test that `CompletionUsage` preserves provider-specific extra usage fields like `promptTokensDetails`. +- [README.md](/home/runner/work/oss-nightly-control/oss-nightly-control/target/README.md): clarified nested undocumented response fields are available from the nested Pydantic model. +- [NIGHTLY_REPORT.md](/home/runner/work/oss-nightly-control/oss-nightly-control/target/NIGHTLY_REPORT.md): added the required nightly summary. + +Validation run: +- `uv run pytest tests/test_models.py::test_completion_usage_preserves_unknown_token_details -q -o addopts=''` +- `uv run pytest tests/test_models.py -q -o addopts=''` +- `uv run ruff format --check tests/test_models.py` +- `uv run ruff check tests/test_models.py` +- `uv run python scripts/utils/ruffen-docs.py README.md` + +Note: I avoided the direct `prompt_tokens_details=None` behavior change because GitHub shows an existing open PR for that exact approach: https://github.com/openai/openai-python/pull/2773. The issue context used was https://github.com/openai/openai-python/issues/2544. \ No newline at end of file diff --git a/README.md b/README.md index 9450c0bc51..9d2b84a715 100644 --- a/README.md +++ b/README.md @@ -848,6 +848,7 @@ options. To access undocumented response properties, you can access the extra fields like `response.unknown_prop`. You can also get all the extra fields on the Pydantic model as a dict with [`response.model_extra`](https://docs.pydantic.dev/latest/api/base_model/#pydantic.BaseModel.model_extra). +For nested response objects, access extra fields from that nested model, such as `response.usage.model_extra`. ### Configuring the HTTP client diff --git a/tests/test_models.py b/tests/test_models.py index 588869ee35..d24682ad96 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -7,6 +7,7 @@ import pydantic from pydantic import Field +from openai.types import CompletionUsage from openai._utils import PropertyInfo from openai._compat import PYDANTIC_V1, parse_obj, model_dump, model_json from openai._models import DISCRIMINATOR_CACHE, BaseModel, construct_type @@ -157,6 +158,36 @@ def test_unknown_fields() -> None: assert model_dump(m2) == {"foo": "foo", "unknown": {"foo_bar": True}} +def test_completion_usage_preserves_unknown_token_details() -> None: + usage = parse_obj( + CompletionUsage, + { + "completion_tokens": 57, + "prompt_tokens": 2181, + "total_tokens": 2518, + "prompt_tokens_details": None, + "reasoning_tokens": 280, + "traffic_type": "ON_DEMAND", + "promptTokensDetails": [{"modality": "TEXT", "tokenCount": 2181}], + "candidatesTokensDetails": [{"modality": "TEXT", "tokenCount": 57}], + }, + ) + + assert usage.prompt_tokens_details is None + assert cast(Any, usage).reasoning_tokens == 280 + assert cast(Any, usage).traffic_type == "ON_DEMAND" + assert cast(Any, usage).promptTokensDetails == [{"modality": "TEXT", "tokenCount": 2181}] + assert usage.to_dict(exclude_none=True) == { + "completion_tokens": 57, + "prompt_tokens": 2181, + "total_tokens": 2518, + "reasoning_tokens": 280, + "traffic_type": "ON_DEMAND", + "promptTokensDetails": [{"modality": "TEXT", "tokenCount": 2181}], + "candidatesTokensDetails": [{"modality": "TEXT", "tokenCount": 57}], + } + + def test_strict_validation_unknown_fields() -> None: class Model(BaseModel): foo: str