Repository navigation
feat(retry): Retry webhooks, client LLM proxy and guardrails calls - #1275
Conversation
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 <[email protected]>
|
Important Review skippedAuto reviews are limited based on label configuration. 🏷️ Required labels (at least one): 1
Please check the settings in the CodeRabbit UI or the ⚙️ Run configuration
You can disable this status message by setting the Use the checkbox below for a quick retry:
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. Comment |
OpenAPI changes ⚪ No API surface changesNote This PR does not modify the API contract.
|
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
| f"[run_guardrails_validation] Response received | job_id={job_id}, " | ||
| f"status={response.status_code}, elapsed_ms={elapsed_ms}, " | ||
|
|
||
| @retry( |
There was a problem hiding this comment.
can combine this into one single decorator I guess, instead of repeating everywhere
There was a problem hiding this comment.
It's a decorator. So thought of keeping it staright forward
There was a problem hiding this comment.
That was Claude's suggestion. But overrode it to make the retry straight forward
…ElevenLabs Drop the max_retries=3 overrides and the ElevenLabs tenacity wrapper; provider calls go back to each SDK's default retry behaviour. This PR now only retries webhooks, the client LLM proxy and guardrails. Co-Authored-By: Claude Opus 5.5 <[email protected]>
_execute_proxy_call ran inside execute_llm_call's session, so the read transaction (or the one reopened by create_llm_call's refresh) stayed open across the proxy request and its retry backoff, pinning a pooled connection per concurrent fast-eval call. Commit before the request. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
|
🎉 This PR is included in version 1.8.0-main.31 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
Summary
Kaapi's own outbound HTTP calls now retry transient failures 3 times (4 attempts) using tenacity's
@retrydirectly. Provider SDK calls keep each SDK's default retry: OpenAI, Anthropic and Sarvam retry twice, and ElevenLabs doesn't retry.Secondary; lock
litellmversion>=1.83.10,<1.90to prevent future security exploits.The shared predicate is
app/core/retry.py:Never retried: 4xx, 429, read timeouts, guardrails auth errors (401/403/422 still fail closed).
A proxy failure that was already retried returns
BlockResult.retryable=False, so fast-evalretry_llm_calldoesn't stack 4 more attempts on top.Evidence
send_callbackagainst a receiver returning503→ 1 POST, then a failure callback /False.After:
TestSendCallbackRetry:302 → ConnectionError → 500 → 200→Trueafter 4 POSTs; a constant503→Falseafter 4 POSTs; each attempt has a freshX-Webhook-Timestamp.503→ bypassed after 1 call.After:
TestGuardrailsRetry:503 → 200→ validated result; four503s →bypassed=True;401→ 1 call,auth_error=True.ConnectError→ job failed after 1 call,retryable=True.After:
TestPostToProxyRetry:ConnectError → 502 → 200→ success on call 3;ReadTimeoutand400→ 1 call.uv run pytest -n 0: 1222 tests (core,test_utils,services/llm,services/guardrails,api/routes/test_guardrails). Pre-commit is clean.Merge Danger
Door: two-way
The change is decorators plus one small module; no schema, migration or config changes. Reverting the PR restores single-attempt behaviour.
Blast Radius: latency
execute_llm_call's session. Withrecord_call=False(fast evals), a read transaction stays open across the backoff waits.🤖 Generated with Claude Code