From b8a9bf72884b750520cb9404f43254e996e75c47 Mon Sep 17 00:00:00 2001 From: Anya Sabo Date: Sat, 6 Jun 2026 14:18:10 -0700 Subject: [PATCH] fix: respect Gemini API RetryInfo delay on 429 responses Parse retryDelay from the google.rpc.RetryInfo error details and use it as the sleep duration before retrying. Falls back to the existing linear backoff (tries * retry_wait_time) when the field is absent or malformed. Ref: https://github.com/googleapis/python-genai/pull/2114 Ref: https://github.com/datalab-to/marker/issues/490 --- marker/services/gemini.py | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/marker/services/gemini.py b/marker/services/gemini.py index fd626fc2f..a687edf50 100644 --- a/marker/services/gemini.py +++ b/marker/services/gemini.py @@ -30,6 +30,31 @@ def img_to_bytes(self, img: PIL.Image.Image): img.save(image_bytes, format="WEBP") return image_bytes.getvalue() + @staticmethod + def _get_retry_delay(error: APIError) -> float | None: + details = getattr(error, "details", None) + if not isinstance(details, dict): + return None + # Response may nest under 'error' key or be flat + error_details = ( + details.get("error", {}).get("details") + or details.get("details") + ) + if not isinstance(error_details, list): + return None + for entry in error_details: + if not isinstance(entry, dict): + continue + if not entry.get("@type", "").endswith("RetryInfo"): + continue + delay_str = entry.get("retryDelay", "") + if isinstance(delay_str, str) and delay_str.endswith("s"): + try: + return float(delay_str[:-1]) + except ValueError: + continue + return None + def get_google_client(self, timeout: int): raise NotImplementedError @@ -101,7 +126,7 @@ def __call__( ) break else: - wait_time = tries * self.retry_wait_time + wait_time = self._get_retry_delay(e) or tries * self.retry_wait_time logger.warning( f"APIError: {e}. Retrying in {wait_time} seconds... (Attempt {tries}/{total_tries})", )