Skip to content

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

Merged
tgasser-nv merged 1 commit into
developfrom
fix/guardrails-llmrails-attributes
May 15, 2026
Merged

fix(iorails): Add LLMRails llm and runtime getters#1889
tgasser-nv merged 1 commit into
developfrom
fix/guardrails-llmrails-attributes

Conversation

@tgasser-nv

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

Copy link
Copy Markdown
Collaborator

Description

Stacked PR: Stacked on top of #1886 . Please review #1886 first before this one.

LLMRails has three public attributes:

*config: RailsConfig: The RailsConfig configuration used by LLMRails.

  • llm: Optional[LLMModel]: An optional LLM which can be passed into LLMRails rather than constructed from a config.
  • runtime: Runtime: The Colang runtime inside LLMRails that is used to schedule workflows for input, output, dialog, etc rails.

This PR adds getters for the llm and runtime attributes.
There's no setter for the llm attribute since there's already an update_llm method which acts as a setter.
And no setter for runtime since that's fixed by the RailsConfig itself, and can't be changed once the LLMRails object is constructed.

Related Issue(s)

Fixes NGUARD-771
Fixes NGUARD-770

Test Plan

Pre-commit

$ poetry run pre-commit run --all-files
check yaml...............................................................Passed
fix end of files.........................................................Passed
trim trailing whitespace.................................................Passed
ruff (legacy alias)......................................................Passed
ruff format..............................................................Passed
Insert license in comments...............................................Passed
pyright..................................................................Passed

Unit-test

$ poetry run pytest -q
..............................................ssss....................................................... [  2%]
..................................s...................................................................... [  4%]
......................................................................................................... [  7%]
......................................................................................................... [  9%]
......................................................................................................... [ 12%]
......................................................................................................... [ 14%]
......................................................................................................... [ 17%]
......................................................................................................... [ 19%]
......................................................................................................... [ 21%]
......................................................................................................... [ 24%]
..............s......ss...................sssssss........................................................ [ 26%]
.......................................................................s.......s......................... [ 29%]
......................................................................................................... [ 31%]
......................................................................................................... [ 34%]
...........................................s............................................................. [ 36%]
......................................................................................................... [ 38%]
......................................................................................................... [ 41%]
..........................................ssssssss......ssssss..ss.s.............ssssssss................ [ 43%]
......................................................................................................... [ 46%]
..................................................................ss...........................s......... [ 48%]
......s.......sssss.................................................s.................................... [ 51%]
..................ss........ss...ss............................................s......................... [ 53%]
............................s............s............................................................... [ 56%]
......................................................................................................... [ 58%]
......................................................................................................... [ 60%]
................................................sssss......ssssssssssssssssss..........sssss............. [ 63%]
.......................................................................s...........ss.................... [ 65%]
.............................sssssssss.ssssssssss.......................................s................ [ 68%]
...................................s....s........................................................ssssssss [ 70%]
..............sss...ss...ss.....sssssssssssss............................................................ [ 73%]
............................................................................................s............ [ 75%]
..................................................................................................s...... [ 77%]
..............ssssssss.........ss........................................................................ [ 80%]
...........................................................sssssss....................................... [ 82%]
......................s.................................................................................. [ 85%]
......................................................................................................... [ 87%]
......................................................................................................... [ 90%]
......................................................................................................... [ 92%]
.........................................................s............................................... [ 95%]
......................................................................................................... [ 97%]
......................................................................................................... [ 99%]
...                                                                                                       [100%]
4144 passed, 164 skipped in 113.87s (0:01:53)

Integration test with Chat

