Skip to content

feat: extract and use retry delay from Gemini 429 error payloads - #662

Merged
dwash96 merged 4 commits into
cecli-dev:v1.4.0from
DinoChiesa:feature/gemini-retry-backoff
Sep 1, 2026
Merged

feat: extract and use retry delay from Gemini 429 error payloads#662
dwash96 merged 4 commits into
cecli-dev:v1.4.0from
DinoChiesa:feature/gemini-retry-backoff

Conversation

@DinoChiesa

Copy link
Copy Markdown

When using Gemini models a 429 response often includes a retryDelay in the JSON. This is typically o(10) seconds, sometimes more. cecli uses a blind backoff computation. This can result in denial of service when using Gemini models. To correct this, respect the retryDelay delivered in Gemini model responses.

  • Add _extract_gemini_retry_delay helper to Model class in cecli/models.py to extract suggested retryDelay (in seconds) from 429 error response payloads.

  • Update send_completion() and simple_send_with_retries() to prioritize Gemini's suggested retry delay over blind unilateral backoff multipliers when available.

  • Add unit tests in tests/unit/test_gemini_retry_backoff.py covering valid retryDelay extraction, JSON string payloads, non-429 responses, and missing details fields.

Co-authored-by: cecli (gemini/gemini-3.6-flash)

When using Gemini models a 429 response often includes a retryDelay
in the JSON. This is typically o(10) seconds, sometimes more. cecli
uses a blind backoff computation. This can result in denial of service
when using Gemini models. To correct this, respect the retryDelay
delivered in Gemini model responses.

- Add `_extract_gemini_retry_delay` helper to `Model` class in
  `cecli/models.py` to extract suggested `retryDelay` (in seconds) from
  429 error response payloads.

- Update `send_completion()` and `simple_send_with_retries()` to
  prioritize Gemini's suggested retry delay over blind unilateral
  backoff multipliers when available.

- Add unit tests in `tests/unit/test_gemini_retry_backoff.py` covering
  valid retryDelay extraction, JSON string payloads, non-429 responses,
  and missing details fields.

Co-authored-by: cecli (gemini/gemini-3.6-flash)
Co-authored-by: cecli (gemini/gemini-3.6-flash)
Co-authored-by: cecli (gemini/gemini-3.6-flash)
@DinoChiesa

Copy link
Copy Markdown
Author

For an example, the actual Gemini payload with a 429 looks like this:

{
  "error": {
    "code": 429,
    "message": "Quota exceeded for metric 'generate_content_requests' and limit 'Requests per minute' of service 'generativelanguage.googleapis.com' for model 'gemini-2.5-flash'. Please retry in 15.2s.",
    "status": "RESOURCE_EXHAUSTED",
    "details": [
      {
        "@type": "://googleapis.com",
        "violations": [
          {
            "subject": "client_id:your_api_key_or_project",
            "description": "Rate limit exceeded. Too many requests per minute (RPM)."
          }
        ]
      },
      {
        "@type": "://googleapis.com",
        "retryDelay": "15.2s"
      }
    ]
  }
}

And that retryDelay is material.

@dwash96

dwash96 commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator

I think this whole thing can be simplified with the dot separated path accessor I use quite a bit with nested.getter(). It's built for pretty much exactly this type of thing and we can make the delay extraction provider agnostic by just adding path strings. The syntax would be something like: nested.getter(err, ["..that long api path"], None)

Edit: I guess there's a bit of looping involved, but most of the deeply nested object logic and intermediate json checking and parsing can be done with the above and utils.split_concatenated_json() to keep the code base consistent-ish

- extract retry delay via Gemini payload and HTTP headers
- add tests for same

Co-authored-by: cecli (gemini/gemini-3.7-flash)
@DinoChiesa

DinoChiesa commented Aug 31, 2026

Copy link
Copy Markdown
Author

OK see what you think about this. I've generalized it beyond Gemini. The other providers use headers; Gemini uses a value in the payload.

Unfortunately the black formatter (via pre-commit) seems to be changing lots of minor formatting things that are irrelevant.

@dwash96

dwash96 commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

Yep, this is exactly what I meant. I want to keep most of the model specific logic inside of the llms module because it's really easy for agents to start adding model/provider specific shims in essentially random places around the code base if we let them and it makes the core agent loop more brittle as a result. Thanks!

@dwash96
dwash96 changed the base branch from main to v1.4.0 September 1, 2026 01:04
@dwash96
dwash96 merged commit 06c559a into cecli-dev:v1.4.0 Sep 1, 2026
12 checks passed
@dwash96 dwash96 mentioned this pull request Sep 1, 2026
@DinoChiesa
DinoChiesa deleted the feature/gemini-retry-backoff branch September 1, 2026 03:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants