Skip to content
Open
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
12 changes: 11 additions & 1 deletion src/openai/_legacy_response.py
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,17 @@ def __init__(self, response: httpx.Response) -> None:

@property
def content(self) -> bytes:
return self.response.content
"""Return the response content, streamed in chunks to avoid ConnectionResetError on large files.

Streaming in 1 MB chunks prevents issues with large Batch API result files (>200 MB)
where reading the entire body at once can trigger a server-side connection reset
on long-lived HTTP connections. Fixes #2959.
"""
_CHUNK_SIZE = 1024 * 1024 # 1 MB
buf = bytearray()
for chunk in self.response.iter_bytes(chunk_size=_CHUNK_SIZE):
buf.extend(chunk)
Comment on lines +403 to +406

Choose a reason for hiding this comment

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

P1 Badge Chunk the body before APIResponse.parse() reads it

For the normal client.files.content(...) flow, HttpxBinaryResponseContent is only constructed after APIResponse.parse() has already called self.read() on the httpx.Response (src/openai/_response.py:323-339). Any large-file download failure therefore happens before this property is ever reached, so moving the loop here does not change the code path that actually reads from the socket and will not fix the reported ConnectionResetError for the non-streaming binary-download API.

Useful? React with 👍 / 👎.

return bytes(buf)
Comment on lines +404 to +407

Choose a reason for hiding this comment

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

P2 Badge Avoid copying the full binary payload a second time

In the same non-streaming path, the response body has already been buffered by the time this wrapper is created (src/openai/_response.py:323-339), so iter_bytes() here just replays response._content. Rebuilding that into a bytearray and then bytes makes an extra full-size copy of every file; for the 200MB+ downloads mentioned in the commit message, .content now adds hundreds of MB of peak memory and extra CPU where return self.response.content previously reused the existing buffer.

Useful? React with 👍 / 👎.


@property
def text(self) -> str:
Expand Down