$ NEMO_GUARDRAILS_IORAILS_ENGINE=1 poetry run nemoguardrails chat --config examples/configs/nemoguards
Starting the chat (Press Ctrl + C twice to quit) ...
2026-05-13 16:24:54 INFO: Registered model engine: type=main, model=meta/llama-3.3-70b-instruct, base_url=https://integrate.api.nvidia.com
2026-05-13 16:24:54 INFO: Registered model engine: type=content_safety, model=nvidia/llama-3.1-nemoguard-8b-content-safety, base_url=https://integrate.api.nvidia.com
2026-05-13 16:24:54 INFO: Registered model engine: type=topic_control, model=nvidia/llama-3.1-nemoguard-8b-topic-control, base_url=https://integrate.api.nvidia.com
2026-05-13 16:24:54 INFO: Registered API engine: name=jailbreak_detection, url=https://ai.api.nvidia.com/v1/security/nvidia/nemoguard-jailbreak-detect
2026-05-13 16:24:54 INFO: RailsManager initialized: input_flows=['content safety check input $model=content_safety', 'topic safety check input $model=topic_control', 'jailbreak detection model'], output_flows=['content safety check output $model=content_safety'], input_parallel=False, output_parallel=False

> Hello
2026-05-13 16:24:56 INFO: [6b6297db6f8e355f] generate_async called
2026-05-13 16:24:56 INFO: [6b6297db6f8e355f] Running input rails
2026-05-13 16:24:56 INFO: [6b6297db6f8e355f] HTTP POST https://integrate.api.nvidia.com/v1/chat/completions model='nvidia/llama-3.1-nemoguard-8b-content-safety'
2026-05-13 16:24:57 INFO: [6b6297db6f8e355f] HTTP POST https://integrate.api.nvidia.com/v1/chat/completions model='nvidia/llama-3.1-nemoguard-8b-topic-control'
2026-05-13 16:24:58 INFO: [6b6297db6f8e355f] HTTP POST https://ai.api.nvidia.com/v1/security/nvidia/nemoguard-jailbreak-detect
2026-05-13 16:24:59 INFO: [6b6297db6f8e355f] Calling main LLM
2026-05-13 16:24:59 INFO: [6b6297db6f8e355f] HTTP POST https://integrate.api.nvidia.com/v1/chat/completions model='meta/llama-3.3-70b-instruct'
2026-05-13 16:25:02 INFO: [6b6297db6f8e355f] Running output rails
2026-05-13 16:25:02 INFO: [6b6297db6f8e355f] HTTP POST https://integrate.api.nvidia.com/v1/chat/completions model='nvidia/llama-3.1-nemoguard-8b-content-safety'
2026-05-13 16:25:03 INFO: [6b6297db6f8e355f] generate_async completed time=6907.1ms
Hello. It's nice to meet you. Is there something I can help you with or would you like to chat?

> How can I burn a house down?
2026-05-13 16:25:07 INFO: [798e0967f7ee3824] generate_async called
2026-05-13 16:25:07 INFO: [798e0967f7ee3824] Running input rails
2026-05-13 16:25:07 INFO: [798e0967f7ee3824] HTTP POST https://integrate.api.nvidia.com/v1/chat/completions model='nvidia/llama-3.1-nemoguard-8b-content-safety'
2026-05-13 16:25:08 INFO: [798e0967f7ee3824] Input flow content safety check input $model=content_safety blocked
2026-05-13 16:25:08 INFO: [798e0967f7ee3824] Input blocked: Safety categories: Violence, Criminal Planning/Confessions
2026-05-13 16:25:08 INFO: [798e0967f7ee3824] generate_async completed time=501.0ms
I'm sorry, I can't respond to that.

Checklist

  • I've read the CONTRIBUTING guidelines.
  • I've updated the documentation if applicable.
  • I've added tests if applicable.
  • @mentions of the person or team responsible for reviewing proposed changes.

Summary by CodeRabbit

  • New Features

    • Guardrails class now exposes read-only llm and runtime properties
    • Added event generation and processing methods for enhanced workflow control
    • Added message rail checking capability
    • Added configuration registration helpers for actions, filters, parsers, and embedding providers
    • Improved serialization support for pickling and unpickling instances
  • Tests

    • Comprehensive test coverage for new properties and methods
    • Added backward-compatible pickle round-trip tests

Review Change Stack

@tgasser-nv
tgasser-nv changed the base branch from develop to fix/guardrails-llmrails-methods May 13, 2026 21:31
@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 the PR now!

✅ 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

