Skip to content

refactor: Guardrails public API#1933

Open
tgasser-nv wants to merge 9 commits into
developfrom
refactor/guardrails-public-api
Open

refactor: Guardrails public API#1933
tgasser-nv wants to merge 9 commits into
developfrom
refactor/guardrails-public-api

Conversation

@tgasser-nv
Copy link
Copy Markdown
Collaborator

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

Description

Create BaseGuardrails base class to define public API (with very loose typing for now). Under NEMO_GUARDRAILS_IORAILS_ENGINE=1, LLMRails is aliased to Guardrails, so every attribute LLMRails exposes has to be handled by Guardrails.

Several attributes were leaking that shouldn't have been public, and the facade was missing a few proxies. Internal attributes are now _underscore with deprecated @property aliases; real public knobs are first-class @property/@setter on both LLMRails and Guardrails (facade raises under IORails).

Follow-on PRs

  • Public methods have no typing, will update these with structured LLMModelResponse(Chunk) / GenerationResponse type return types.

Changes

  • New BaseGuardrails abstract base class (nemoguardrails/base_guardrails.py) — minimal contract (generate,
    generate_async, stream_async, config) shared by LLMRails, IORails, and Guardrails.
  • Reclassified as internal with deprecated @property aliases: kb, embedding_search_providers,
    default_embedding_{model,engine,params}, llm_generation_actions, explain_info.
  • Renamed LLMGenerationActions.passthrough_fn_passthrough_fn (storage now fully internal).
  • Promoted passthrough_fn to a first-class @property + @setter on LLMRails and Guardrails — replaces the previous attribute on LLMGenerationActions (which is a private object).
    • Fixes latent bug where the langchain integration's rails.llm_generation_actions.passthrough_fn = ... would have raised AttributeError when using Guardrails.
  • Added explain_info proxy on Guardrails (deprecated, points users to explain()); raises under IORails.
  • Added events_history_cache proxy on Guardrails (@property + @setter, raises under IORails). LLMRails.events_history_cache stays a plain public attribute — the server tightly couples to it for now.
  • Updated the in-tree langchain integration to use the new rails.passthrough_fn API.
  • Updated ~10 internal tests to use the new private names rather than the deprecated aliases (keeps test output free of our own deprecation warnings).

Related Issue(s)

