Skip to content

feat(retry): Retry webhooks, LLM proxy, guardrails and provider SDK calls - #1275

Open
Prajna1999 wants to merge 6 commits into
mainfrom
feat/retry-webhooks-and-provider-calls
Open

Prajna1999 wants to merge 6 commits into
mainfrom
feat/retry-webhooks-and-provider-calls

Conversation

@Prajna1999

@Prajna1999 Prajna1999 commented Oct 19, 2026 •

Copy link
Copy Markdown
Collaborator

Issue

No linked issue.

Summary

Transient failures in synchronous outbound calls currently fail a job on the first attempt. This covers a provider 5xx, a blip reaching the guardrails service, the client LLM proxy returning a 502, and a webhook receiver timing out. This PR adds 3 retries (4 attempts in total) to each of these. Batch providers are out of scope.

Kaapi's own HTTP calls: tenacity @retry

Each of these uses stop_after_attempt(4) (3 retries), wait_exponential(max=10) and one shared retry check, app/core/retry.py::is_transient. The check is true for an HTTP 5xx, or for a connection that never reached upstream.

Call site Retries on Not retried
utils.send_callback (webhooks) any non-2xx (3xx too, since redirects are disabled) and any requests.RequestException. The HMAC is re-signed on each attempt. invalid / SSRF-blocked URL
jobs._post_to_proxy (client LLM proxy) 5xx, connect failures ReadTimeout (the call may already have run and been billed), 4xx
guardrails.run_guardrails_validation / list_validators_config 5xx, connect failures; then the existing fail-open path 401/403/422 (these still fail closed)
guardrails.proxy_guardrails_request (management API) connect failures only, so a POST is never re-sent after it landed 5xx is returned upstream as-is

Provider calls: each SDK's built-in retry

I checked each setting exists in the installed SDK version, then ran it against a fake server that always returns 503:

Provider Setting Attempts measured on 503
OpenAI (2.24.0) OpenAI(..., max_retries=3) 4
Anthropic (0.104.1) Anthropic(..., max_retries=3) 4
Sarvam (0.1.27), STT + TTS request_options={"max_retries": 3} per call 4; retried uploads still carry the file
ElevenLabs (2.38.1), STT + TTS tenacity @retry in eleven_ai.py (5xx / connect only) 4; STT re-sends the file, TTS retry wraps the stream read

The SDKs retry 408/409/429/5xx with backoff, and OpenAI/Anthropic also retry connection errors and timeouts.

Behaviour change: a proxy failure that was already retried now comes back as BlockResult.retryable=False. This keeps fast-eval retry_llm_call from stacking more attempts. Provider failures keep retryable=True for now.

The wiki pages are updated: llm-call.md, guardrails.md, cross-cutting/exceptions.md.

Checklist

  • Ran fastapi run --reload app/main.py or docker compose up in the repository root and test.
  • If you've fixed a bug or added code that is tested and has test cases.

Notes

  • ElevenLabs uses tenacity, not its SDK setting. In 2.38.1 max_retries doesn't work as documented: its retry counter starts at 2, so max_retries=3 gives only 1 retry, and streaming TTS never retries.
  • Gemini / Vertex: these retries are in feat(genai): Update google-genai SDK for improvements #1244 (google-genai SDK update). Both genai clients there set HttpRetryOptions(attempts=3) at client level.
  • Tests: the affected suites pass (1591 tests with -n 0). Backoff is zeroed with an opt-in no_retry_backoff fixture.
  • Not covered: api/routes/threads.py has its own unvalidated send_callback with no retry. It's untouched.

🤖 Generated with Claude Code

Add a thin tenacity decorator, retry_thrice, and a shared is_transient
predicate in app/core/retry.py.

- send_callback retries any non-2xx response or request error and
  re-signs the payload on each attempt.
- The provider call in execute_llm_call retries 5xx errors.
- The client-LLM proxy and guardrails calls retry 5xx and connect
  failures.

Already-retried failures are marked retryable=False so fast-eval
retries don't stack.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot changed the title feat(retry): Retry webhooks and transient upstream failures feat(retry): Add shared retry layer Oct 19, 2026
@coderabbitai

coderabbitai Bot commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are limited based on label configuration.

🏷️ Required labels (at least one): 1
  • ready-for-review

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration
  • Configuration used: Repository: ProjectTech4DevAI/kaapi-backend/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 4a071e9b-21a2-4b8f-8b95-7c8694de3012

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

OpenAPI changes   ⚪ No API surface changes

Note

This PR does not modify the API contract.

main ↔ 9098b378 · generated by oasdiff

Prajna1999 and others added 2 commits October 19, 2026 18:06
Drop the retry_thrice wrapper. Call sites now use tenacity's @Retry with
stop_after_attempt(3) and exponential backoff. app/core/retry.py keeps
only the shared is_transient predicate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Remove the provider-call retry from execute_llm_call. Provider SDK
retries move to a follow-up PR stacked on the google-genai SDK update.
Provider failures keep retryable=True as before, and is_transient now
only classifies HTTP exceptions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Prajna1999 Prajna1999 changed the title feat(retry): Add shared retry layer feat(retry): Retry webhooks, client LLM proxy and guardrails calls Oct 19, 2026
Use each SDK's built-in retry: max_retries=3 on the OpenAI and Anthropic
clients, and request_options={"max_retries": 3} on Sarvam STT/TTS calls.
Verified against a stub server: 4 attempts on 503, and retried Sarvam
uploads still carry the file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Prajna1999 Prajna1999 changed the title feat(retry): Retry webhooks, client LLM proxy and guardrails calls feat(retry): Retry webhooks, LLM proxy, guardrails and provider SDK calls Oct 19, 2026
Match the provider SDKs' max_retries=3 so webhooks, the LLM proxy and
guardrails also retry three times after the first attempt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@codecov

codecov Bot commented Oct 19, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

The SDK's max_retries is unusable: its counter starts at 2, and
streaming TTS never retries. Wrap STT convert and the TTS stream
consumption in a tenacity @Retry (4 attempts, 5xx/connect only).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
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.

1 participant