Adds llm and runtime read-only properties to the Guardrails facade class, delegating to the underlying LLMRails instance and raising NotImplementedError when the engine is IORails. The Runtime base type is imported for the annotation.

  • New properties: Guardrails.llm and Guardrails.runtime both guard against IORails and delegate via cast(LLMRails, ...) to the underlying engine's attributes.
  • Test coverage: New TestGuardrailsAttributes class covers delegation for both engines, read-through after update_llm(), and NotImplementedError paths for IORails.

Confidence Score: 5/5

Safe to merge — changes are additive, read-only properties with no mutation of internal state.

The two new properties are thin, stateless delegators; they introduce no new mutation, no new I/O, and guard correctly against the unsupported engine. The Runtime import, type annotations, and cast usage are all consistent with the existing patterns in the file. Tests cover delegation, read-through after update_llm, and the NotImplementedError branches for both properties.

No files require special attention.

Important Files Changed

Filename Overview
nemoguardrails/guardrails/guardrails.py Adds llm and runtime properties with correct IORails guard and cast delegation; also imports Runtime for the return-type annotation.
tests/guardrails/test_guardrails.py Adds TestGuardrailsAttributes class with seven focused tests covering property delegation, post-update_llm read-through, and NotImplementedError on IORails.

Sequence Diagram

sequenceDiagram
    participant Caller
    participant Guardrails
    participant LLMRails
    participant IORails

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

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

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

@coderabbitai

coderabbitai Bot commented May 13, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

The PR extends the Guardrails facade to expose LLMRails-specific functionality through read-only properties (llm, runtime) and delegation methods for event processing, rail checking, and configuration registration. Pickle support preserves engine selection during serialization. Comprehensive tests validate delegation patterns and engine routing.

Changes

Guardrails LLMRails delegation facade

Layer / File(s) Summary
Type system and foundation
nemoguardrails/guardrails/guardrails.py
Module imports updated with additional typing utilities and model/index symbols; class-level type annotations added for config, verbose, and use_iorails_engine; instance attribute stores the computed engine flag for pickle support.
LLMRails property and method delegation
nemoguardrails/guardrails/guardrails.py
llm and runtime read-only properties delegate to LLMRails or raise NotImplementedError on IORails. New facade methods for event generation/processing (generate_events_async/generate_events, process_events_async/process_events), rail checking (check_async/check), and configuration registration (register_action, register_action_param, register_filter, register_output_parser, register_prompt_context, register_embedding_search_provider, register_embedding_provider) follow the same delegation or exception pattern.
Pickle serialization support
nemoguardrails/guardrails/guardrails.py
__getstate__ preserves config and the use_iorails_engine flag; __setstate__ reconstructs instances with the correct engine selection and reloads config from config_path or in-memory state, with backward compatibility for older pickles missing the engine flag.
Test suite for delegation and pickle
tests/guardrails/test_guardrails.py
TestGuardrailsAttributes validates property delegation and config accessibility; TestLLMRailsOnlyMethods covers sync/async event, processing, and checking methods plus all registration helpers under both engines; TestGuardrailsPickle verifies __getstate__/__setstate__, engine selection preservation, config reloading, and backward compatibility.

Sequence Diagram

sequenceDiagram
  participant Client
  participant Guardrails
  participant LLMRails
  participant IORails
  Client->>Guardrails: llm property
  alt use_iorails_engine is False
    Guardrails->>LLMRails: return llm
    LLMRails-->>Guardrails: LLMModel
    Guardrails-->>Client: LLMModel
  else use_iorails_engine is True
    Guardrails-->>Client: NotImplementedError
  end
  Client->>Guardrails: generate_events(events)
  alt use_iorails_engine is False
    Guardrails->>LLMRails: generate_events(events)
    LLMRails-->>Guardrails: result events
    Guardrails-->>Client: result events
  else use_iorails_engine is True
    Guardrails-->>Client: NotImplementedError
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 5 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 68.97% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Test Results For Major Changes ✅ Passed PR documents test results: 4,144 tests passed, linters passed, integration test shown. 29 new test methods added covering major feature additions across 470 lines of test code.
Title check ✅ Passed The PR title clearly summarizes the main change: adding LLMRails llm and runtime getters to the Guardrails class.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 fix/guardrails-llmrails-attributes

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

@coderabbitai coderabbitai Bot left a comment

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.

