Fix OpenAI 429 rate limiting with exponential backoff
The Reddit search uses OpenAI's Responses API with web_search, which frequently returns 429 rate limit errors. The previous retry logic used linear backoff (1s, 2s, 3s) which is too aggressive for OpenAI's rate limiter (often needs 10-60s waits). Changes: - Increase max retries from 3 to 5 - Switch from linear to exponential backoff (2s, 5s, 9s, 17s, 33s) - Parse and respect Retry-After header from OpenAI 429 responses - Fall back to cheaper models (gpt-4.1 → gpt-4o) on 429s, not just on 400/403 access errors - Remove gpt-4o-mini from fallback chain — it doesn't support web_search with the filters parameter Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+16
-3
@@ -18,8 +18,8 @@ def log(msg: str):
|
|||||||
if DEBUG:
|
if DEBUG:
|
||||||
sys.stderr.write(f"[DEBUG] {msg}\n")
|
sys.stderr.write(f"[DEBUG] {msg}\n")
|
||||||
sys.stderr.flush()
|
sys.stderr.flush()
|
||||||
MAX_RETRIES = 3
|
MAX_RETRIES = 5
|
||||||
RETRY_DELAY = 1.0
|
RETRY_DELAY = 2.0
|
||||||
USER_AGENT = "last30days-skill/2.1 (Assistant Skill)"
|
USER_AGENT = "last30days-skill/2.1 (Assistant Skill)"
|
||||||
|
|
||||||
|
|
||||||
@@ -92,7 +92,20 @@ def request(
|
|||||||
raise last_error
|
raise last_error
|
||||||
|
|
||||||
if attempt < retries - 1:
|
if attempt < retries - 1:
|
||||||
time.sleep(RETRY_DELAY * (attempt + 1))
|
if e.code == 429:
|
||||||
|
# Respect Retry-After header, fall back to exponential backoff
|
||||||
|
retry_after = e.headers.get("Retry-After") if hasattr(e, 'headers') else None
|
||||||
|
if retry_after:
|
||||||
|
try:
|
||||||
|
delay = float(retry_after)
|
||||||
|
except ValueError:
|
||||||
|
delay = RETRY_DELAY * (2 ** attempt) + 1
|
||||||
|
else:
|
||||||
|
delay = RETRY_DELAY * (2 ** attempt) + 1 # 2s, 5s, 9s...
|
||||||
|
log(f"Rate limited (429). Waiting {delay:.1f}s before retry {attempt + 2}/{retries}")
|
||||||
|
else:
|
||||||
|
delay = RETRY_DELAY * (2 ** attempt)
|
||||||
|
time.sleep(delay)
|
||||||
except urllib.error.URLError as e:
|
except urllib.error.URLError as e:
|
||||||
log(f"URL Error: {e.reason}")
|
log(f"URL Error: {e.reason}")
|
||||||
last_error = HTTPError(f"URL Error: {e.reason}")
|
last_error = HTTPError(f"URL Error: {e.reason}")
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ from typing import Any, Dict, List, Optional
|
|||||||
from . import http
|
from . import http
|
||||||
|
|
||||||
# Fallback models when the selected model isn't accessible (e.g., org not verified for GPT-5)
|
# Fallback models when the selected model isn't accessible (e.g., org not verified for GPT-5)
|
||||||
MODEL_FALLBACK_ORDER = ["gpt-4.1", "gpt-4o", "gpt-4o-mini"]
|
# Note: gpt-4o-mini does NOT support web_search with filters param, so exclude it
|
||||||
|
MODEL_FALLBACK_ORDER = ["gpt-4.1", "gpt-4o"]
|
||||||
|
|
||||||
|
|
||||||
def _log_error(msg: str):
|
def _log_error(msg: str):
|
||||||
@@ -188,6 +189,9 @@ def search_reddit(
|
|||||||
if _is_model_access_error(e):
|
if _is_model_access_error(e):
|
||||||
_log_info(f"Model {current_model} not accessible, trying fallback...")
|
_log_info(f"Model {current_model} not accessible, trying fallback...")
|
||||||
continue
|
continue
|
||||||
|
if e.status_code == 429:
|
||||||
|
_log_info(f"Rate limited on {current_model}, trying fallback model...")
|
||||||
|
continue
|
||||||
# Non-access error, don't retry with different model
|
# Non-access error, don't retry with different model
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user