fix(compiler): retry transient LLM API timeouts with bounded backoff - #230
fix(compiler): retry transient LLM API timeouts with bounded backoff#230sebastianbraun25 wants to merge 2 commits into
Conversation
- Added retryable exception classifier (_should_retry_exception) - Modified _llm_call() to pass retries=2 to litellm.completion() - Modified _llm_call_async() to pass retries=2 to litellm.acompletion() - LiteLLM handles exponential backoff (base 2) internally - Retries transient errors (Timeout, RateLimitError, ConnectionError) - Skips retry for permanent errors (ValueError, Auth, BadRequest) - Added comprehensive unit tests for exception filtering logic Fixes VectifyAI#229
The retry mechanism used kwargs.setdefault('retries', 2), but LiteLLM only
recognizes 'num_retries'/'max_retries' as internal retry-control parameters.
An unrecognized 'retries' kwarg falls through as a provider request-body
field, which strict-mode proxies (e.g. custom Anthropic gateways) reject
with 'retries: Extra inputs are not permitted'.
Refines VectifyAI#229
…ectifyAI#229) Withdrawn together with the retry-sweep work (see prior revert commit): root-cause investigation shows the Gateway Timeout failures correlate with response duration/size (the largest existing pages fail deterministically on every attempt regardless of retries), pointing to an intermediary corporate LLM gateway/proxy enforcing a fixed request-duration limit rather than transient upstream load. A client-side retry policy does not address that root cause, and other users behind a similar corporate proxy would likely hit the same wall first — so retrying isn't the right fix to ship right now. The fix itself (num_retries vs. the unrecognized retries kwarg) may still be independently correct/useful; the feature branch is kept (not deleted) on the fork for possible later reactivation. Related upstream: closes our own copies tracking VectifyAI#229 and VectifyAI#230 (withdrawn there directly, not merged).
The branch was merged into integration twice: once early with the initial (buggy) 'retries=2' kwarg (68ee872, this revert), and again later with the 'num_retries' rename follow-up (4a4a3c1, already reverted). The prior revert of 4a4a3c1 alone left the original _should_retry_exception / kwargs.setdefault('retries', 2) code from 68ee872 still in place — completing the withdrawal here so integration carries no retry-classification code at all, matching the decision to withdraw VectifyAI#229/VectifyAI#230 pending root-cause investigation (see prior two revert commits).
|
Withdrawing this PR for now. WhyRoot-cause investigation (in downstream production use) points to these Gateway This is consistent with a fixed-duration timeout enforced by an intermediary Not a rejection of the approachThe underlying fix (using LiteLLM's recognized Closing #229 with the same reasoning. |
Problem
During batch document ingestion (
openkb add), ~15-20% of planned concepts and entities fail to generate with transient timeout errors:This occurs under sustained high-concurrency load (5 parallel requests over ~150 seconds) when multiple documents trigger concept/entity generation in the same batch run. Despite the error, the document is marked
[OK]and added with incomplete concept/entity coverage, reducing knowledge base quality.Root Cause
The Anthropic API gateway experiences overload after ~6-7 concurrent tasks sustained for 150+ seconds. This is a transient, recoverable server-side phenomenon (not a client-side timeout misconfiguration). The same request succeeds on retry because the gateway recovers within exponential backoff windows (2-4 seconds).
Solution
Implemented hybrid retry mechanism combining LiteLLM's built-in retry capability with selective exception filtering:
Timeout/Gateway TimeoutRateLimitError/429 Too Many RequestsConnectionErrorServiceUnavailableError/5xxAPI errorsValueError,TypeError(client-side bugs)AuthenticationError,BadRequestError(permanent auth/validation failures)TruncatedResponseError(output already truncated; retry won't fix it)Changes
Added
_should_retry_exception()function (~60 LOC)Modified
_llm_call()function (~30 LOC changes)num_retries=2kwarg tolitellm.completion()Modified
_llm_call_async()function (~30 LOC changes)num_retries=2kwarg tolitellm.acompletion()Added comprehensive unit tests (17 test cases in
tests/test_compiler_retry.py)num_retries(notretries) is forwarded tolitellm.completion/acompletion, and that an explicit caller-suppliednum_retriesisn't overriddenBehavior
"LLM [X] failed with transient error (retries applied by litellm)""LLM [X] failed with permanent error (no retry)"+ tracebackConfiguration
No configuration changes required. Retry behavior is fixed:
Testing
Expected Impact
Update: fixed wrong LiteLLM kwarg name (
retries→num_retries)The initial implementation passed
retries=2tolitellm.completion()/acompletion(). LiteLLM does not recognize a bareretrieskwarg as an internal control parameter (onlynum_retries/max_retries) — it silently falls through as an unrecognized provider request-body field. Against most providers this is harmlessly dropped, but strict-mode proxies (e.g. custom enterprise Anthropic gateways) reject the request outright:This effectively broke every LLM call (and therefore every
add) for anyone behind such a proxy — a regression more severe than the original timeout issue this PR set out to fix. Renamed tonum_retriesin both_llm_call()and_llm_call_async(), and added regression tests asserting the exact kwarg forwarded tolitellm.completion/acompletion.Issues