NVBug 6102155
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.............................................................................................. [  5%]
....................................................................................................................... [  7%]
....................................................................................................................... [ 10%]
....................................................................................................................... [ 13%]
....................................................................................................................... [ 15%]
....................................................................................................................... [ 18%]
....................................................................................................................... [ 21%]
....................................................................................................................... [ 23%]
...............................s......ss...................sssssss..................................................... [ 26%]
..........................................................................s.......s.................................... [ 28%]
....................................................................................................................... [ 31%]
....................................................................................................................... [ 34%]
....s.................................................................................................................. [ 36%]
....................................................................................................................... [ 39%]
..................................................................................................ssssssss......ssssss. [ 42%]
.ss.s.............ssssssss............................................................................................. [ 44%]
..............................................................................................ss....................... [ 47%]
....s...............s.......sssss.......................................................s.............................. [ 49%]
....................................................................................................................... [ 52%]
........ss........ss...ss............................................s................................................. [ 55%]
....s............s..................................................................................................... [ 57%]
....................................................................................................................... [ 60%]
.....................................................................................................sssss......sssssss [ 63%]
sssssssssss..........sssss....................................................................................s........ [ 65%]
...ss...................................................sssssssss.ssssssssss.......................................s... [ 68%]
.................................................s....s........................................................ssssssss [ 70%]
.................sss...ss...ss.....sssssssssssss....................................................................... [ 73%]
.................................................................................s..................................... [ 76%]
.........................................................................s....................ssssssss.........ss...... [ 78%]
....................................................................................................................... [ 81%]
.......sssssss................................................................s........................................ [ 84%]
....................................................................................................................... [ 86%]
....................................................................................................................... [ 89%]
....................................................................................................................... [ 91%]
..........................................................................................................s............ [ 94%]
....................................................................................................................... [ 97%]
....................................................................................................................... [ 99%]
..........                                                                                                              [100%]
4368 passed, 164 skipped in 119.96s (0:01:59)

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-26 14:54:49 INFO: Registered model engine: type=main, model=meta/llama-3.3-70b-instruct, base_url=https://integrate.api.nvidia.com
2026-05-26 14:54:49 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-26 14:54:49 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-26 14:54:49 INFO: Registered API engine: name=jailbreak_detection, url=https://ai.api.nvidia.com/v1/security/nvidia/nemoguard-jailbreak-detect
2026-05-26 14:54:49 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-26 14:54:55 INFO: [5e5fec1bfb3f8a49] generate_async called
2026-05-26 14:54:55 INFO: [5e5fec1bfb3f8a49] Running input rails
2026-05-26 14:54:55 INFO: [5e5fec1bfb3f8a49] HTTP POST https://integrate.api.nvidia.com/v1/chat/completions model='nvidia/llama-3.1-nemoguard-8b-content-safety'
2026-05-26 14:54:56 INFO: [5e5fec1bfb3f8a49] HTTP POST https://integrate.api.nvidia.com/v1/chat/completions model='nvidia/llama-3.1-nemoguard-8b-topic-control'
2026-05-26 14:54:56 INFO: [5e5fec1bfb3f8a49] HTTP POST https://ai.api.nvidia.com/v1/security/nvidia/nemoguard-jailbreak-detect
2026-05-26 14:54:57 INFO: [5e5fec1bfb3f8a49] Calling main LLM
2026-05-26 14:54:57 INFO: [5e5fec1bfb3f8a49] HTTP POST https://integrate.api.nvidia.com/v1/chat/completions model='meta/llama-3.3-70b-instruct'
2026-05-26 14:55:03 INFO: [5e5fec1bfb3f8a49] Running output rails
2026-05-26 14:55:03 INFO: [5e5fec1bfb3f8a49] HTTP POST https://integrate.api.nvidia.com/v1/chat/completions model='nvidia/llama-3.1-nemoguard-8b-content-safety'
2026-05-26 14:55:04 INFO: [5e5fec1bfb3f8a49] generate_async completed time=8985.4ms
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-26 14:55:10 INFO: [0b1c1f36ee446dd6] generate_async called
2026-05-26 14:55:10 INFO: [0b1c1f36ee446dd6] Running input rails
2026-05-26 14:55:10 INFO: [0b1c1f36ee446dd6] HTTP POST https://integrate.api.nvidia.com/v1/chat/completions model='nvidia/llama-3.1-nemoguard-8b-content-safety'
2026-05-26 14:55:11 INFO: [0b1c1f36ee446dd6] Input flow content safety check input $model=content_safety blocked
2026-05-26 14:55:11 INFO: [0b1c1f36ee446dd6] Input blocked: Safety categories: Violence, Criminal Planning/Confessions
2026-05-26 14:55:11 INFO: [0b1c1f36ee446dd6] generate_async completed time=732.3ms
I'm sorry, I can't respond to that.

Integration test with Chat (LLMRails)

$ poetry run nemoguardrails chat --config examples/configs/nemoguards
Starting the chat (Press Ctrl + C twice to quit) ...

> Hello!
Hello. It's lovely to meet you and have the opportunity to chat with you. I hope you're having a fantastic day so far. Is there
something on your mind that you'd like to talk about, or perhaps you're looking for information on a specific topic? I'm all
ears and here to help in any way I can. If you're unsure where to start, we could always begin with some light conversation and
see where it takes us. I can tell you about my capabilities, or we could discuss current events, hobbies, or anything else that
piques your interest. What sounds appealing to you?

> 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.

@codecov
Copy link
Copy Markdown

codecov Bot commented May 26, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@tgasser-nv tgasser-nv marked this pull request as ready for review May 26, 2026 20:43
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 26, 2026

Greptile Summary

This PR refactors the NeMo Guardrails public API by introducing a BaseGuardrails abstract base class, reclassifying several previously public LLMRails attributes as private (_underscore) with deprecated @property aliases, and adding explicit first-class properties to the Guardrails facade for passthrough_fn, explain_info, and events_history_cache.

  • BaseGuardrails ABC establishes a minimal contract (generate, generate_async, stream_async, config) shared by LLMRails, IORails, and Guardrails.
  • Internal attributes (kb, embedding_search_providers, default_embedding_*, llm_generation_actions, explain_info) are now private with deprecated getter/setter aliases on LLMRails; passthrough_fn is promoted to a first-class property on both LLMRails and Guardrails, fixing the latent AttributeError in the LangChain integration.
  • The Guardrails facade gains proxies for explain_info (deprecated), passthrough_fn, and events_history_cache, plus NotImplementedError guards for IORails-incompatible paths.

Confidence Score: 5/5

Safe to merge; no functional regressions found in the API surface, deprecation wrappers, or delegation chains.

