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
24 changes: 22 additions & 2 deletions src/openai/lib/streaming/responses/_responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,14 @@ def close(self) -> None:
def get_final_response(self) -> ParsedResponse[TextFormatT]:
"""Waits until the stream has been read to completion and returns
the accumulated `ParsedResponse` object.

A terminal `response.completed`, `response.incomplete`, or
`response.failed` event is required.
"""
self.until_done()
response = self._state._completed_response
if not response:
raise RuntimeError("Didn't receive a `response.completed` event.")
raise RuntimeError("Didn't receive a terminal response event.")

return response

Expand Down Expand Up @@ -180,11 +183,14 @@ async def close(self) -> None:
async def get_final_response(self) -> ParsedResponse[TextFormatT]:
"""Waits until the stream has been read to completion and returns
the accumulated `ParsedResponse` object.

A terminal `response.completed`, `response.incomplete`, or
`response.failed` event is required.
"""
await self.until_done()
response = self._state._completed_response
if not response:
raise RuntimeError("Didn't receive a `response.completed` event.")
raise RuntimeError("Didn't receive a terminal response event.")

return response

Expand Down Expand Up @@ -362,6 +368,20 @@ def accumulate_event(self, event: RawResponseStreamEvent) -> ParsedResponseSnaps
response=event.response,
input_tools=self._input_tools,
)
elif event.type == "response.incomplete":
# Don't strictly parse structured output: truncation often yields invalid JSON.
self._completed_response = parse_response(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid parsing truncated structured output on terminal failures

parse_response() calls the strict parse_text() path for every output text, so this raises before the terminal response can be stored whenever an incomplete structured output contains partial JSON. On this exact head I reproduced it with text_format=Answer and a response.incomplete payload whose text is {"value": and incomplete_details.reason == "max_output_tokens"; handle_event() raises Pydantic ValidationError. The same happens for response.failed, while the base commit accepts both events without attempting the parse. Consequently get_final_response() still cannot return the terminal response in the structured-output case this change is meant to fix. All new tests use text_format=omit, so they do not cover this branch. Please preserve the terminal response without strict structured parsing (for example, leave parsed unset for non-completed responses) and add incomplete/failed tests with a supplied Pydantic text format.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks — confirmed and fixed on this PR.

For response.incomplete / response.failed we now store the terminal response via parse_response(..., text_format=omit, input_tools=omit) so truncated structured JSON (and truncated tool args) no longer raise before get_final_response() can return. response.completed still uses the strict text_format path.

Added regression coverage with text_format=_Answer and truncated payload text {"value": for both incomplete and failed, plus sync/async get_final_response() cases and a completed-path sanity check that still parses successfully.

text_format=omit,
response=event.response,
input_tools=omit,
)
elif event.type == "response.failed":
# Same as incomplete: preserve the terminal Response without strict parsing.
self._completed_response = parse_response(
text_format=omit,
response=event.response,
input_tools=omit,
)

return snapshot

Expand Down
Loading