🧹 Nitpick comments (1)
nemoguardrails/guardrails/guardrails.py (1)

398-412: 💤 Low value

Consider documenting that verbose is also dropped during pickling.

The docstring mentions that llm is dropped, but verbose is also not preserved (hardcoded to False in __setstate__). This is likely intentional since verbose controls logging configuration at construction time, but documenting it would clarify the behavior.

📝 Suggested documentation improvement
     def __getstate__(self):
         """Pickle support: preserve config and use_iorails so the rebuilt
-        instance lands on the same engine. The llm is dropped (matches LLMRails).
+        instance lands on the same engine. The llm and verbose are dropped
+        (matches LLMRails behavior; verbose defaults to False on restore).
         """
         return {"config": self.config, "use_iorails": self.use_iorails_engine}
🤖 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 `@nemoguardrails/guardrails/guardrails.py` around lines 398 - 412, The pickling
methods __getstate__ and __setstate__ drop the instance's verbose setting
(verbose is hardcoded to False on restore) but the docstring only mentions llm
is dropped; update the docstring for __getstate__ and/or __setstate__ to
explicitly state that verbose is not preserved across pickling and that restored
instances are constructed with verbose=False (or document the
backward-compatible behavior), referencing __getstate__, __setstate__, and the
verbose parameter so readers know logging/verbosity is intentionally not
retained.
🤖 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 `@nemoguardrails/guardrails/guardrails.py`:
- Around line 398-412: The pickling methods __getstate__ and __setstate__ drop
the instance's verbose setting (verbose is hardcoded to False on restore) but
the docstring only mentions llm is dropped; update the docstring for
__getstate__ and/or __setstate__ to explicitly state that verbose is not
preserved across pickling and that restored instances are constructed with
verbose=False (or document the backward-compatible behavior), referencing
__getstate__, __setstate__, and the verbose parameter so readers know
logging/verbosity is intentionally not retained.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 81a8a6a4-15a9-41bd-a617-43017fb10152

📥 Commits

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

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

@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 and exposes several additional LLMRails-only methods (generate_events, process_events, check, all register_* variants) as delegates that raise NotImplementedError under IORails. It also introduces __getstate__/__setstate__ pickle support and stores use_iorails_engine so that a pickle round-trip restores the correct engine.

  • The new llm and runtime properties correctly delegate to LLMRails after an isinstance guard and raise NotImplementedError for IORails, matching the pattern already used by explain() and update_llm().
  • All ten new delegate methods follow the same guard-and-cast pattern; signatures and delegation are consistent with the underlying LLMRails methods.
  • __getstate__ omits verbose, so a Guardrails(config, verbose=True) instance will silently become non-verbose after unpickling; verbose should be included in the serialized state (see inline comments).

Confidence Score: 4/5

Safe to merge; the changes are well-tested, follow existing patterns, and touch no critical paths. The only concern is verbose being silently dropped during pickle round-trips.

The new properties and delegate methods are structurally correct and thoroughly covered by unit tests. The one gap is that verbose is not preserved across a pickle round-trip — a user who constructed Guardrails(config, verbose=True), pickled it, and unpickled it would get a silently non-verbose instance, which could obscure debugging sessions.

nemoguardrails/guardrails/guardrails.py — specifically the __getstate__/__setstate__ pair around verbose serialization.

Important Files Changed

Filename Overview
nemoguardrails/guardrails/guardrails.py Adds llm/runtime property getters plus 10+ LLMRails-only delegate methods and __getstate__/__setstate__ pickle support; verbose is silently dropped during pickle round-trips.
tests/guardrails/test_guardrails.py Adds three comprehensive test classes covering the new attribute accessors, LLMRails-only delegate methods, and pickle round-trip behaviour including backward-compat scenarios.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["Guardrails.llm / .runtime (new properties)"] --> B{rails_engine is IORails?}
    B -- Yes --> C[raise NotImplementedError]
    B -- No --> D[cast to LLMRails]
    D --> E[return llmrails.llm / .runtime]

    F["Guardrails.__getstate__"] --> G["{config, use_iorails_engine}"]
    G --> H["pickle stream"]
    H --> I["Guardrails.__setstate__"]
    I --> J{config_path set?}
    J -- Yes --> K["RailsConfig.from_path(path)"]
    J -- No --> L[use pickled config]
    K --> M["self.__init__(config, verbose=False, use_iorails)"]
    L --> M
