Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions src/openai/lib/_parsing/_responses.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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),
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
65 changes: 65 additions & 0 deletions tests/lib/responses/test_parsing.py
Original file line number Diff line number Diff line change
@@ -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?"