Commit e114dcf
UN-3344 [FIX] Unified retry for LLM and embedding providers (#1886)
* [FIX] Unified retry for LLM and embedding providers
litellm's retry only works for SDK-based providers (OpenAI/Azure).
httpx-based providers (Anthropic, Vertex, Bedrock, Mistral) and ALL
embedding calls silently ignore max_retries. This adds self-managed
retry with exponential backoff at the SDK layer, disabling litellm's
own retry entirely for consistency.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* [REFACTOR] DRY retry logic into reusable call_with_retry utilities
Move retry loops out of LLM/Embedding classes into generic
call_with_retry, acall_with_retry, and iter_with_retry functions
in retry_utils.py. Both classes now call these directly instead
of maintaining their own retry helper methods.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* [FIX] Consolidate retry logic, expose max_retries for all adapters
- Extract _get_retry_delay() shared helper to eliminate duplicated
retry decision logic across call_with_retry, acall_with_retry,
iter_with_retry, and retry_with_exponential_backoff
- Add num_retries=0 to embedding._pop_retry_params() to fully
disable litellm's internal retry for embedding calls
- Expose max_retries in UI JSON schemas for embedding adapters
(OpenAI, Azure, VertexAI, Ollama) and Ollama LLM — previously
the field existed in Pydantic models but wasn't shown to users,
silently defaulting to 0 retries
- Add debug logging to LLM and Embedding retry parameter extraction
- Clarify docstrings distinguishing is_retryable_litellm_error()
from is_retryable_error() (different exception hierarchies)
- Remove stale noqa: C901 from simplified retry_with_exponential_backoff
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* [FIX] Set max_retries default to 3 for all embedding and Ollama LLM adapters
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* [FIX] Address greptile review: fix shadowed ConnectionError, use MRO check
- Fix `requests.ConnectionError` shadowing Python's builtin `ConnectionError`
in `is_retryable_litellm_error()` — rename import to `RequestsConnectionError`
and use `builtins.ConnectionError` / `builtins.TimeoutError` explicitly
- Use `__mro__`-based class name check instead of `type(error).__name__`
to also catch subclasses of retryable error types
- P1 (num_retries not zeroed) was already fixed in prior commit
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* [FIX] Address CodeRabbit review: add APITimeoutError, validate max_retries
- Add APITimeoutError to _RETRYABLE_ERROR_NAMES for explicit OpenAI
SDK timeout coverage
- Add _validate_max_retries() guard to call_with_retry, acall_with_retry,
iter_with_retry to fail fast on negative values instead of silently
returning None
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* UN-3344 [FIX] Reduce cognitive complexity and remove useless except clause
Address SonarCloud findings on PR #1886:
- S3776: Flatten retry_with_exponential_backoff.wrapper by moving the
success logging + return out of the try block and using `continue` in
the retry path, so the except branch only handles the give-up case.
- S2737: Drop the `except Exception: raise` clause — it was a no-op that
added complexity without changing behavior (non-matching exceptions
propagate naturally).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* UN-3344 [FIX] Extract retry loop to top-level helper to drop cognitive complexity
Sonar still flagged retry_with_exponential_backoff at complexity 16 after
the previous flatten. Nested def decorator / def wrapper counted against
the outer function's score. Move the retry body to a module-level
_invoke_with_retries helper so the decorator factory just delegates,
bringing the outer function well under the 15 threshold.
Behavior is unchanged — all paths (success, retry, give-up, non-retryable
propagate) are preserved and covered by the existing SDK1 tests.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* UN-3344 [FIX] Honor Retry-After, close stream gen on retry, share give-up log
Address review comments on PR #1886:
- #10 (resource leak): close the generator returned by fn() before
retrying in iter_with_retry — otherwise streaming providers leak an
in-flight HTTP socket until GC.
- #12 (behavioral regression): when we zero out SDK/wrapper retries we
also lose the OpenAI SDK's native Retry-After handling on 429/503.
_get_retry_delay now checks error.response.headers["retry-after"] and
uses that value ahead of exponential backoff. HTTP-date form is not
parsed; those fall back to backoff.
- #8 (observability gap): move the "Giving up ... after N attempt(s)"
log into _get_retry_delay so all four retry helpers (call_with_retry,
acall_with_retry, iter_with_retry, decorator) share the same
exhaustion signal. Previously only the decorator path logged it.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* UN-3344 [REFACTOR] Share retry-kwargs helper and add TypeVar to retry wrappers
Address review comments on PR #1886:
- #9 (typing): call_with_retry / acall_with_retry / iter_with_retry
previously returned `object`, erasing caller type info. Add PEP 695
generics so the return type flows from the wrapped callable:
acall_with_retry now takes Callable[[], Awaitable[T]] and
iter_with_retry takes Callable[[], Iterable[T]] -> Generator[T, ...].
- #11 / #13 (DRY): `_pop_retry_params` in embedding.py and
`_disable_litellm_retry` in llm.py were identical logic. Lift to
shared `pop_litellm_retry_kwargs` helper in retry_utils.py and delete
both methods.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: ali <117142933+muhammad-ali-e@users.noreply.github.com>1 parent fd1bc63 commit e114dcf
9 files changed
Lines changed: 452 additions & 107 deletions
File tree
- unstract/sdk1/src/unstract/sdk1
- adapters
- embedding1/static
- llm1/static
- utils
Lines changed: 8 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
61 | 61 | | |
62 | 62 | | |
63 | 63 | | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
64 | 72 | | |
65 | 73 | | |
66 | 74 | | |
| |||
Lines changed: 2 additions & 2 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
43 | 43 | | |
44 | 44 | | |
45 | 45 | | |
46 | | - | |
47 | | - | |
| 46 | + | |
| 47 | + | |
48 | 48 | | |
49 | 49 | | |
50 | 50 | | |
| |||
Lines changed: 8 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
31 | 31 | | |
32 | 32 | | |
33 | 33 | | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
34 | 42 | | |
35 | 43 | | |
36 | 44 | | |
Lines changed: 8 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
44 | 44 | | |
45 | 45 | | |
46 | 46 | | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
47 | 55 | | |
48 | 56 | | |
49 | 57 | | |
| |||
Lines changed: 8 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
57 | 57 | | |
58 | 58 | | |
59 | 59 | | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
60 | 68 | | |
61 | 69 | | |
62 | 70 | | |
Lines changed: 8 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
48 | 48 | | |
49 | 49 | | |
50 | 50 | | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
51 | 59 | | |
52 | 60 | | |
53 | 61 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
| 3 | + | |
3 | 4 | | |
4 | 5 | | |
5 | 6 | | |
| |||
14 | 15 | | |
15 | 16 | | |
16 | 17 | | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
17 | 24 | | |
18 | 25 | | |
19 | 26 | | |
20 | 27 | | |
| 28 | + | |
| 29 | + | |
21 | 30 | | |
22 | 31 | | |
23 | 32 | | |
| |||
115 | 124 | | |
116 | 125 | | |
117 | 126 | | |
| 127 | + | |
118 | 128 | | |
119 | | - | |
120 | | - | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
121 | 135 | | |
122 | 136 | | |
123 | 137 | | |
| |||
127 | 141 | | |
128 | 142 | | |
129 | 143 | | |
| 144 | + | |
130 | 145 | | |
131 | | - | |
132 | | - | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
133 | 152 | | |
134 | 153 | | |
135 | 154 | | |
| |||
139 | 158 | | |
140 | 159 | | |
141 | 160 | | |
| 161 | + | |
142 | 162 | | |
143 | | - | |
144 | | - | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
145 | 169 | | |
146 | 170 | | |
147 | | - | |
148 | | - | |
| 171 | + | |
149 | 172 | | |
150 | 173 | | |
151 | 174 | | |
152 | 175 | | |
153 | 176 | | |
154 | 177 | | |
| 178 | + | |
155 | 179 | | |
156 | | - | |
157 | | - | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
158 | 186 | | |
159 | 187 | | |
160 | | - | |
161 | | - | |
| 188 | + | |
162 | 189 | | |
163 | 190 | | |
164 | 191 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
24 | 24 | | |
25 | 25 | | |
26 | 26 | | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
27 | 34 | | |
28 | 35 | | |
29 | 36 | | |
| |||
285 | 292 | | |
286 | 293 | | |
287 | 294 | | |
288 | | - | |
289 | | - | |
290 | | - | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
291 | 303 | | |
292 | 304 | | |
293 | 305 | | |
| |||
373 | 385 | | |
374 | 386 | | |
375 | 387 | | |
| 388 | + | |
| 389 | + | |
| 390 | + | |
376 | 391 | | |
377 | | - | |
378 | | - | |
379 | | - | |
380 | | - | |
381 | | - | |
382 | | - | |
383 | | - | |
| 392 | + | |
| 393 | + | |
| 394 | + | |
| 395 | + | |
| 396 | + | |
| 397 | + | |
| 398 | + | |
| 399 | + | |
| 400 | + | |
| 401 | + | |
384 | 402 | | |
385 | 403 | | |
386 | 404 | | |
| |||
437 | 455 | | |
438 | 456 | | |
439 | 457 | | |
440 | | - | |
441 | | - | |
442 | | - | |
| 458 | + | |
| 459 | + | |
| 460 | + | |
| 461 | + | |
| 462 | + | |
| 463 | + | |
| 464 | + | |
| 465 | + | |
443 | 466 | | |
444 | 467 | | |
445 | 468 | | |
| |||
0 commit comments