feat: coordinated rate-limit retry with per-model adaptive backoff - #17
Merged
Conversation
- Add exponential backoff retry (up to 5 retries) for LLMRateLimitError and LLMProviderError in all three generate*() functions - Add per-model coordinated cooldown (_ModelRateLimiter) so concurrent tasks pause together on 429, preventing thundering-herd retries - Extract Retry-After header from 429 responses when available - Add jitter to cooldown waits to spread wake-ups across the window - Only escalate cooldown when previous one expired (concurrent in-flight 429s absorbed without escalation) - Reset escalation on success so base cooldown returns to default - Suppress duplicate log warnings for concurrent in-flight 429s - Add call_label to GenerateOptions for per-seed retry log context - Thread call_label through rollout, judge, and seeds stages
…m internal retry - Document _classify_llm_error with full LiteLLM exception → HTTP status code mapping table (https://docs.litellm.ai/docs/exception_mapping) - Add PermissionDeniedError (403) → LLMAuthError - Add UnprocessableEntityError (422) → LLMInputError - Add APIResponseValidationError → LLMInputError - Use getattr() for new exception types for backward compat with older litellm - Raise _MAX_BACKOFF_S from 60 to 120 to fully honor Retry-After headers - Explicitly set litellm.num_retries=0 to prevent double-retry - Update test_retry_after_clamped_to_max to reflect new cap
Copilot started reviewing on behalf of
Aaron Aspinwall (AaronAspinwall123)
May 6, 2026 17:43
View session
Aaron Aspinwall (AaronAspinwall123)
approved these changes
May 6, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Add retry with coordinated per-model backoff for transient LLM failures (429 rate limits, 5xx provider errors). Previously, a single 429 or transient 5xx permanently failed the affected seed with no recovery.
What changed
p2m/core/model_client.pyPer-model adaptive rate limiter (
_ModelRateLimiter):Retry-Afterheader from the provider when availableRetry wrapper (
_with_retries):generatefunctions (generate,generate_structured,generate_with_tools)LLMRateLimitError(429) → coordinated cooldown retry (up to 5 attempts)LLMProviderError(5xx) → exponential backoff retry (1s × 2^attempt + jitter, capped at 120s)LLMAuthError(401/403) andLLMInputError(400/404/422) → propagate immediately, no retryError classification (
_classify_llm_error):PermissionDeniedError(403) →LLMAuthErrorUnprocessableEntityError(422) →LLMInputErrorAPIResponseValidationError→LLMInputErrorgetattr(litellm, ..., ())for new exception types for backward compatLiteLLM config hardening:
_MAX_BACKOFF_Sfrom 60 to 120 to fully honor serverRetry-Afterheaderslitellm.num_retries = 0to prevent double-retry (LiteLLM internal + our_with_retries)tests/test_rate_limit_retry.py(new, 23 tests)ExtractRetryAfterTest: header extraction from cause chainModelRateLimiterTest: cooldown, escalation, jitter, cross-model independence, retry-after clamping, success resetWithRetriesTest: success path, rate-limit retry, provider error backoff, non-retryable passthrough, max-retry exhaustionGenerateWithRetriesTest: integration test with mocked litellmReference
Compared against azure-ai-evaluation RetryPolicy — p2m's approach provides equivalent retry coverage with the addition of coordinated per-model cooldown, which is critical for fan-out eval workloads.
Testing