From 607d78caa56c24071c495dfa5804c365e0eca31f Mon Sep 17 00:00:00 2001 From: Zhiqi Zhang Date: Mon, 21 Sep 2026 06:01:15 +0000 Subject: [PATCH] fix: keep __api_exclude__ fields out of async request bodies _async_transform_recursive dumps pydantic models without the exclude= argument, so fields declared in __api_exclude__ (e.g. ParsedResponseFunctionToolCall.parsed_arguments) leak into request bodies sent through AsyncOpenAI while OpenAI strips them. Mirror the model_dump call from the sync path. --- src/openai/_utils/_transform.py | 2 +- tests/test_transform.py | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/openai/_utils/_transform.py b/src/openai/_utils/_transform.py index 304fd12ffe..9f1e66ebb5 100644 --- a/src/openai/_utils/_transform.py +++ b/src/openai/_utils/_transform.py @@ -387,7 +387,7 @@ async def _async_transform_recursive( return data if isinstance(data, pydantic.BaseModel): - return model_dump(data, exclude_unset=True, mode="json") + return model_dump(data, exclude_unset=True, mode="json", exclude=getattr(data, "__api_exclude__", None)) annotated_type = _get_annotated_type(annotation) if annotated_type is None: diff --git a/tests/test_transform.py b/tests/test_transform.py index 93f7ad8dd8..a6f2e6331b 100644 --- a/tests/test_transform.py +++ b/tests/test_transform.py @@ -357,6 +357,21 @@ async def test_pydantic_default_field(use_async: bool) -> None: assert cast(Any, await transform(model, Any, use_async)) == {"with_none_default": "bar", "with_str_default": "baz"} +class ModelWithApiExcludedField(BaseModel): + foo: str + client_only: Union[str, None] = None + + __api_exclude__ = {"client_only"} + + +@parametrize +@pytest.mark.asyncio +async def test_pydantic_model_api_exclude(use_async: bool) -> None: + model = ModelWithApiExcludedField(foo="hello!", client_only="secret") + assert cast(Any, await transform(model, Any, use_async)) == {"foo": "hello!"} + assert cast(Any, await transform([model], List[ModelWithApiExcludedField], use_async)) == [{"foo": "hello!"}] + + class TypedDictIterableUnion(TypedDict): foo: Annotated[Union[Bar8, Iterable[Baz8]], PropertyInfo(alias="FOO")]