diff --git a/src/openai/lib/_parsing/_responses.py b/src/openai/lib/_parsing/_responses.py index 8853a0749f..45130bba8d 100644 --- a/src/openai/lib/_parsing/_responses.py +++ b/src/openai/lib/_parsing/_responses.py @@ -1,7 +1,7 @@ from __future__ import annotations import json -from typing import TYPE_CHECKING, List, Iterable, cast +from typing import TYPE_CHECKING, Any, List, Iterable, cast from typing_extensions import TypeVar, assert_never import pydantic @@ -68,7 +68,7 @@ def parse_response( content_list.append( construct_type_unchecked( - type_=ParsedResponseOutputText[TextFormatT], + type_=cast(Any, ParsedResponseOutputText), value={ **item.to_dict(), "parsed": parse_text(item.text, text_format=text_format), @@ -78,7 +78,7 @@ def parse_response( output_list.append( construct_type_unchecked( - type_=ParsedResponseOutputMessage[TextFormatT], + type_=cast(Any, ParsedResponseOutputMessage), value={ **output.to_dict(), "content": content_list, @@ -130,7 +130,7 @@ def parse_response( output_list.append(output) return construct_type_unchecked( - type_=ParsedResponse[TextFormatT], + type_=cast(Any, ParsedResponse), value={ **response.to_dict(), "output": output_list, diff --git a/tests/lib/responses/test_parsing.py b/tests/lib/responses/test_parsing.py new file mode 100644 index 0000000000..535eeff9b8 --- /dev/null +++ b/tests/lib/responses/test_parsing.py @@ -0,0 +1,65 @@ +from __future__ import annotations + +from typing import Any + +import pydantic + +import openai._models as models +from openai.lib._parsing._responses import parse_response +from openai.types.responses import Response + + +_MOCK_RESPONSE_JSON: dict[str, Any] = { + "id": "resp_poc", + "object": "response", + "created_at": 1234567890.0, + "model": "gpt-5.4", + "parallel_tool_calls": True, + "status": "completed", + "tool_choice": "auto", + "tools": [], + "output": [ + { + "type": "message", + "id": "msg_poc", + "status": "completed", + "role": "assistant", + "content": [ + { + "type": "output_text", + "text": '{"message": "Hello! How can I help?"}', + "annotations": [], + } + ], + } + ], + "text": {"format": {"type": "text"}}, + "usage": { + "input_tokens": 10, + "output_tokens": 20, + "total_tokens": 30, + "input_tokens_details": {"cached_tokens": 0}, + "output_tokens_details": {"reasoning_tokens": 0}, + }, +} + + +class ChatbotResponse(pydantic.BaseModel): + message: str + intent: str | None = None + + +def test_parse_response_does_not_use_non_model_validation(monkeypatch: Any) -> None: + def fail_if_called(*, type_: Any, value: object) -> Any: + raise AssertionError(f"unexpected non-model validation for {type_}") + + monkeypatch.setattr(models, "_validate_non_model_type", fail_if_called) + + raw_response = Response.model_validate(_MOCK_RESPONSE_JSON) + parsed = parse_response(response=raw_response, text_format=ChatbotResponse, input_tools=None) + + assert parsed.output[0].type == "message" + output_text = parsed.output[0].content[0] + assert output_text.type == "output_text" + assert output_text.parsed is not None + assert output_text.parsed.message == "Hello! How can I help?"