Skip to content

fix(iorails): Add LLMRails llm and runtime getters#1888

Closed
tgasser-nv wants to merge 1 commit into
fix/guardrails-llmrails-methodsfrom
fit/guardrails-llmrails-attributes
Closed

fix(iorails): Add LLMRails llm and runtime getters#1888
tgasser-nv wants to merge 1 commit into
fix/guardrails-llmrails-methodsfrom
fit/guardrails-llmrails-attributes

Conversation

@tgasser-nv

@tgasser-nv tgasser-nv commented May 13, 2026

Copy link
Copy Markdown
Collaborator

This PR has a typo in its branch name, see #1889 for the corrected branch name

@tgasser-nv tgasser-nv changed the base branch from develop to fix/guardrails-llmrails-methods May 13, 2026 21:22
@codecov

codecov Bot commented May 13, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@tgasser-nv tgasser-nv changed the title fix(iorails): Add LLMRails llm and runtime getters fix(iorails): Add LLMRails llm and runtime getters May 13, 2026
@tgasser-nv tgasser-nv marked this pull request as ready for review May 13, 2026 21:25
@tgasser-nv

Copy link
Copy Markdown
Collaborator Author

@greptile-apps Review this PR

@tgasser-nv

Copy link
Copy Markdown
Collaborator Author

@coderabbitai Review this PR

@coderabbitai

coderabbitai Bot commented May 13, 2026

Copy link
Copy Markdown
Contributor

@tgasser-nv Sure, I'll review this PR right away!

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@greptile-apps

greptile-apps Bot commented May 13, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds llm and runtime property getters to the Guardrails facade class, delegating to the underlying LLMRails instance and raising NotImplementedError for IORails — consistent with the existing pattern used by explain(), update_llm(), and other LLMRails-only methods.

  • guardrails.py: Two new read-only properties (llm, runtime) and one new import (Runtime) following the exact isinstance(IORails) → raise then cast(LLMRails, ...) pattern already established in the file.
  • test_guardrails.py: New TestGuardrailsAttributes class covering delegation under LLMRails, read-through after update_llm, and NotImplementedError under IORails for both new properties plus config.

Confidence Score: 5/5

Safe to merge — two small read-only property getters following an already-established pattern, fully covered by new unit tests.

The change is minimal: two delegating properties and one import. Both properties mirror the exact guard-and-cast pattern used by every other LLMRails-only method in the file. Tests cover delegation, read-through after update_llm, and the IORails error path for each property. No edge cases were left unaddressed.

No files require special attention.

Important Files Changed

Filename Overview
nemoguardrails/guardrails/guardrails.py Adds llm and runtime property getters using the established IORails guard + cast pattern; import of Runtime base class is correct.
tests/guardrails/test_guardrails.py New TestGuardrailsAttributes class covers delegation, read-through after update_llm, and NotImplementedError for both IORails cases; no issues found.

Sequence Diagram

sequenceDiagram
    participant Caller
    participant Guardrails
    participant LLMRails
    participant IORails

    Caller->>Guardrails: .llm
    alt rails_engine is IORails
        Guardrails-->>Caller: raise NotImplementedError
    else rails_engine is LLMRails
        Guardrails->>LLMRails: .llm
        LLMRails-->>Guardrails: Optional[LLMModel]
        Guardrails-->>Caller: Optional[LLMModel]
    end

    Caller->>Guardrails: .runtime
    alt rails_engine is IORails
        Guardrails-->>Caller: raise NotImplementedError
    else rails_engine is LLMRails
        Guardrails->>LLMRails: .runtime
        LLMRails-->>Guardrails: Runtime
        Guardrails-->>Caller: Runtime
    end
Loading

Reviews (1): Last reviewed commit: "Add getters for Guardrails properties: l..." | Re-trigger Greptile

@greptile-apps

greptile-apps Bot commented May 13, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds llm and runtime read-only property getters to the Guardrails facade class, delegating to the underlying LLMRails instance and raising NotImplementedError when IORails is in use. The import of Runtime from nemoguardrails.colang.runtime is consistent with the base class used by LLMRails, so the return type annotation is accurate.

  • Guardrails.llm / Guardrails.runtime: Both properties guard against IORails via isinstance then cast to LLMRails, correctly exposing the underlying attributes without caching.
  • Tests: Seven new tests in TestGuardrailsAttributes cover LLMRails delegation, IORails rejection, and config accessibility; one test (test_llm_property_reflects_update_llm) has a minor logical gap where the mock attribute is set before update_llm is called, making the update_llm call redundant to the assertion.

