Skip to content

feat(reliability): bounded retry for transient LLM/embedding provider failures#928

Closed
seonghobae wants to merge 1 commit into
developfrom
feat/provider-transient-retry
Closed

feat(reliability): bounded retry for transient LLM/embedding provider failures#928
seonghobae wants to merge 1 commit into
developfrom
feat/provider-transient-retry

Conversation

@seonghobae

Copy link
Copy Markdown
Contributor

Gap

Request-path provider calls had zero retry: a single flaky connection, timeout, 429, or provider 5xx became a user-facing 500 — across search embeddings, summary extraction, translation, and reply drafting. (Reliability audit item #3.)

What

  • services/retry.py: stdlib-only retry_transientno new dependency (requirements are hash-pinned). Retries ONLY transient errors (APIConnectionError, APITimeoutError, RateLimitError, InternalServerError) with exponential backoff + jitter (max 2 retries, 8s cap). Auth/4xx validation errors propagate immediately → existing fail-closed behavior preserved.
  • Wired into the 4 provider call sites: embedding.generate_embeddings, extract_todos_and_summary, translate_email_body, draft_reply. Error mapping (EmbeddingGenerationError/LLMServiceError) and client lifecycle unchanged.

Verification (local — CI currently frozen org-wide)

  • tests/test_retry.py (5 new): success passthrough · transient→success with exponential delays · budget exhaustion re-raises original · non-transient immediate fail · custom retryable tuple.
  • Regressions green: test_llm_service + test_embedding + test_search + test_llm_api + test_import_fixtures = 92 passed.

Not included (follow-up)

Circuit breaker / load-shedding when a provider is hard-down — retry alone is the right first slice; a breaker needs shared state and deserves its own design.

🤖 Generated with Claude Code

… failures

Request-path provider calls previously had zero retry: one flaky connection,
timeout, 429, or provider 5xx became a user-facing failure (search embedding,
summary extraction, translation, reply drafting).

Adds services/retry.py — a stdlib-only retry_transient helper (no new
dependency; requirements are hash-pinned): retries ONLY transient errors
(APIConnectionError, APITimeoutError, RateLimitError, InternalServerError)
with exponential backoff + jitter, capped at 2 retries / 8s delay. Auth and
4xx validation errors propagate immediately, preserving fail-closed behavior.
Wired into the four provider call sites (embeddings.create, extraction parse,
translation, reply drafting); error mapping and client lifecycle unchanged.

Tests: tests/test_retry.py (5) — success passthrough, transient-then-success
with exponential delays, budget exhaustion re-raises original error,
non-transient fails immediately, custom retryable tuple. Regressions green
locally: llm_service/embedding/search/llm_api/import_fixtures (92 passed).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017RkKdtHRLG4wSLh6PVsp8J
Comment thread backend/services/retry.py
if attempt >= retries:
raise
delay = min(base_delay_seconds * (2**attempt), MAX_DELAY_SECONDS)
delay += random.uniform(0, delay / 2)
@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

PR governance metadata gate is not ready for d51b918724156d10ad306c90e58475de7b69f2de:

  • 1 unresolved current review thread(s) remain.

@seonghobae

Copy link
Copy Markdown
Contributor Author

Folded into #933, which is now retargeted to develop as one independent PR (this was a stacked PR that could not auto-merge). Closing to avoid duplicate/tangled merge order.

@seonghobae seonghobae closed this Jul 6, 2026
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