Skip to content

Commit d5abe79

Browse files
committed
chore(internal): speculative retry-after-ms support (#1086)
Fixes #957.
1 parent d9e9d7a commit d5abe79

File tree

2 files changed

+42
-27
lines changed

2 files changed

+42
-27
lines changed

Diff for: src/openai/_base_client.py

+39-27
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,9 @@
7373
from ._constants import (
7474
DEFAULT_LIMITS,
7575
DEFAULT_TIMEOUT,
76+
MAX_RETRY_DELAY,
7677
DEFAULT_MAX_RETRIES,
78+
INITIAL_RETRY_DELAY,
7779
RAW_RESPONSE_HEADER,
7880
OVERRIDE_CAST_TO_HEADER,
7981
)
@@ -590,47 +592,57 @@ def base_url(self, url: URL | str) -> None:
590592
def platform_headers(self) -> Dict[str, str]:
591593
return platform_headers(self._version)
592594

595+
def _parse_retry_after_header(self, response_headers: Optional[httpx.Headers] = None) -> float | None:
596+
"""Returns a float of the number of seconds (not milliseconds) to wait after retrying, or None if unspecified.
597+
598+
About the Retry-After header: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After
599+
See also https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After#syntax
600+
"""
601+
if response_headers is None:
602+
return None
603+
604+
# First, try the non-standard `retry-after-ms` header for milliseconds,
605+
# which is more precise than integer-seconds `retry-after`
606+
try:
607+
retry_ms_header = response_headers.get("retry-after-ms", None)
608+
return float(retry_ms_header) / 1000
609+
except (TypeError, ValueError):
610+
pass
611+
612+
# Next, try parsing `retry-after` header as seconds (allowing nonstandard floats).
613+
retry_header = response_headers.get("retry-after")
614+
try:
615+
# note: the spec indicates that this should only ever be an integer
616+
# but if someone sends a float there's no reason for us to not respect it
617+
return float(retry_header)
618+
except (TypeError, ValueError):
619+
pass
620+
621+
# Last, try parsing `retry-after` as a date.
622+
retry_date_tuple = email.utils.parsedate_tz(retry_header)
623+
if retry_date_tuple is None:
624+
return None
625+
626+
retry_date = email.utils.mktime_tz(retry_date_tuple)
627+
return float(retry_date - time.time())
628+
593629
def _calculate_retry_timeout(
594630
self,
595631
remaining_retries: int,
596632
options: FinalRequestOptions,
597633
response_headers: Optional[httpx.Headers] = None,
598634
) -> float:
599635
max_retries = options.get_max_retries(self.max_retries)
600-
try:
601-
# About the Retry-After header: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After
602-
#
603-
# <http-date>". See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After#syntax for
604-
# details.
605-
if response_headers is not None:
606-
retry_header = response_headers.get("retry-after")
607-
try:
608-
# note: the spec indicates that this should only ever be an integer
609-
# but if someone sends a float there's no reason for us to not respect it
610-
retry_after = float(retry_header)
611-
except Exception:
612-
retry_date_tuple = email.utils.parsedate_tz(retry_header)
613-
if retry_date_tuple is None:
614-
retry_after = -1
615-
else:
616-
retry_date = email.utils.mktime_tz(retry_date_tuple)
617-
retry_after = int(retry_date - time.time())
618-
else:
619-
retry_after = -1
620-
621-
except Exception:
622-
retry_after = -1
623636

624637
# If the API asks us to wait a certain amount of time (and it's a reasonable amount), just do what it says.
625-
if 0 < retry_after <= 60:
638+
retry_after = self._parse_retry_after_header(response_headers)
639+
if retry_after is not None and 0 < retry_after <= 60:
626640
return retry_after
627641

628-
initial_retry_delay = 0.5
629-
max_retry_delay = 8.0
630642
nb_retries = max_retries - remaining_retries
631643

632644
# Apply exponential backoff, but not more than the max.
633-
sleep_seconds = min(initial_retry_delay * pow(2.0, nb_retries), max_retry_delay)
645+
sleep_seconds = min(INITIAL_RETRY_DELAY * pow(2.0, nb_retries), MAX_RETRY_DELAY)
634646

635647
# Apply some jitter, plus-or-minus half a second.
636648
jitter = 1 - 0.25 * random()

Diff for: src/openai/_constants.py

+3
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,6 @@
99
DEFAULT_TIMEOUT = httpx.Timeout(timeout=600.0, connect=5.0)
1010
DEFAULT_MAX_RETRIES = 2
1111
DEFAULT_LIMITS = httpx.Limits(max_connections=100, max_keepalive_connections=20)
12+
13+
INITIAL_RETRY_DELAY = 0.5
14+
MAX_RETRY_DELAY = 8.0

0 commit comments

Comments
 (0)