Confidence Score: 4/5

Safe to merge; the new getters are thin read-through properties with no side effects and the import chain is consistent.

The implementation is minimal and correct — Runtime from nemoguardrails.colang.runtime is the same base class that LLMRails uses, so the return type annotation is accurate. The only open concern is that both properties use a negative isinstance(IORails) check followed by an unconditional cast rather than a positive isinstance(LLMRails) assertion, leaving the code silently fragile if a third engine type is ever added.

Both changed files are straightforward; guardrails.py is worth a second look for the implicit LLMRails assumption in the guard logic.

Important Files Changed

Filename Overview
nemoguardrails/guardrails/guardrails.py Adds llm and runtime property getters that delegate to LLMRails or raise NotImplementedError for IORails; relies on a negative-only isinstance guard before casting to LLMRails.
tests/guardrails/test_guardrails.py Adds TestGuardrailsAttributes with 7 tests covering delegation, NotImplementedError on IORails, and config accessibility; one test pre-sets the mock attribute before calling update_llm, weakening its "no caching" claim.

Sequence Diagram

sequenceDiagram
    participant Caller
    participant Guardrails
    participant IORails
    participant LLMRails

    Caller->>Guardrails: guardrails.llm (or .runtime)
    alt rails_engine is IORails
        Guardrails-->>Caller: raise NotImplementedError
    else rails_engine is LLMRails
        Guardrails->>LLMRails: cast + read .llm / .runtime
        LLMRails-->>Guardrails: value
        Guardrails-->>Caller: return value
    end
Loading
Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
nemoguardrails/guardrails/guardrails.py:95-108
**Implicit LLMRails assumption via negative-only check**

Both properties guard against `IORails` but then unconditionally `cast` the result to `LLMRails`. If a third engine type is introduced in the future, the `cast` would silently succeed (no-op at runtime) and the attribute access would either return a wrong value or raise an `AttributeError` with a confusing message. A positive `isinstance(self.rails_engine, LLMRails)` check would make the assumption explicit and produce a clear error for unsupported engines.

### Issue 2 of 2
tests/guardrails/test_guardrails.py:795-812
**`update_llm` call is inconsequential to the assertion**

The test sets `mock_llmrails_instance.llm = new_llm` directly *before* calling `guardrails.update_llm(new_llm)`, so the subsequent `assert guardrails.llm is new_llm` would pass regardless of whether `update_llm` on the `Guardrails` facade actually delegates to `LLMRails`. To genuinely prove "no caching on the facade", the manual attribute assignment should come *after* the `update_llm` call and the test should verify `mock_llmrails_instance.update_llm.assert_called_once_with(new_llm)`.

Reviews (2): Last reviewed commit: "Add getters for Guardrails properties: l..." | Re-trigger Greptile

Comment on lines +95 to +108
if isinstance(self.rails_engine, IORails):
raise NotImplementedError("IORails doesn't support llm attribute access")

llmrails = cast(LLMRails, self.rails_engine)
return llmrails.llm

@property
def runtime(self) -> Runtime:
"""The Colang runtime backing the rails engine. Only supported for LLMRails."""
if isinstance(self.rails_engine, IORails):
raise NotImplementedError("IORails doesn't support runtime attribute access")

llmrails = cast(LLMRails, self.rails_engine)
return llmrails.runtime

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Implicit LLMRails assumption via negative-only check

Both properties guard against IORails but then unconditionally cast the result to LLMRails. If a third engine type is introduced in the future, the cast would silently succeed (no-op at runtime) and the attribute access would either return a wrong value or raise an AttributeError with a confusing message. A positive isinstance(self.rails_engine, LLMRails) check would make the assumption explicit and produce a clear error for unsupported engines.

Prompt To Fix With AI
This is a comment left during a code review.
Path: nemoguardrails/guardrails/guardrails.py
Line: 95-108