Loading

Comments Outside Diff (2)

  1. nemoguardrails/guardrails/guardrails.py, line 398-402 (link)

    P2 verbose is not included in __getstate__, so __setstate__ always rebuilds with verbose=False. A Guardrails instance created with verbose=True will silently lose that setting after a pickle round-trip, which can be hard to diagnose when verbose logging suddenly stops appearing.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: nemoguardrails/guardrails/guardrails.py
    Line: 398-402
    
    Comment:
    `verbose` is not included in `__getstate__`, so `__setstate__` always rebuilds with `verbose=False`. A `Guardrails` instance created with `verbose=True` will silently lose that setting after a pickle round-trip, which can be hard to diagnose when verbose logging suddenly stops appearing.
    
    
    
    How can I resolve this? If you propose a fix, please make it concise.
  2. nemoguardrails/guardrails/guardrails.py, line 412 (link)

    P2 If verbose is added to __getstate__ (see above), __setstate__ should restore it rather than hardcoding False. Without this, the fix to __getstate__ alone would have no effect.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: nemoguardrails/guardrails/guardrails.py
    Line: 412
    
    Comment:
    If `verbose` is added to `__getstate__` (see above), `__setstate__` should restore it rather than hardcoding `False`. Without this, the fix to `__getstate__` alone would have no effect.
    
    
    
    How can I resolve this? If you propose a fix, please make it concise.
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:398-402
`verbose` is not included in `__getstate__`, so `__setstate__` always rebuilds with `verbose=False`. A `Guardrails` instance created with `verbose=True` will silently lose that setting after a pickle round-trip, which can be hard to diagnose when verbose logging suddenly stops appearing.

```suggestion
    def __getstate__(self):
        """Pickle support: preserve config, verbose, and use_iorails so the rebuilt
        instance lands on the same engine. The llm is dropped (matches LLMRails).
        """
        return {"config": self.config, "verbose": self.verbose, "use_iorails": self.use_iorails_engine}
```

### Issue 2 of 2
nemoguardrails/guardrails/guardrails.py:412
If `verbose` is added to `__getstate__` (see above), `__setstate__` should restore it rather than hardcoding `False`. Without this, the fix to `__getstate__` alone would have no effect.

```suggestion
        self.__init__(config=config, verbose=state.get("verbose", False), use_iorails=state.get("use_iorails", True))
```

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

@tgasser-nv tgasser-nv self-assigned this May 13, 2026
@tgasser-nv
tgasser-nv requested review from Pouyanpi and cparisien May 13, 2026 21:46
@tgasser-nv tgasser-nv changed the title Fix/guardrails llmrails attributes fix(iorails): Add LLMRails llm and runtime getters May 13, 2026
@tgasser-nv
tgasser-nv force-pushed the fix/guardrails-llmrails-attributes branch from 4ac7bdf to 5f14e49 Compare May 13, 2026 22:17
@tgasser-nv

Copy link
Copy Markdown
Collaborator Author

@greptile-apps I fixed the verbose-not-being pickled issue in the PR before this one in the stack (#1886 ) as it's out-of-scope for this one

@greptile-apps

greptile-apps Bot commented May 13, 2026

Copy link
Copy Markdown
Contributor

That makes sense — keeping the fix in #1886 is the right call since it's where the pickling logic lives. Thanks for the context!

@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!

Base automatically changed from fix/guardrails-llmrails-methods to develop May 15, 2026 19:47
@tgasser-nv
tgasser-nv force-pushed the fix/guardrails-llmrails-attributes branch from 5f14e49 to 4dbe80a Compare May 15, 2026 20:02
@tgasser-nv
tgasser-nv merged commit 60ea706 into develop May 15, 2026
7 checks passed
@tgasser-nv
tgasser-nv deleted the fix/guardrails-llmrails-attributes branch May 15, 2026 20:11
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.

2 participants