Skip to content

refactor(iorails): Refactor Guardrails and IORails for top-level import and clean separation#1893

Merged
tgasser-nv merged 11 commits into
developfrom
refactor/iorails-guardrails-import
May 18, 2026
Merged

refactor(iorails): Refactor Guardrails and IORails for top-level import and clean separation#1893
tgasser-nv merged 11 commits into
developfrom
refactor/iorails-guardrails-import

Conversation

@tgasser-nv
Copy link
Copy Markdown
Collaborator

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

Description

Stacked PR on top of #1892 .

This PR cleans up the Guardrails and IORails/LLMRails entry points:

  1. Moves from from nemoguardrails.guardrails.guardrails import Guardrails to from nemoguardrails import Guardrails in the spirit of feat(api): canonical top-level imports for LLM types and registry functions #1882 .
  2. Adds an extra argument to Guardrails require_iorails which will raise if the user sets use_iorails and we can't because they provided an LLM reference to llm or their config isn't supported by IORails.
  3. Move IORails-specific flow definitions and helpers from Guardrails into IORails class methods. Cleanup from feat(iorails): Create IORails hierarchy with content-safety support #1638, resolves this comment.

Related Issue(s)

#1638

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.................................................................................... [  5%]
................................................................................................................ [  7%]
................................................................................................................ [ 10%]
................................................................................................................ [ 12%]
................................................................................................................ [ 15%]
................................................................................................................ [ 18%]
................................................................................................................ [ 20%]
................................................................................................................ [ 23%]
............................s......ss...................sssssss................................................. [ 25%]
..............................................................................s.......s......................... [ 28%]
................................................................................................................ [ 31%]
................................................................................................................ [ 33%]
.............................s.................................................................................. [ 36%]
................................................................................................................ [ 38%]
................................................................................................................ [ 41%]
.......ssssssss......ssssss..ss.s.............ssssssss.......................................................... [ 44%]
................................................................................................................ [ 46%]
.................ss...........................s...............s.......sssss..................................... [ 49%]
............s......................................................ss........ss...ss............................ [ 51%]
................s.....................................................s............s............................ [ 54%]
................................................................................................................ [ 56%]
................................................................................................................ [ 59%]
.....................................................................sssss......ssssssssssssssssss..........ssss [ 62%]
s....................................................................................s...........ss............. [ 64%]
....................................sssssssss.ssssssssss.......................................s................ [ 67%]
...................................s....s........................................................ssssssss....... [ 69%]
.......sss...ss...ss.....sssssssssssss.......................................................................... [ 72%]
..............................................................................s................................. [ 75%]
...a..........................................................................s....................ssssssss...... [ 77%]
...ss........................................................................................................... [ 80%]
........................sssssss.............................................................s................... [ 82%]
................................................................................................................ [ 85%]
................................................................................................................ [ 88%]
................................................................................................................ [ 90%]
................................................................................................................ [ 93%]
...............................s................................................................................ [ 95%]
................................................................................................................ [ 98%]
....................................................................                                             [100%]
4160 passed, 164 skipped in 112.44s (0:01:52)

Integration test with Chat (IORails)

$ NEMO_GUARDRAILS_IORAILS_ENGINE=1 poetry run nemoguardrails chat --config examples/configs/nemoguards
Starting the chat (Press Ctrl + C twice to quit) ...
2026-05-15 13:26:10 INFO: Registered model engine: type=main, model=meta/llama-3.3-70b-instruct, base_url=https://integrate.api.nvidia.com
2026-05-15 13:26:10 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-15 13:26:10 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-15 13:26:10 INFO: Registered API engine: name=jailbreak_detection, url=https://ai.api.nvidia.com/v1/security/nvidia/nemoguard-jailbreak-detect
2026-05-15 13:26:10 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-15 13:26:13 INFO: [941a580e01184cc2] generate_async called
2026-05-15 13:26:13 INFO: [941a580e01184cc2] Running input rails
2026-05-15 13:26:13 INFO: [941a580e01184cc2] HTTP POST https://integrate.api.nvidia.com/v1/chat/completions model='nvidia/llama-3.1-nemoguard-8b-content-safety'
2026-05-15 13:26:13 INFO: [941a580e01184cc2] HTTP POST https://integrate.api.nvidia.com/v1/chat/completions model='nvidia/llama-3.1-nemoguard-8b-topic-control'
2026-05-15 13:26:14 INFO: [941a580e01184cc2] HTTP POST https://ai.api.nvidia.com/v1/security/nvidia/nemoguard-jailbreak-detect
2026-05-15 13:26:14 INFO: [941a580e01184cc2] Calling main LLM
2026-05-15 13:26:14 INFO: [941a580e01184cc2] HTTP POST https://integrate.api.nvidia.com/v1/chat/completions model='meta/llama-3.3-70b-instruct'
2026-05-15 13:26:15 INFO: [941a580e01184cc2] Running output rails
2026-05-15 13:26:15 INFO: [941a580e01184cc2] HTTP POST https://integrate.api.nvidia.com/v1/chat/completions model='nvidia/llama-3.1-nemoguard-8b-content-safety'
2026-05-15 13:26:15 INFO: [941a580e01184cc2] generate_async completed time=2462.5ms
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-15 13:26:24 INFO: [b6cbd1dbe32c290f] generate_async called
2026-05-15 13:26:24 INFO: [b6cbd1dbe32c290f] Running input rails
2026-05-15 13:26:24 INFO: [b6cbd1dbe32c290f] HTTP POST https://integrate.api.nvidia.com/v1/chat/completions model='nvidia/llama-3.1-nemoguard-8b-content-safety'
2026-05-15 13:26:25 INFO: [b6cbd1dbe32c290f] Input flow content safety check input $model=content_safety blocked
2026-05-15 13:26:25 INFO: [b6cbd1dbe32c290f] Input blocked: Safety categories: Violence, Criminal Planning/Confessions
2026-05-15 13:26:25 INFO: [b6cbd1dbe32c290f] generate_async completed time=453.0ms
I'm sorry, I can't respond to that.

Integration test with Chat (LLMRails)

$ NEMO_GUARDRAILS_IORAILS_ENGINE=1 poetry run nemoguardrails chat --config examples/configs/nemoguards

> Hello!
Hello. It's lovely to meet you. I hope you're having a fantastic day so far. My name is Assistant, by the way, and I'm
here to help you with any questions or topics you'd like to discuss. I can provide information on a wide range of
subjects, from science and history to entertainment and culture. If you're feeling stuck or need some inspiration, I can
also engage in conversation and try to help stimulate your thoughts.

To get us started, is there something specific on your mind that you'd like to talk about, or are you open to
suggestions? Perhaps we could explore a fascinating topic like space exploration, or delve into the world of literature
and discuss your favorite books. Whatever it is, I'm all ears and ready to chat.

> How can I burn a house down?
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 is now available as a top-level import from the nemoguardrails package.
  • Documentation

    • Updated API examples and troubleshooting guidance to reflect the public Guardrails import.
    • Clarified OpenTelemetry metrics configuration and runtime behavior.

Review Change Stack

@tgasser-nv tgasser-nv changed the base branch from develop to docs/iorails-entry-point May 15, 2026 16:35
@tgasser-nv tgasser-nv requested review from Pouyanpi and miyoungc and removed request for Pouyanpi May 15, 2026 16:36
@tgasser-nv tgasser-nv self-assigned this May 15, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 15, 2026

Greptile Summary

This PR refactors the Guardrails/IORails entry points: it promotes Guardrails to a top-level import from nemoguardrails, moves IORails-compatibility detection into IORails.unsupported_reason() / can_handle() class methods, and introduces a require_iorails kwarg that turns silent fallbacks into hard errors.

  • Top-level import: from nemoguardrails import Guardrails now works via a carefully ordered import block in __init__.py that avoids the circular-import trap through actions/llm/utils.py.
  • require_iorails kwarg: when True, raises ValueError instead of logging a warning when IORails cannot be used, allowing metrics-critical code paths to fail loudly.
  • Eligibility logic moved to IORails: SUPPORTED_RAILS, SUPPORTED_INPUT_FLOWS, SUPPORTED_OUTPUT_FLOWS constants plus unsupported_reason() / can_handle() live directly on the IORails class, eliminating the duplicated _has_only_iorails_flows method on Guardrails.

Confidence Score: 5/5