Comment:
**Implicit LLMRails assumption via negative-only check**

Both properties guard against `IORails` but then unconditionally `cast` the result to `LLMRails`. If a third engine type is introduced in the future, the `cast` would silently succeed (no-op at runtime) and the attribute access would either return a wrong value or raise an `AttributeError` with a confusing message. A positive `isinstance(self.rails_engine, LLMRails)` check would make the assumption explicit and produce a clear error for unsupported engines.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +795 to +812
assert guardrails.llm is new_llm

@patch("nemoguardrails.guardrails.guardrails.LLMRails")
def test_config_attribute_on_llmrails(self, mock_llmrails_class, _nemoguards_rails_config):
"""guardrails.config is the same RailsConfig instance passed in."""
mock_llmrails_class.return_value = MagicMock()
guardrails = Guardrails(config=_nemoguards_rails_config, use_iorails=False)
assert guardrails.config is _nemoguards_rails_config

@patch.object(IORails, "__init__", return_value=None)
def test_llm_property_raises_on_iorails(self, mock_iorails_init, _content_safety_rails_config):
"""guardrails.llm raises NotImplementedError when running on IORails."""
guardrails = Guardrails(config=_content_safety_rails_config, use_iorails=True)
assert isinstance(guardrails.rails_engine, IORails)

with pytest.raises(NotImplementedError, match="IORails doesn't support llm attribute access"):
_ = guardrails.llm

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 update_llm call is inconsequential to the assertion

The test sets mock_llmrails_instance.llm = new_llm directly before calling guardrails.update_llm(new_llm), so the subsequent assert guardrails.llm is new_llm would pass regardless of whether update_llm on the Guardrails facade actually delegates to LLMRails. To genuinely prove "no caching on the facade", the manual attribute assignment should come after the update_llm call and the test should verify mock_llmrails_instance.update_llm.assert_called_once_with(new_llm).

Prompt To Fix With AI
This is a comment left during a code review.
Path: tests/guardrails/test_guardrails.py
Line: 795-812

Comment:
**`update_llm` call is inconsequential to the assertion**

The test sets `mock_llmrails_instance.llm = new_llm` directly *before* calling `guardrails.update_llm(new_llm)`, so the subsequent `assert guardrails.llm is new_llm` would pass regardless of whether `update_llm` on the `Guardrails` facade actually delegates to `LLMRails`. To genuinely prove "no caching on the facade", the manual attribute assignment should come *after* the `update_llm` call and the test should verify `mock_llmrails_instance.update_llm.assert_called_once_with(new_llm)`.

How can I resolve this? If you propose a fix, please make it concise.

@tgasser-nv tgasser-nv closed this May 13, 2026
@tgasser-nv tgasser-nv deleted the fit/guardrails-llmrails-attributes branch May 13, 2026 21:30
@coderabbitai

coderabbitai Bot commented May 13, 2026

Copy link
Copy Markdown
Contributor

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: a0febc21-b4f0-4c69-a9c7-a72842b008cc

📥 Commits

Reviewing files that changed from the base of the PR and between e53ed06 and 4ac7bdf.

📒 Files selected for processing (2)
  • nemoguardrails/guardrails/guardrails.py
  • tests/guardrails/test_guardrails.py

📝 Walkthrough

Walkthrough

The PR adds two new read-only properties to the Guardrails class: llm (exposing the underlying LLMModel) and runtime (exposing the underlying colang Runtime). Both properties work when the instance uses LLMRails, but explicitly raise NotImplementedError when using IORails. Comprehensive tests verify delegation for both engine types and config consistency.

Changes

Guardrails property exposure

Layer / File(s) Summary
Property implementation with imports
nemoguardrails/guardrails/guardrails.py
Import Runtime and add llm and runtime properties that delegate to LLMRails internals or raise NotImplementedError for IORails.
Attribute access test coverage
tests/guardrails/test_guardrails.py
Test suite verifying llm and runtime delegation and updates on LLMRails, and confirming NotImplementedError is raised for both properties on IORails, while config remains accessible on both engines.

🎯 2 (Simple) | ⏱️ ~10 minutes

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fit/guardrails-llmrails-attributes

Comment @coderabbitai help to get the list of available commands and usage tips.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant