fix(core): a non-positive retry budget no longer skips the wrapped call - #2146
fix(core): a non-positive retry budget no longer skips the wrapped call#2146DABH wants to merge 3 commits into
Conversation
All four retry wrappers in automatic_retries.py iterate 'for attempt in range(retries)', so a retries value of 0 or below made the range empty and silently skipped the wrapped callable: the sync and async wrappers fell through and returned None, and the generator wrappers yielded nothing. Nothing validates the budget upstream (RetryMixin.num_retries is an unconstrained int passed straight into patch_with_retry), so a misconfigured budget turned every patched method into a silent no-op. Normalize the budget once in _retry_decorator (total_attempts = max(1, retries)) and use it consistently in all four variants, so the wrapped callable always executes at least once, and log the normalization once at debug level in the decorator factory so a below-1 budget is observable without per-call or per-method noise. Behavior for budgets >= 1 is unchanged: the budget still counts total attempts. Document the semantics in the module, _retry_decorator, and patch_with_retry docstrings, and add tests covering budgets of 0, -1, and the boundary budget of 1 for all four wrapper variants plus failure propagation and the patch_with_retry path. Signed-off-by: David Hyde <DABH@users.noreply.github.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Enterprise Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
WalkthroughThe retry decorator now treats ChangesRetry budget behavior
Estimated code review effort: 3 (Moderate) | ~20 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
packages/nvidia_nat_core/src/nat/utils/exception_handlers/automatic_retries.py (1)
447-453: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winUse the required Google-style docstring.
patch_with_retryis a public function. Replace the NumPy-styleRetry budgetsection with a Google-styleArgs:section. Format parameter names as code entities.As per coding guidelines, “Provide Google-style docstrings for every public module, class, function and CLI command” and “Surround code entities with backticks.”
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/nvidia_nat_core/src/nat/utils/exception_handlers/automatic_retries.py` around lines 447 - 453, Update the public function `patch_with_retry` docstring by replacing the NumPy-style “Retry budget” section with a Google-style `Args:` section. Document `retries` there and surround the parameter name with backticks, while preserving the existing description of attempts and minimum behavior.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In
`@packages/nvidia_nat_core/src/nat/utils/exception_handlers/automatic_retries.py`:
- Around line 447-453: Update the public function `patch_with_retry` docstring
by replacing the NumPy-style “Retry budget” section with a Google-style `Args:`
section. Document `retries` there and surround the parameter name with
backticks, while preserving the existing description of attempts and minimum
behavior.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Enterprise
Run ID: 06ab08a6-50a7-4447-bda1-5e9728ac02e8
📒 Files selected for processing (2)
packages/nvidia_nat_core/src/nat/utils/exception_handlers/automatic_retries.pypackages/nvidia_nat_core/tests/nat/utils/test_retry_wrapper.py
Review feedback: the retry-budget paragraph added to the patch_with_retry docstring used a NumPy-style section, but the coding guidelines require Google-style docstrings for public functions with backticked code entities. Convert the section to a Google-style Args: entry for retries, preserving the documented semantics (total attempts per call, values below 1 behave as 1). Docstring-only change; runtime behavior is unchanged for every budget value. Signed-off-by: David Hyde <DABH@users.noreply.github.com>
|
Checked this against
Why a non-positive budget is reachable. So The fix doesn't change the normal path. Counting invocations of an always-failing callable, before and after the patch: Identical, which confirms the docstring addition ("counts total attempts: the initial call plus any retries") is documenting the existing semantics rather than redefining them — worth stating explicitly, since "normalize to at least 1 attempt" could otherwise read like a behaviour change to the budget. One detail worth calling out because it's easy to miss when reviewing: changing Nothing blocking from me. The only thing I'd consider is whether AI-assisted review; the outputs above come from executing the module as fetched from |
Description
nat.utils.exception_handlers.automatic_retriesis the retry layer that provider and client plugin packages build on. The provider-authoring guide (docs/source/extend/custom-components/adding-an-llm-provider.md) tells plugin authors to addRetryMixinto their provider config, and the first-party client packages wireRetryMixin.num_retriesstraight intopatch_with_retryso every public client method retries on retryable errors (for examplepackages/nvidia_nat_langchain/src/nat/plugins/langchain/llm.py) — the pattern third-party client packages follow.All four wrapper variants produced by
_retry_decorator(plain sync, coroutine, sync generator, and async generator) iteratefor attempt in range(retries). When the budget is 0 or negative, the range is empty and the wrapped callable is never invoked at all:None,No exception is raised and nothing is logged, so the failure mode is a silent no-op. Nothing validates the budget upstream either:
RetryMixin.num_retriesis an unconstrainedintthat first-party and third-party provider packages pass straight intopatch_with_retry(retries=...). A user who setsnum_retries: 0in a workflow configuration (a natural way to express "do not retry") turns every patched LLM, embedder, or memory client method into a call that never reaches the provider and quietly returnsNoneor an empty stream. That is very hard to debug from inside a plugin package, and it is a correctness trap for any plugin author who exposes the retry configuration to end users.This change makes the wrapped callable always execute at least once:
_retry_decorator(total_attempts = max(1, retries)) and use it consistently in all four wrapper variants, both for the attempt loop and for the last-attempt re-raise check.DEBUGlevel. It happens in the decorator factory, which runs once perpatch_with_retrycall rather than per patched method or per invocation, so the adjustment is observable without adding log noise._retry_decoratorandpatch_with_retrydocstrings: the budget counts total attempts (the initial call plus any retries), and values below 1 behave as 1.patch_with_retrypublic path. The eleven non-positive-budget cases fail against the previous implementation; the budget-1 cases pass against both implementations.Clamping rather than raising is deliberate:
num_retrieshas never been constrained, so configurations with a non-positive budget load and run today (however incorrectly), and raising would turn them into hard startup failures — normalizing to a single attempt instead givesnum_retries: 0the semantics its author plainly intended (call once, never retry).No tracking issue exists for this yet; happy to file one if the team prefers.
Testing
uv run --project packages/nvidia_nat_core -- pytest packages/nvidia_nat_core/tests/nat/utils/test_retry_wrapper.py -q— 46 passed (30 pre-existing tests unchanged, 16 new).uv run pre-commit run yapf --files packages/nvidia_nat_core/src/nat/utils/exception_handlers/automatic_retries.py packages/nvidia_nat_core/tests/nat/utils/test_retry_wrapper.py— Passed.uv run pre-commit run ruff-check --files packages/nvidia_nat_core/src/nat/utils/exception_handlers/automatic_retries.py packages/nvidia_nat_core/tests/nat/utils/test_retry_wrapper.py— Passed.uv run python ci/scripts/copyright.py --verify-apache-v2— passed.By Submitting this PR I confirm:
Summary by CodeRabbit
Bug Fixes
Documentation
Tests