The refactoring correctly privatises internal attributes, adds deprecated aliases with proper stacklevel and message text, and wires up the Guardrails facade proxies consistently. All abstract methods are satisfied by the concrete subclasses (confirmed by 4368 passing tests). The two observations raised are documentation gaps and a missing-proxy gap in the Guardrails facade; neither breaks existing callers because the facade never exposed those attributes before this PR either.

No files require special attention for correctness; guardrails/guardrails.py could gain deprecated proxy properties for kb, embedding_search_providers, default_embedding_*, and llm_generation_actions as a follow-up.

Important Files Changed

Filename Overview
nemoguardrails/base_guardrails.py New ABC defining the minimal shared contract; correctly uses @AbstractMethod for generate, generate_async, and stream_async; config annotation is informational only (not ABC-enforced), which the docstring correctly documents.
nemoguardrails/rails/llm/llmrails.py Reclassifies kb, embedding_search_providers, default_embedding_, explain_info, and llm_generation_actions to private with deprecated getters (and setters for the embedding_ and explain_info attributes); adds first-class passthrough_fn property/setter; all internal usages correctly updated to use private names.
nemoguardrails/guardrails/guardrails.py Adds explain_info (deprecated), passthrough_fn, and events_history_cache proxies to the Guardrails facade; IORails guard consistently raised before any side effects; delegation to LLMRails private attributes is direct (avoids double-warning).
nemoguardrails/guardrails/iorails.py Trivial change: IORails now inherits from BaseGuardrails; no logic changes.
nemoguardrails/integrations/langchain/runnable_rails.py Updated to use the new first-class rails.passthrough_fn API instead of rails.llm_generation_actions.passthrough_fn.
tests/guardrails/test_public_api_deprecations.py New comprehensive test suite covering all deprecated aliases (read + write), passthrough_fn first-class property, and Guardrails facade proxies; uses LLMRails.new and patched init correctly to isolate descriptor behavior.

Class Diagram

%%{init: {'theme': 'neutral'}}%%
classDiagram
    class BaseGuardrails {
        <<abstract>>
        +RailsConfig config
        +generate()*
        +generate_async()*
        +stream_async()*
    }

    class LLMRails {
        +RailsConfig config
        +LLMModel llm
        +Runtime runtime
        +passthrough_fn [property/setter]
        -_kb
        -_embedding_search_providers
        -_explain_info
        -_llm_generation_actions
        +kb [deprecated getter]
        +embedding_search_providers [deprecated getter]
        +default_embedding_model [deprecated getter/setter]
        +explain_info [deprecated getter/setter]
        +llm_generation_actions [deprecated getter]
        +generate()
        +generate_async()
        +stream_async()
        +explain()
    }

    class IORails {
        +RailsConfig config
        +generate()
        +generate_async()
        +stream_async()
    }

    class Guardrails {
        +RailsConfig config
        -_rails_engine
        +rails_engine [property]
        +passthrough_fn [property/setter]
        +explain_info [deprecated getter/setter]
        +events_history_cache [property/setter]
        +generate()
        +generate_async()
        +stream_async()
        +explain()
    }

    class LLMGenerationActions {
        -_passthrough_fn
    }

    BaseGuardrails <|-- LLMRails
    BaseGuardrails <|-- IORails
    BaseGuardrails <|-- Guardrails
    Guardrails --> LLMRails : wraps (use_iorails=False)
    Guardrails --> IORails : wraps (use_iorails=True)
    LLMRails --> LLMGenerationActions : _llm_generation_actions
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/rails/llm/llmrails.py:143-150
**`kb` deprecation message lacks migration guidance**

The `embedding_search_providers` deprecation message helpfully says _"use `register_embedding_search_provider()` to add providers"_, while `kb` just says it is deprecated with no hint of what to do. A user who reads the warning and wants to stop using `rails.kb` has nowhere to turn. Consider adding a note such as _"There is no public read API for the knowledge base; interact with it through the action system."_ (or similar), so the migration path is clear before the attribute is removed.

### Issue 2 of 2
nemoguardrails/guardrails/guardrails.py:168-186
**`Guardrails` facade omits deprecated proxies for `kb`, `embedding_search_providers`, `default_embedding_*`, and `llm_generation_actions`**

The PR description states that _"every attribute `LLMRails` exposes has to be handled by `Guardrails`"_ (since `NEMO_GUARDRAILS_IORAILS_ENGINE=1` aliases `LLMRails` to `Guardrails`). Four deprecated-but-still-present properties on `LLMRails``kb`, `embedding_search_providers`, `default_embedding_model/engine/params`, and `llm_generation_actions` — are not proxied on the `Guardrails` facade. A caller using the env-var alias who accesses any of these will get a bare `AttributeError` instead of the deprecation warning they would get with a direct `LLMRails` instance. Adding deprecated getter (and setter) proxies to `Guardrails` for these attributes, guarded with the same `isinstance(self.rails_engine, IORails): raise NotImplementedError` pattern, would make the alias behaviour consistent with the PR's stated goal.

Reviews (4): Last reviewed commit: "Add comments to address greptile feedbac..." | Re-trigger Greptile

Comment thread nemoguardrails/rails/llm/llmrails.py
Comment thread nemoguardrails/guardrails/guardrails.py Outdated
Comment thread nemoguardrails/rails/llm/llmrails.py
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 26, 2026

📝 Walkthrough

Walkthrough

This PR refactors the NeMo Guardrails codebase to establish a common abstract base interface (BaseGuardrails) for all guardrails engines, privatize LLMRails internal attributes with deprecated property facades, and update the Guardrails facade to expose LLMRails-only features. All code referencing these attributes is updated accordingly, with comprehensive test coverage added.

Changes

Public API Refactoring for Guardrails Engine Interface

Layer / File(s) Summary
Abstract base interface and class hierarchy
nemoguardrails/base_guardrails.py, nemoguardrails/guardrails/guardrails.py, nemoguardrails/guardrails/iorails.py, nemoguardrails/rails/llm/llmrails.py
New BaseGuardrails abstract class defines the minimal engine interface (config, generate, generate_async, stream_async). Both Guardrails and IORails updated to inherit from it, establishing a common contract.
LLMRails privatization and deprecated property accessors
nemoguardrails/rails/llm/llmrails.py
LLMRails privatizes kb, llm_generation_actions, explain_info, and embedding fields as underscore-prefixed attributes. Introduces deprecated property accessors for backward compatibility; passthrough_fn and events_history_cache are first-class properties without warnings. All internal code paths updated to use private references.
Guardrails facade properties and passthrough function refactoring
nemoguardrails/guardrails/guardrails.py, nemoguardrails/actions/llm/generation.py, nemoguardrails/integrations/langchain/runnable_rails.py
Adds explain_info, passthrough_fn, and events_history_cache properties to Guardrails that delegate to LLMRails or raise NotImplementedError for IORails. Renames passthrough_fn storage in LLMGenerationActions to _passthrough_fn; RunnableRails refactored to register passthrough via rails.passthrough_fn property.
Comprehensive deprecation tests and integration verification
tests/guardrails/test_public_api_deprecations.py, tests/integrations/langchain/runnable_rails/test_runnable_rails.py, tests/test_actions_llm_embedding_lazy_init.py, tests/test_embeddings_*.py, tests/test_kb_openai_embeddings.py, tests/test_llmrails.py, tests/v2_x/test_*.py
New test module validates deprecated aliases emit DeprecationWarning, confirms Guardrails facade correctly proxies and guards LLMRails-only properties under both backends, verifies passthrough_fn behavior. All existing tests updated to access private internal attributes instead of public ones.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • NVIDIA-NeMo/Guardrails#1886: Both PRs modify the Guardrails facade in nemoguardrails/guardrails/guardrails.py to expose LLMRails-only surface while routing/guarding behavior differently under IORails (main PR via new passthrough/explain/events properties; retrieved PR via added LLMRails methods with NotImplementedError).

Suggested labels

enhancement

Suggested reviewers

  • cparisien
🚥 Pre-merge checks | ✅ 4 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.14% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Test Results For Major Changes ⚠️ Warning PR contains major API refactoring (new BaseGuardrails, attribute privatization) but lacks actual test results in PR description despite claim of inclusion. Add pytest output, pass/fail counts, and pre-commit results to PR description to document that changes pass tests without introducing regressions.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'refactor: Guardrails public API' directly and clearly summarizes the main change—refactoring the public API surface of Guardrails by introducing BaseGuardrails, moving attributes to private names with deprecation wrappers, and exposing new properties.
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.
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/guardrails-public-api

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

@tgasser-nv tgasser-nv requested review from Pouyanpi and cparisien May 26, 2026 22:26
@tgasser-nv tgasser-nv self-assigned this May 26, 2026
@Pouyanpi
Copy link
Copy Markdown
Collaborator

@tgasser-nv can we hold this until we get e2e tests and LLMRails refactor draft ready? I have some ideas about this.

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