fix(nim): retry transient HTTP errors and report actual status code - #2019
fix(nim): retry transient HTTP errors and report actual status code#2019Aftabbs wants to merge 2 commits into
Conversation
|
DCO signoff is missing from the commit message and the approach here has already been noted an incorrect in a previous PR. Please see #1973, this PR duplicates the original offering in that PR and needs to account for the same feedback. |
When a NIM endpoint returns HTTP 408 (timeout), 502, 503, or 504, garak
currently aborts the entire probe with a misleading generic error message
("Is the model name spelled correctly?"). Two independent problems:
1. The backoff decorator on OpenAICompatible._call_model only caught
RateLimitError, InternalServerError, APITimeoutError, and
APIConnectionError — leaving bare openai.APIStatusError (which NIM
raises for 408) to fall through without any retry.
2. The catch-all except Exception block in NIM._call_model swallowed the
original HTTP status code, making diagnostics difficult.
Fix:
- Add _TRANSIENT_HTTP_CODES frozenset {408, 502, 503, 504} and a module-
level _is_terminal_api_error() giveup predicate in generators/openai.py.
- Extend the backoff decorator to also catch openai.APIStatusError, using
the giveup predicate to retry only transient codes and immediately
surface terminal errors (400, 401, 403, 404, 409, 422).
- Split the bare except Exception in NIM._call_model into an
openai.APIStatusError branch that logs and re-raises with the actual
HTTP status code and URL, plus a fallback branch that includes the
exception type name.
- Add 15 unit tests covering the giveup logic and error message content.
Closes NVIDIA#1967
Signed-off-by: Aftabbs <aftabbs.wwe@gmail.com>
d87a596 to
bd09a60
Compare
|
Nice catch on 408 arriving as One thing worth checking: So 429 and 500 would stop being retried, though both are listed in the existing backoff tuple. Adding the 5xx range and 429 to |
…or RateLimitError and InternalServerError The _is_terminal_api_error giveup function previously returned True for any APIStatusError whose status code was not in _TRANSIENT_HTTP_CODES. Because RateLimitError (429) and InternalServerError (500/503) both subclass APIStatusError, the predicate fired for them too, causing the backoff decorator to stop retrying errors that have dedicated retry entries in the exception tuple. Fix: return False immediately for RateLimitError and InternalServerError instances so the giveup predicate never overrides their dedicated retry logic. Also add 429 to _TRANSIENT_HTTP_CODES for completeness (bare APIStatusError with 429 should also be retried, e.g. from non-standard endpoints). New tests verify that the predicate does not fire for: - openai.RateLimitError (429) - openai.InternalServerError (500, 503) Signed-off-by: Aftabbs <aftabbs.wwe@gmail.com>
Summary
Fixes #1967 — NIM generator aborts the entire probe on HTTP 408 (request timeout) with a misleading error message instead of retrying.
Two root causes, both fixed here:
1.
openai.APIStatusErrorwas not caught by the backoff decoratorOpenAICompatible._call_modeluses@backoff.on_exceptionto retry on transient network failures, but the decorator only listedRateLimitError,InternalServerError,APITimeoutError, andAPIConnectionError. NIM raises a bareopenai.APIStatusErrorfor HTTP 408, which fell through the decorator and bubbled up unretried.Fix: Add
openai.APIStatusErrorto the exception tuple, with agiveup=_is_terminal_api_errorpredicate that retries only transient codes (408, 502, 503, 504) and immediately surfaces terminal errors (400, 401, 403, 404, 409, 422).2. The catch-all swallowed the HTTP status code
NIM._call_model's bareexcept Exceptionblock replaced the actual error with"NIM generation failed. Is the model name spelled correctly?"— losing the HTTP status code and URL that would identify the real problem.Fix: Split the handler:
openai.APIStatusErrornow logs and re-raises with the actual status code and request URL; the fallback branch includes the exception type name.Changes
garak/generators/openai.py_TRANSIENT_HTTP_CODESfrozenset and_is_terminal_api_error()giveup predicate; extend backoff decorator to catchAPIStatusErrorgarak/generators/nim.pyopenai.APIStatusError(reports HTTP status) +Exception(reports type name)tests/generators/test_nim.pyTesting
The 5 deselected tests require a live
NIM_API_KEYand are unchanged.