-
Notifications
You must be signed in to change notification settings - Fork 564
chore(types): Type-clean llm/ (27 errors) #1394
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
Converting to draft while I rebase on the latest changes to develop. |
eb83d1b to
bb67063
Compare
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
09198df to
6bc9cd5
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Greptile Overview
Greptile Summary
This PR improves type safety across the llm/ directory by eliminating 27 Pyright errors through stricter type annotations, defensive null checks, and proper handling of optional dependencies. The changes include tightening function contracts (e.g., making model_name required in init_llm_model), correcting return type annotations (to_chat_messages now properly returns List[dict]), adding **kwargs to method signatures to satisfy the Liskov Substitution Principle, and implementing graceful degradation for optional packages like tritonclient and transformers. The CI workflows were enhanced to install all extras via --all-extras, enabling comprehensive type-checking of optional integrations such as langchain-nvidia-ai-endpoints. The trtllm provider was excluded from type-checking due to tritonclient's incompatibility with Python 3.10+.
PR Description Notes:
- Several typos in
Model Initialization Errorreferences (should beModelInitializationError)
Important Files Changed
| Filename | Score | Overview |
|---|---|---|
nemoguardrails/llm/providers/trtllm/llm.py |
1/5 | Critical runtime error: InferenceServerException imported inside TYPE_CHECKING block but used at runtime, will cause NameError |
nemoguardrails/llm/providers/huggingface/streamers.py |
2/5 | Potential logic bug: stream_end=True appears to enqueue text instead of expected stop signal; fallback TextStreamer stub has no implementation |
nemoguardrails/llm/params.py |
3/5 | Changed from in-place mutation to explicit getattr/setattr of model_kwargs, may cause side effects if dictionary is shared elsewhere |
nemoguardrails/rails/llm/llmrails.py |
3/5 | Fallback model name extraction from parameters["model"] lacks defensive access, risks KeyError on malformed configs |
nemoguardrails/llm/providers/huggingface/pipeline.py |
3/5 | Defensive getattr() for _generate/_agenerate trades static type safety for runtime flexibility; fire-and-forget async task on line 123 |
nemoguardrails/llm/models/initializer.py |
4/5 | Breaking change: model_name changed from Optional[str] to str, requires all callers to provide valid string |
nemoguardrails/llm/helpers.py |
4/5 | Added **kwargs to _call/_acall to satisfy base class contract; redundant hasattr + getattr pattern could be streamlined |
nemoguardrails/llm/providers/trtllm/client.py |
4/5 | Improved optional dependency handling, but early TRITONCLIENT_AVAILABLE check in __init__ may raise ImportError prematurely |
.github/workflows/test-coverage-report.yml |
4/5 | Added --all-extras to Poetry install, increases CI time and memory but enables comprehensive type-checking |
.github/workflows/full-tests.yml |
4/5 | Added upgrade-deps: true parameter to support --all-extras installation in reusable workflow |
.github/workflows/pr-tests.yml |
5/5 | Low-risk addition of upgrade-deps: true parameter for flexible test configuration |
pyproject.toml |
5/5 | Pragmatic exclusion of trtllm provider from Pyright checks due to dependency incompatibility |
nemoguardrails/llm/filters.py |
5/5 | Purely additive type hints and corrected return type for to_chat_messages, no runtime behavior change |
nemoguardrails/llm/taskmanager.py |
5/5 | Added defensive None checks for prompt.max_length and config.instructions to prevent TypeError |
Confidence score: 2/5
- This PR contains critical runtime errors and potential logic bugs that will cause failures in production if merged as-is
- Score reflects the
InferenceServerExceptionTYPE_CHECKINGissue that guarantees crashes when trtllm is used, the suspiciousstream_endlogic in streamers.py, the unsafe dictionary access in llmrails.py that risksKeyError, and the breaking API change toinit_llm_modelthat may impact external callers - Pay close attention to
nemoguardrails/llm/providers/trtllm/llm.py(must moveInferenceServerExceptionimport outsideTYPE_CHECKING),nemoguardrails/llm/providers/huggingface/streamers.py(verifyon_finalized_textlogic), andnemoguardrails/rails/llm/llmrails.py(add defensiveparametersaccess)
Sequence Diagram
sequenceDiagram
participant Developer
participant CI/CD
participant Poetry
participant Pyright
participant LLMRails
participant ModelInitializer
participant LLMProviders
participant Runtime
Developer->>CI/CD: Push PR with type fixes
CI/CD->>Poetry: Install dependencies with --all-extras
Note over Poetry: Includes langchain-nvidia-ai-endpoints<br/>for type checking
CI/CD->>Pyright: Run type checking
Note over Pyright: Excludes trtllm provider<br/>(tritonclient deprecated)
Pyright-->>CI/CD: ✓ Type check passed (27 errors fixed)
Developer->>LLMRails: Initialize with config
LLMRails->>ModelInitializer: init_llm_model(model_name, provider, mode, kwargs)
Note over ModelInitializer: model_name now REQUIRED (str)<br/>Previously Optional[str]
ModelInitializer->>LLMProviders: Initialize language models
alt Main LLM provided via constructor
LLMRails->>LLMRails: Configure streaming support
LLMRails->>Runtime: Register llm param
else Main LLM from config
ModelInitializer-->>LLMRails: Return BaseLLM/BaseChatModel
LLMRails->>LLMRails: Configure streaming support
LLMRails->>Runtime: Register llm param
end
Note over LLMRails,Runtime: Type-safe parameter passing<br/>with proper None checks
LLMRails->>Runtime: Initialize model caches
Note over Runtime: Defensive getattr() usage<br/>Safe attribute access
Developer->>LLMRails: generate_async(messages, options)
LLMRails->>LLMRails: _get_events_for_messages(messages, state)
Note over LLMRails: Enhanced None checks<br/>Guard clauses added
LLMRails->>Runtime: generate_events(events)
Runtime->>LLMProviders: _call(prompt, stop, run_manager, **kwargs)
Note over LLMProviders: Method signatures match superclass<br/>Liskov Substitution Principle
LLMProviders-->>Runtime: Generated response
Runtime-->>LLMRails: New events
LLMRails->>LLMRails: Apply type-safe filters
Note over LLMRails: to_chat_messages returns List[dict]<br/>Previously incorrectly typed as str
LLMRails-->>Developer: GenerationResponse with proper types
Additional Comments (3)
-
nemoguardrails/llm/params.py, line 74-87 (link)logic: The dictionary retrieved via
getattr(self.llm, "model_kwargs", {})is now mutated and written back. Ifmodel_kwargsis shared/referenced elsewhere, this could cause unintended side effects. -
nemoguardrails/llm/providers/huggingface/streamers.py, line 61 (link)logic: putting
textagain in the queue whenstream_end=Trueseems unintentional—should this beself.stop_signalinstead? -
nemoguardrails/llm/providers/huggingface/pipeline.py, line 75-80 (link)style: Using
getattr(self, "_generate")instead of direct method call. This pattern breaks static type checking and IDE support. If_generateis part of theHuggingFacePipelineAPI contract, use it directly; if it's optional, handle the AttributeError explicitly. Is _generate guaranteed to exist on HuggingFacePipeline, or is this defensive programming for version compatibility?
14 files reviewed, 12 comments
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Greptile Overview
Greptile Summary
Note: This review covers only the changes made since the last review, not the entire PR.
This update addresses all outstanding review feedback from the type-cleaning PR by reverting most trtllm/ changes (which are excluded from type-checking), simplifying the workflow configuration, and refining error-handling patterns. The developer has:
- Excluded the LangChain NVIDIA patch from type-checking – Added
_langchain_nvidia_ai_endpoints_patch.pyto the pyright exclusion list inpyproject.toml, eliminating the need to install all extras in CI. - Reverted
trtllm/changes – Sincetrtllm/**is excluded from pyright, the defensive type-safety changes (lazy imports, None-checks) have been rolled back to minimize unnecessary churn in deprecated code. - Refined the LFU cache – Added defensive guards for
Nonechecks in doubly-linked-list operations (append,pop) and ensured consistent initialization of thehit_ratefield in stats dictionaries.
These changes integrate with the existing Guardrails codebase by maintaining the pragmatic pattern of excluding hard-to-type optional dependencies (like tritonclient and now the LangChain patch) from static analysis, while preserving runtime correctness. The LFU cache improvements align with the broader type-safety initiative without altering core logic or introducing performance overhead.
Important Files Changed
| Filename | Score | Overview |
|---|---|---|
pyproject.toml |
5/5 | Added _langchain_nvidia_ai_endpoints_patch.py to pyright exclusion list and reformatted to multi-line for maintainability. |
nemoguardrails/llm/cache/lfu.py |
5/5 | Added redundant but harmless None checks to linked-list operations and ensured hit_rate is consistently initialized. |
nemoguardrails/llm/providers/trtllm/client.py |
3/5 | Refactored to use lazy imports for tritonclient and removed defensive None checks on API responses; defers ImportError to runtime method invocation. |
nemoguardrails/llm/providers/trtllm/llm.py |
N/A | File appears to have no meaningful changes in this diff. |
Confidence score: 4/5
- This PR is safe to merge with low risk, provided the
trtllmmodule is indeed deprecated or unused in production (as the PR description indicates). - Score reflects that the changes are minimal and mostly additive (type-checking exclusions, defensive guards), but the
trtllm/client.pyrefactoring introduces a behavior change (lazy imports + removedNonechecks) that could surface issues if that module is still in use. The developer should confirm thattrtllmis either fully deprecated or covered by integration tests. - Pay close attention to
nemoguardrails/llm/providers/trtllm/client.py– verify that the removal ofNonechecks forget_model_repository_index,get_model_config, andas_numpyis safe given the tritonclient API contract. If this module is not deprecated, add tests to validate the assumptions about non-Noneresponses.
4 files reviewed, 1 comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Greptile Overview
Greptile Summary
This PR implements systematic type-safety improvements across the LLM module by making model_name a required parameter, correcting method signatures to match base classes, adding explicit type annotations, and introducing defensive None checks. The changes eliminate 27 type errors by tightening contracts at the type-checking level rather than relying on runtime validation. A key architectural shift is that Model.model is now required (non-optional) in the configuration schema, supported by a Pydantic validator that extracts model names from the parameters dict when necessary. The CI workflows are also modified to handle optional dependencies more carefully—the coverage report workflow now excludes --all-extras to keep testing lightweight, while the _langchain_nvidia_ai_endpoints_patch.py file remains excluded from pyright checks as a runtime-only patch.
Important Files Changed
| Filename | Score | Overview |
|---|---|---|
.github/workflows/test-coverage-report.yml |
3/5 | Reverts to base installation without --all-extras, potentially missing coverage for optional dependencies |
nemoguardrails/rails/llm/config.py |
2/5 | Makes Model.model required (non-optional), which may break configs that rely on parameter-based extraction |
nemoguardrails/rails/llm/llmrails.py |
3/5 | Removes fallback logic for retrieving model name from parameters, relying on upstream validation |
nemoguardrails/llm/providers/huggingface/pipeline.py |
3.5/5 | Replaces one getattr with direct access, but leaves async streaming task unhandled and creates inconsistency |
nemoguardrails/llm/models/initializer.py |
4/5 | Changes model_name from optional to required—breaking change mitigated by upstream validation changes |
nemoguardrails/llm/helpers.py |
5/5 | Adds **kwargs to _call and _acall to fix Liskov Substitution Principle violation, fully safe change |
.github/workflows/pr-tests.yml |
5/5 | Removes upgrade-deps parameter to standardize test behavior, no risk introduced |
.github/workflows/full-tests.yml |
5/5 | Removes upgrade-deps parameter consistently with pr-tests, safe configuration change |
Confidence score: 3/5
- This PR has significant type-safety improvements but introduces potential breaking changes that require careful validation of all configuration paths and call sites.
- Score reflects three primary concerns: (1) the
Model.modelfield change from optional to required may break existing configurations that only specify model names in parameters, (2) the coverage workflow no longer installs all extras which could mask type-checking issues in optional dependencies, and (3) the unhandled async task inhuggingface/pipeline.pyremains unresolved despite previous review comments. - Pay close attention to
nemoguardrails/rails/llm/config.pyandnemoguardrails/rails/llm/llmrails.pyto ensure the Pydantic validator correctly populatesmodelbefore the required-field validation runs, and verify that all example configurations and tests that use parameter-based model specification continue to work.
Additional Comments (1)
-
nemoguardrails/rails/llm/config.py, line 145-148 (link)logic: This logic allows
modelto be absent when onlyparameters.modelorparameters.model_nameis provided. But with the change on line 115 makingmodelrequired (non-optional), Pydantic will reject such configs before this validator runs. The validator expects to populatemodelfrom parameters, but nowmodelmust exist at parse time. How should configs that only specify model_name in parameters (without a top-level model field) be handled given the new required model field?
8 files reviewed, 3 comments
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Greptile Overview
Greptile Summary
This review covers only the changes made since the last review, not the entire PR. The most recent changes address reviewer feedback by reversing the CI/CD strategy: instead of installing all extras to support type-checking of optional dependencies (langchain-nvidia-ai-endpoints), the PR now excludes _langchain_nvidia_ai_endpoints_patch.py from Pyright checks and removes the --all-extras flag from workflows. The changes modify three workflow files (.github/workflows/pr-tests.yml, full-tests.yml, test-coverage-report.yml) by removing the upgrade-deps: true parameter (first two files) or removing --all-extras from poetry install (test-coverage-report.yml). The pyproject.toml now excludes the patch file from type-checking, consistent with the existing trtllm exclusion pattern. This approach trades comprehensive type-checking for simpler CI configuration and faster builds by avoiding the installation of large optional dependency sets.
Important Files Changed
| Filename | Score | Overview |
|---|---|---|
.github/workflows/pr-tests.yml |
4/5 | Removed upgrade-deps: true parameter from workflow call, simplifying CI configuration but potentially affecting dependency freshness in test environments |
.github/workflows/full-tests.yml |
4/5 | Removed upgrade-deps: true parameter from workflow call, matching the change in pr-tests workflow for consistency |
.github/workflows/test-coverage-report.yml |
2/5 | Removed --all-extras from poetry install command, causing coverage tests to skip optional dependencies and potentially mask issues in those code paths |
pyproject.toml |
5/5 | Added _langchain_nvidia_ai_endpoints_patch.py to Pyright exclude list, preventing type-checking failures for optional dependency code |
Confidence score: 3/5
- This PR changes the testing strategy in ways that create inconsistencies and reduce test coverage, requiring careful verification before merge
- Score reflects concerns about the coverage-report workflow removing
--all-extras(creating incomplete coverage of optional features) and potential unintended consequences of removingupgrade-depsflags across multiple workflows - Pay close attention to
.github/workflows/test-coverage-report.yml- verify whether excluding optional dependencies from coverage is intentional; also check whether theupgrade-depsremoval inpr-tests.ymlandfull-tests.ymlaligns with the project's dependency management strategy
4 files reviewed, no comments
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Greptile Overview
Greptile Summary
This review covers changes made to two critical files (config.py and llmrails.py) in response to feedback from the previous review. The key change reverts the model field in the Model class from a required str back to Optional[str], resolving a Pydantic validator sequencing issue identified earlier. Specifically, the mode="before" validator set_and_validate_model extracts model from the parameters dict if not directly provided, but Pydantic would have rejected instantiation before this validator could run if the field were required. The new two-stage validator pattern (mode="before" for extraction + mode="after" for non-empty validation) allows the code to support multiple configuration styles—model can be specified directly in the model field or within parameters["model_name"]/parameters["model"]—while still enforcing that a valid, non-empty model name exists post-validation. The companion change in llmrails.py adds a runtime guard (if not model_name: raise ValueError) before calling init_llm_model, ensuring the contract is enforced even if validators are bypassed. Together these changes maintain backward compatibility with existing configurations (which may pass model names via parameters) while improving type safety and error handling.
PR Description Notes:
- The PR description incorrectly classifies the
config.pychange as "High-Risk" and states it makesmodel_namerequired; the actual code does the opposite (makes it optional with validators).
Important Files Changed
| Filename | Score | Overview |
|---|---|---|
nemoguardrails/rails/llm/config.py |
4/5 | Reverted model field to Optional[str] with two-stage validator pattern (before/after) to handle model extraction from parameters and enforce non-empty values post-validation. |
nemoguardrails/rails/llm/llmrails.py |
5/5 | Added runtime validation to raise ValueError if model field is missing or empty before initializing LLM, acting as defensive check for programmatically constructed configs. |
Confidence score: 4/5
- This PR is safe to merge with low risk, addressing the critical Pydantic validator sequencing issue identified in previous reviews.
- Score reflects that the core logic is sound and well-tested (1932 passed tests), but the validator interaction is subtle—Pydantic's
mode="before"runs before field validation, andmode="after"runs after model construction. The two-stage pattern is correct, but any future changes to the validator order or field constraints could re-introduce the catch-22. The discrepancy between the PR description (claiming "high-risk" required field change) and the actual implementation (optional field with validators) suggests potential confusion that should be clarified in documentation. - Pay close attention to
nemoguardrails/rails/llm/config.pylines 115-168 (the validator logic) to ensure the three scenarios (model in field, model in parameters, model missing) are all handled correctly. Thellmrails.pychange is straightforward defensive programming.
2 files reviewed, 1 comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Greptile Overview
Greptile Summary
This review covers only the changes made since the last review, not the entire PR. The developer has addressed previous feedback by fixing the LFU cache implementation with explicit None-checks and removing unnecessary float coercion. The changes in nemoguardrails/llm/cache/lfu.py replace implicit truthiness checks (if self.tail.prev:) with explicit comparisons (if self.tail.prev is not None:) on lines 57-58 and 71-74, which prevents false negatives when dealing with potentially falsy objects. Additionally, lines 279-281 remove the redundant float() wrapper around the hit-rate calculation since Python3division already returns a float. These modifications align with the PR's broader goal of eliminating type errors while maintaining defensive programming practices. The LFU cache is a least-frequently-used eviction policy implementation used by the LLM module's caching layer, so these changes improve type-safety in a performance-critical path without altering runtime behavior.
Important Files Changed
| Filename | Score | Overview |
|---|---|---|
| nemoguardrails/llm/cache/lfu.py | 5/5 | Replaced implicit truthiness checks with explicit is not None comparisons and removed unnecessary float() wrapper in hit-rate calculation |
Confidence score: 5/5
- This change is extremely safe to merge with no risk of regressions
- Score reflects simple defensive programming improvements with zero logic changes and comprehensive test coverage (1932 tests passed)
- No files require special attention; the LFU cache changes are straightforward null-safety improvements
1 file reviewed, 1 comment
…a_ai_endpoints for Github CI tests
…points work for pyright type-checking
…nts_patch.py from type-checking
…ed from type-checking
68b2b1b to
721a71b
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Greptile Overview
Greptile Summary
This PR improves type safety across the llm/ module by adding explicit type hints, defensive None checks, and fixing function signature mismatches. The changes eliminate 27 Pyright errors by making the type system reflect actual runtime behavior: variables now have explicit types, optional values are guarded before use, and function contracts are clarified. The PR includes one high-risk breaking change (init_llm_model now requires a non-optional model_name), several medium-risk signature corrections (return types, **kwargs additions, None checks in loop conditions), and numerous low-risk additive improvements (type annotations, getattr usage, optional-dependency handling). The changes integrate with the broader codebase by ensuring that upstream callers (particularly in llmrails.py) validate inputs before passing them to stricter downstream functions, shifting error detection from runtime to static analysis time.
Important Files Changed
| Filename | Score | Overview |
|---|---|---|
nemoguardrails/llm/models/initializer.py |
4/5 | Changed model_name parameter from Optional[str] to required str, a breaking API change requiring all callers to provide a valid string |
nemoguardrails/rails/llm/llmrails.py |
4/5 | Added None guards before model initialization to ensure non-optional model names are always passed to init_llm_model |
nemoguardrails/llm/helpers.py |
4/5 | Added **kwargs to _call and _acall methods to match LangChain base class interface, fixing Liskov Substitution Principle violation |
nemoguardrails/llm/providers/huggingface/pipeline.py |
3/5 | Added fallback import logic for optional dependencies and defensive getattr usage, but dummy class inheritance may fail if both imports fail |
nemoguardrails/llm/params.py |
3.5/5 | Added type hints and getattr for defensive attribute access, but copy-modify-set pattern for model_kwargs may be unnecessary if attribute is mutable |
nemoguardrails/llm/filters.py |
5/5 | Added type annotations to local variables and corrected return type from str to List[dict], purely additive with no runtime impact |
pyproject.toml |
5/5 | Enabled Pyright type-checking for llm/ module and excluded trtllm provider and NVIDIA endpoints patch from checks |
Confidence score: 4/5
- This PR is relatively safe to merge, with most changes being defensive improvements that reduce runtime error risk rather than introducing new behavior.
- Score reflects one breaking API change (
model_nameparameter strictness), potential edge-case failures in optional-dependency fallback logic (HuggingFace dummy class inheritance, triton runtime exception handling), and the copy-modify-set pattern inparams.pythat may be redundant. The breaking change is mitigated by coordinated guard clauses in callers, but external consumers of the API may be affected. - Pay close attention to
nemoguardrails/llm/models/initializer.py(breaking change requires verification that all callers provide non-None model names),nemoguardrails/llm/providers/huggingface/pipeline.py(dummy class inheritance may fail at definition time), andnemoguardrails/llm/params.py(copy-modify-set pattern formodel_kwargsneeds validation that it preserves expected mutability semantics).
Sequence Diagram
sequenceDiagram
participant User
participant LLMRails
participant Initializer
participant ModelInit
participant LLMWrapper
participant TaskManager
participant Filters
participant Cache
User->>LLMRails: generate_async(messages)
Note over LLMRails: Type-safe initialization
LLMRails->>LLMRails: _init_llms()
alt Main model from config
LLMRails->>Initializer: init_llm_model(model_name: str, provider_name, mode, kwargs)
Note over Initializer: model_name now required (not Optional[str])
Initializer->>ModelInit: init_langchain_model()
ModelInit-->>LLMRails: Return BaseChatModel | BaseLLM
end
LLMRails->>Cache: _initialize_model_caches()
Cache->>Cache: LFUCache(maxsize, track_stats, stats_logging_interval)
Note over Cache: Type-safe initialization with explicit types
LLMRails->>LLMRails: _get_events_for_messages(messages, state)
LLMRails->>TaskManager: render_task_prompt(prompt, context, events)
Note over TaskManager: Guard against None values
TaskManager->>TaskManager: Check if instructions is None
TaskManager->>TaskManager: Check if prompt.max_length is not None
TaskManager->>TaskManager: Check if prompt.messages is not None
TaskManager-->>LLMRails: Return rendered prompt
LLMRails->>LLMWrapper: _call(prompt, stop, run_manager, **kwargs)
Note over LLMWrapper: Now accepts **kwargs for compatibility
LLMWrapper->>LLMWrapper: _modify_instance_kwargs()
LLMWrapper->>LLMWrapper: getattr(llm_instance, "model_kwargs", {})
Note over LLMWrapper: Safe attribute access with default
LLMRails->>Filters: to_chat_messages(events)
Note over Filters: Return type corrected to List[dict]
Filters-->>LLMRails: List[dict] of chat messages
LLMRails-->>User: Return GenerationResponse
Additional Comments (3)
-
nemoguardrails/llm/providers/huggingface/streamers.py, line 60-61 (link)logic: When
stream_end=True, the sametextis put into the queue twice (lines 58 & 61). This will duplicate the final chunk. Should line 61 putself.stop_signalinstead? -
nemoguardrails/llm/params.py, line 74-87 (link)logic: Lines 74 & 86-87 retrieve
model_kwargsviagetattr, modify it, then set it back. However, ifmodel_kwargsis a mutable dict on the llm instance, this copy-modify-set pattern is unnecessary—you can mutate in-place. Ifmodel_kwargsis a frozen/immutable object, this pattern is correct. Verify whetherself.llm.model_kwargsis typically mutable. If it is, lines 74 & 87 can be removed and you can mutateself.llm.model_kwargsdirectly. Ismodel_kwargstypically a mutable dict attribute on LangChain LLMs, or is it sometimes immutable/copy-on-write? -
nemoguardrails/llm/params.py, line 102-105 (link)style: Same copy-modify-set pattern for
model_kwargsin__exit__. Ifmodel_kwargsis mutable (see above), this can be simplified to direct mutation without thegetattr/setattrround-trip.
9 files reviewed, 3 comments
* Cleaned llm/ type errors * Add nemoguardrails/llm to the pyright pre-commit check * Fix types in nemoguardrails/rails module * Use poetry install --all-extras --with dev to install langchain_nvidia_ai_endpoints for Github CI tests * Install extras in test-coverage-report so the langchain_nvidia_ai_endpoints work for pyright type-checking * Remove tritonclient from type-checking (should this be deprecated? * Add upgrade-deps to the full-tests.yml file in Github CI/CD * Exclude providers/trtllm/** and providers/_langchain_nvidia_ai_endpoints_patch.py from type-checking * Roll back type cleaning under llm/providers/trtllm now they're excluded from type-checking * Type-clean the LFU cache implementation * Address Pouyan's feedback. Removed Model.model Optional and default value * fix typo * Revert github workflow changes (not needed now we exclude trtllm from type-checking) * Remove comment from pyproject.toml * Revert mandatory Model name field change, add None-guard back * Address last feedback
* Cleaned llm/ type errors * Add nemoguardrails/llm to the pyright pre-commit check * Fix types in nemoguardrails/rails module * Use poetry install --all-extras --with dev to install langchain_nvidia_ai_endpoints for Github CI tests * Install extras in test-coverage-report so the langchain_nvidia_ai_endpoints work for pyright type-checking * Remove tritonclient from type-checking (should this be deprecated? * Add upgrade-deps to the full-tests.yml file in Github CI/CD * Exclude providers/trtllm/** and providers/_langchain_nvidia_ai_endpoints_patch.py from type-checking * Roll back type cleaning under llm/providers/trtllm now they're excluded from type-checking * Type-clean the LFU cache implementation * Address Pouyan's feedback. Removed Model.model Optional and default value * fix typo * Revert github workflow changes (not needed now we exclude trtllm from type-checking) * Remove comment from pyproject.toml * Revert mandatory Model name field change, add None-guard back * Address last feedback
Type-Safety Fixes Summary
This report summarizes the code changes made to improve type safety. The fixes have been categorized into high, medium, and low-risk buckets based on their potential to cause regressions or unintended behavior.
🔴 High-Risk Changes
This category includes changes that alter a function's core contract, making it a potentially breaking change for consumers of the API.
Stricter Function Argument Type
This fix tightens a function's API by changing an optional argument to a required one. This is high-risk because any calling code that previously passed
Nonewill now fail.nemoguardrails/llm/models/initializer.py27model_namewasOptional[str], but the function's logic implicitly required a string value, creating a risk of aNonevalue causing a downstream crash.model_namewas changed fromOptional[str]tostr, making it a required argument. This enforces the contract at the type-checking level and makes the function's expectations explicit.model_nameshould never beNoneand that all call sites can provide a valid string. This assumption is supported by related changes innemoguardrails/rails/llm/llmrails.pywhich now ensure a valid model name is always passed.Optional[str]and raise aValueErrorinside the function ifmodel_nameisNone. The implemented fix is better because it fails earlier (at static analysis or integration time) rather than at runtime.🟡 Medium-Risk Changes
These changes correct significant errors or modify function signatures in a way that is likely correct but could affect downstream code that depended on the previous (incorrect) behavior.
1. Corrected Function Return Type Annotation
The function's implementation returned a different type than what its signature declared.
nemoguardrails/llm/filters.py278strbut was actually returning aList[dict].strtoList[dict]to match the actual output of the function. This resolves the inconsistency and provides the correct type information to static analyzers and developers.List[dict]and that no code was relying on the incorrectstrannotation.2. Modified Function Signature to Match Superclass
The method signature in a subclass did not match the signature of the method it was overriding in the parent class.
nemoguardrails/llm/helpers.py77-80and88-91_calland_acallmethods in theWrapperLLMclass were missing the**kwargsparameter present in the baseLLMclass from LangChain, causing a signature mismatch error (Liskov Substitution Principle violation).**kwargswas added to the method signatures and passed through to the wrappedllm_instancecall. This makes the wrapper compliant with the base class interface, allowing it to accept and forward any additional keyword arguments.llm_instancealso accepts**kwargsin its_calland_acallmethods, which is a safe assumption for LangChain LLM objects.3. Added Control Flow for Optional Values in Core Logic
A loop condition involved comparing a value with a potentially
Noneattribute, which would cause aTypeErrorat runtime.nemoguardrails/llm/taskmanager.py268-269and294-296len(task_prompt)withprompt.max_lengthinside awhileloop without checking ifprompt.max_lengthwasNone.prompt.max_length is not Nonewas added to the loop condition. This ensures the length comparison only happens when a maximum length is actually defined, preventing aTypeError.prompt.max_lengthisNone, no length constraint should be applied. This is the standard and logical interpretation of such an optional parameter.max_lengthif it'sNone, but the implemented guard condition is more explicit and readable.🟢 Low-Risk Changes
These changes are additive or fix minor issues with minimal to no risk of causing regressions. They include adding type hints,
Nonechecks in non-critical paths, and improving CI configurations.1. Added Type Hints to Local and Instance Variables
Variables were initialized without explicit types, reducing code clarity and hindering static analysis.
nemoguardrails/llm/filters.py,nemoguardrails/llm/params.py,nemoguardrails/llm/providers/huggingface/streamers.py2. Improved Runtime Safety with
NoneChecks (Guard Clauses)Code that accessed or iterated over potentially
Nonevalues lacked proper checks, creating a risk of runtime errors.nemoguardrails/llm/taskmanager.py,nemoguardrails/rails/llm/llmrails.pyNonebefore attempting to use an object. This is a defensive programming practice that makes the code more robust againstTypeErrororAttributeErrorexceptions.3. Defensive Attribute Access with
getattrDirect attribute access (
obj.attr) was used on objects where the attribute might not exist, risking anAttributeError.nemoguardrails/llm/helpers.py,nemoguardrails/llm/params.py,nemoguardrails/llm/providers/huggingface/pipeline.pygetattr(obj, "attr", default_value). This safely retrieves an attribute, providing a default value if it doesn't exist, thus preventing crashes and making the code more resilient.4. Graceful Handling of Optional Dependencies
The code would crash with an
ImportErrorif optional dependencies liketritonclientortransformerswere not installed, even if their functionality wasn't being used.nemoguardrails/llm/providers/trtllm/client.py,nemoguardrails/llm/providers/huggingface/pipeline.pytry...except ImportErrorblocks to set a boolean flag (e.g.,TRITONCLIENT_AVAILABLE). Type hints are handled usingif TYPE_CHECKING:blocks andAnyfallbacks. At runtime, anImportErroris raised only when the specific functionality is actually invoked. This makes the library's import structure more robust.5. Enhanced CI/CD and Static Analysis Configuration
The continuous integration and static analysis configurations were improved for better test coverage and dependency management.
.github/workflows/*,pyproject.toml--all-extras), ensuring that optional features are also tested.pyproject.tomlfile was updated to exclude thetrtllmprovider frompyrightchecks, which is a pragmatic solution given thattritonclientcan be difficult to type-check correctly. This prevents the CI pipeline from failing on type errors related to an optional, platform-specific dependency.Test Plan
Type-checking
$ poetry run pre-commit run --all-files check yaml...............................................................Passed fix end of files.........................................................Passed trim trailing whitespace.................................................Passed isort (python)...........................................................Passed black....................................................................Passed Insert license in comments...............................................Passed pyright..................................................................PassedUnit-tests
Local CLI check
Related Issue(s)
Top-level PR to merge into before develop-branch merge: #1367
Checklist