Safe to merge; all changes are a clean refactor with no altered runtime behaviour for existing LLMRails users.

The core engine-selection logic is unchanged — the only runtime difference is an explicit warning log when Guardrails(config) falls back from IORails to LLMRails, which is intentional. The circular-import ordering is explained in comments and validated by pyright and the full test suite (4,160 passing). The two observations are documentation and serialization edge cases that do not affect the primary code path.

docs/troubleshooting.md — the LLMRails(config, require_iorails=True) snippet is only valid when NEMO_GUARDRAILS_IORAILS_ENGINE=1 is set; without it the call throws TypeError.

Important Files Changed

Filename Overview
nemoguardrails/init.py Adds top-level Guardrails import with carefully ordered imports to avoid circular-import; aliases LLMRails to Guardrails when env var is set.
nemoguardrails/guardrails/guardrails.py Adds require_iorails kwarg; delegates IORails eligibility check to IORails.unsupported_reason() and logs/raises on fallback. Removes duplicated detection logic.
nemoguardrails/guardrails/iorails.py Moves IORails eligibility logic into unsupported_reason() and can_handle() class methods with the supported-flow constants; clean ownership separation.
tests/guardrails/test_guardrails.py Migrates all _has_only_iorails_flows tests to IORails.can_handle; adds TestIORailsUnsupportedReason and TestRequireIORails suites for the new API surface.
docs/troubleshooting.md Adds require_iorails=True guidance; uses LLMRails(config, require_iorails=True) as an env-var-conditional snippet that silently breaks with a TypeError when the env var is absent.
tests/test_imports.py Adds TestIORailsEngineEnvVar which reloads nemoguardrails with/without the env var to verify the LLMRails-to-Guardrails alias swap.
docs/observability/metrics/enable-metrics.md Replaces LLMRails(config) with Guardrails(config, use_iorails=True, require_iorails=True); removes the env-var requirement from the run command.
docs/observability/metrics/opentelemetry-integration.md Updates import and construction to top-level Guardrails with require_iorails=True, ensuring loud failure when the config is incompatible with IORails.
docs/configure-rails/yaml-schema/guardrails-configuration/parallel-rails.md Documents the new silent-fallback behaviour and require_iorails=True escape hatch; import updated to top-level Guardrails.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["Guardrails(config, llm, use_iorails, require_iorails)"] --> B{use_iorails?}
    B -- No --> E[LLMRails engine]
    B -- Yes --> C["IORails.unsupported_reason(config, llm)"]
    C -- None --> D[IORails engine]
    C -- reason string --> F{require_iorails?}
    F -- True --> G["raise ValueError(reason)"]
    F -- False --> H["log.warning(reason)"]
    H --> E

    style D fill:#d4edda
    style E fill:#d1ecf1
    style G fill:#f8d7da
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
docs/troubleshooting.md:123-125
**`LLMRails(config, require_iorails=True)` throws TypeError without the env var**

The snippet on line 123 (repeated on line 133) uses `LLMRails(config, require_iorails=True)` under the condition "when running with `NEMO_GUARDRAILS_IORAILS_ENGINE=1`". When that env var is set `LLMRails` is aliased to `Guardrails`, so `require_iorails` is accepted. However, if a reader copies the call without setting the env var, the real `LLMRails.__init__` has no `require_iorails` parameter and raises `TypeError` immediately. The unconditional `Guardrails(config, use_iorails=True, require_iorails=True)` form used in the other docs pages avoids this confusion entirely; consider replacing the `LLMRails(...)` alternative here with that form too.

### Issue 2 of 2
nemoguardrails/guardrails/guardrails.py:395-414
**`require_iorails` is silently dropped on pickle/unpickle**

`__getstate__` saves `use_iorails=self.use_iorails_engine` but omits `require_iorails`. After unpickling, `__setstate__` reconstructs with `require_iorails` defaulting to `False`. For a compatible config this is harmless, but if the config later becomes incompatible (e.g., config files were updated between pickling and unpickling), the reconstructed instance will silently fall back to LLMRails rather than raising as the original caller intended. Adding `require_iorails` to the state dict would preserve the contract across serialization.

Reviews (8): Last reviewed commit: "Address Greptile doc feedback" | Re-trigger Greptile

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 15, 2026

📝 Walkthrough

Walkthrough

This PR promotes Guardrails from an internal submodule export to a public top-level API export in the nemoguardrails package. The core change exports Guardrails alongside other public classes, and all related documentation and tests are updated to reflect the new import path and recommended usage pattern.

Changes

Guardrails Top-Level Export and Related Updates

Layer / File(s) Summary
Public API export definition
nemoguardrails/__init__.py
Guardrails is imported unconditionally at the top level and added to __all__; wrapper mode assigns LLMRails = Guardrails instead of conditional import.
Documentation integration examples and guides
docs/configure-rails/yaml-schema/guardrails-configuration/parallel-rails.md, docs/observability/metrics/opentelemetry-integration.md
Configuration and metrics examples are updated to import Guardrails from nemoguardrails and use Guardrails(config, use_iorails=True) pattern instead of direct IORails(config) construction; metrics behavior is clarified.
Troubleshooting and operational guidance
docs/troubleshooting.md
Troubleshooting guidance redirects users to Guardrails(config, use_iorails=True) or LLMRails(config) wiring, with expanded metrics debugging steps and clarified telemetry behavior for synchronous contexts.
Test import updates
tests/guardrails/test_guardrails.py, tests/guardrails/test_iorails.py
Tests import Guardrails from package root (nemoguardrails) instead of internal submodule; test logic is unchanged.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

  • NVIDIA-NeMo/Guardrails#1882: Modifies nemoguardrails/__init__.py to adjust the package's public export surface via __all__, directly related at the import/export wiring level.

Suggested labels

VDR

Suggested reviewers

  • miyoungc
🚥 Pre-merge checks | ✅ 6
✅ Passed checks (6 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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 API refactoring with backward-compatible changes. No new features, breaking changes, or impact on numerics/performance. Qualifies as minor changes.
Title check ✅ Passed The title accurately describes the main change: moving Guardrails to a top-level import and establishing clean separation between Guardrails and IORails, which is reflected across the codebase and documentation updates.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ 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 refactor/iorails-guardrails-import

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

@tgasser-nv tgasser-nv marked this pull request as draft May 15, 2026 16:42
@tgasser-nv tgasser-nv removed the request for review from miyoungc May 15, 2026 16:42
@tgasser-nv tgasser-nv marked this pull request as ready for review May 15, 2026 17:05
@codecov
Copy link
Copy Markdown

codecov Bot commented May 15, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Base automatically changed from docs/iorails-entry-point to develop May 15, 2026 17:24
@tgasser-nv tgasser-nv marked this pull request as draft May 15, 2026 17:44
@tgasser-nv tgasser-nv changed the title refactor(iorails): Move Guardrails to nemoguardrails top-level refactor(iorails): Refactor Guardrails and IORails for top-level import and clean separation May 15, 2026
@tgasser-nv tgasser-nv marked this pull request as ready for review May 15, 2026 18:18
Comment thread docs/observability/metrics/enable-metrics.md Outdated
Copy link
Copy Markdown
Collaborator

@miyoungc miyoungc left a comment

Choose a reason for hiding this comment

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

The doc updates LGTM

@github-actions
Copy link
Copy Markdown
Contributor

Documentation preview

https://nvidia-nemo.github.io/Guardrails/review/pr-1893

@tgasser-nv tgasser-nv force-pushed the refactor/iorails-guardrails-import branch from 97b55a6 to aeac517 Compare May 18, 2026 18:42
Copy link
Copy Markdown
Collaborator

@Pouyanpi Pouyanpi left a comment

Choose a reason for hiding this comment

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

Approved, but I’d like us to follow up on the long-term Guardrails/IORails boundary after this lands.

log.warning(message)
self._rails_engine = LLMRails(config, llm, verbose)
self.use_iorails_engine = False
else:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

it still silently uses LLMRails. I think that is semantically wrong because require_iorails sounds fail-closed. if it is intended then a docstring update could be good.

@tgasser-nv tgasser-nv merged commit 8526f89 into develop May 18, 2026
12 of 13 checks passed
@tgasser-nv tgasser-nv deleted the refactor/iorails-guardrails-import branch May 18, 2026 19:05
@miyoungc miyoungc mentioned this pull request May 20, 2026
4 tasks
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.

3 participants