Skip to content

feat: add local backend for built-in nemo guardrails#197

Open
afourniernv wants to merge 7 commits into
NVIDIA:mainfrom
afourniernv:afournier/relay-149-implement-local-python-backed-backend-for-built-in-nemo
Open

feat: add local backend for built-in nemo guardrails#197
afourniernv wants to merge 7 commits into
NVIDIA:mainfrom
afourniernv:afournier/relay-149-implement-local-python-backed-backend-for-built-in-nemo

Conversation

@afourniernv
Copy link
Copy Markdown
Contributor

@afourniernv afourniernv commented Jun 1, 2026

Overview

  • I confirm this contribution is my own work, or I have the right to submit it under this project's license.
  • I searched existing issues and open pull requests, and this does not duplicate existing work.

Details

This PR adds the built-in Python-backed local backend for the first-party nemo_guardrails plugin and documents the shipped remote/local mode boundaries.

What changed:

  • add a narrow core local-backend provider seam for mode = "local"
  • install the local backend provider from the Python binding at _native init time
  • implement the in-process nemoguardrails backend in python/nemo_relay/_guardrails_local.py
  • support local managed LLM input/output, local managed tool_input/tool_output, and local streaming output through Guardrails-native stream_first = true semantics
  • reject unsupported local surfaces explicitly, including request_defaults and rails.output.streaming.stream_first = false
  • add focused core/Python coverage tests and update the built-in Guardrails docs for remote vs local mode

Important local-mode boundaries in this PR:

  • local mode is Python-runtime-backed and unavailable in runtimes that do not install the provider
  • local streamed output uses Guardrails-native stream_first = true semantics, so a later output block can happen after some chunks were already emitted
  • local mode does not yet support request_defaults
  • local mode does not yet support rails.output.streaming.stream_first = false

Where should the reviewer start?

Start with:

  • python/nemo_relay/_guardrails_local.py
  • crates/core/src/plugins/nemo_guardrails/local.rs
  • crates/core/src/plugins/nemo_guardrails/component.rs
  • docs/nemo-guardrails-plugin/configuration.mdx

Related Issues: (use one of the action keywords Closes / Fixes / Resolves / Relates to)

Summary by CodeRabbit

  • New Features
    • Added a local NeMo Guardrails backend for in-process Python-based enforcement (LLM and tool flows), including streaming support with stream_first semantics and runtime-installable local providers.
  • Bug Fixes
    • Local mode now rejects request_defaults with a clear validation diagnostic; initialization surfaces when the local backend is unavailable.
  • Documentation
    • Docs updated with local vs remote capability matrix, examples, and local-mode constraints.
  • Tests
    • Expanded unit and end-to-end tests covering local registration, enforcement, streaming behavior, and rollback-on-error for plugin registration.

Signed-off-by: Alex Fournier <afournier@nvidia.com>
Signed-off-by: Alex Fournier <afournier@nvidia.com>
@copy-pr-bot
Copy link
Copy Markdown

copy-pr-bot Bot commented Jun 1, 2026

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 1, 2026

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 2208e1ab-d5c8-498e-a36a-9d4eb041ff01

📥 Commits

Reviewing files that changed from the base of the PR and between f8dead5 and 67fd1b9.

📒 Files selected for processing (1)
  • crates/cli/tests/coverage/plugins_tests.rs
📜 Recent review details
🧰 Additional context used
📓 Path-based instructions (9)
**/*.rs

📄 CodeRabbit inference engine (.agents/skills/add-binding-feature/SKILL.md)

Use snake_case naming convention for Rust identifiers (e.g., nemo_relay_tool_call)

**/*.rs: Any Rust change must run just test-rust
Any Rust change must run cargo fmt --all
Any Rust change must run cargo clippy --workspace --all-targets -- -D warnings

**/*.rs: Run cargo fmt --all for all FFI work since it is Rust work
Run just test-rust to validate FFI changes
Run cargo clippy --workspace --all-targets -- -D warnings to enforce strict linting on FFI work

When Rust files changed as part of Go work, also run cargo fmt --all, just test-rust, and cargo clippy --workspace --all-targets -- -D warnings

**/*.rs: Run cargo fmt --all when Rust files are changed as part of Node work
Run cargo clippy --workspace --all-targets -- -D warnings when Rust files are changed as part of Node work
Run just test-rust when Rust files are changed as part of Node work

**/*.rs: Run cargo fmt --all to format all Rust code
Run cargo clippy --workspace --all-targets -- -D warnings to enforce all clippy lints as errors

**/*.rs: Run cargo fmt --all when Rust files changed as part of WebAssembly work
Run cargo clippy --workspace --all-targets -- -D warnings when Rust files changed as part of WebAssembly work

**/*.rs: If any Rust code changed, always run just test-rust
If any Rust code changed, also run cargo fmt --all
If any Rust code changed, also run cargo clippy --workspace --all-targets -- -D warnings
Run Rust formatting with cargo fmt --all
Run Rust linting with cargo clippy --workspace --all-targets -- -D warnings

**/*.rs: Use cargo fmt for Rust code formatting
Run cargo clippy -- -D warnings to lint Rust code and treat all warnings as errors
Use Rust snake_case naming convention for Rust identifiers
Include SPDX license header in all Rust source files using double-slash comment syntax
Validate Rust code with uv run pre-commit run --all-files to enforce cargo fmt formatting check, cargo clippy lints, and cargo deny aud...

Files:

  • crates/cli/tests/coverage/plugins_tests.rs
{crates/adaptive/**/*.rs,**/*test*.{rs,py,go,ts,js},**/*adaptive*test*.{rs,py,go,ts,js},docs/plugins/adaptive/**}

📄 CodeRabbit inference engine (.agents/skills/maintain-optimizer/SKILL.md)

Maintain documented and tested validation and report behavior for adaptive surfaces

Files:

  • crates/cli/tests/coverage/plugins_tests.rs
**/{Cargo.toml,**/*.rs}

📄 CodeRabbit inference engine (.agents/skills/maintain-packaging/SKILL.md)

Maintain consistency between Rust package names in Cargo.toml and their actual usage across the codebase

Files:

  • crates/cli/tests/coverage/plugins_tests.rs
**/*.{h,hpp,c,cpp,rs}

📄 CodeRabbit inference engine (.agents/skills/maintain-packaging/SKILL.md)

Ensure FFI header and library naming follows consistent conventions across platform-specific builds

Files:

  • crates/cli/tests/coverage/plugins_tests.rs
**/*.{rs,toml}

📄 CodeRabbit inference engine (.agents/skills/rename-surfaces/SKILL.md)

Update Rust crate names and module prefixes during coordinated rename operations

Files:

  • crates/cli/tests/coverage/plugins_tests.rs
**/*.{rs,py,js,ts,tsx,jsx,go,sh,toml,yaml,yml,md}

📄 CodeRabbit inference engine (AGENTS.md)

Keep SPDX headers on source, docs, scripts, and configuration files. The project is Apache-2.0.

Files:

  • crates/cli/tests/coverage/plugins_tests.rs
**/*.{rs,py,go,js,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Follow binding naming conventions: Rust and Python use snake_case, C FFI exports prefixed nemo_relay_, Go uses PascalCase for public APIs, Node.js uses camelCase.

Files:

  • crates/cli/tests/coverage/plugins_tests.rs
crates/**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

crates/**/*.rs: Keep async behavior on the existing tokio-based model. Bindings should preserve callback and future lifetimes rather than blocking or hiding async work unexpectedly.
Use Json = serde_json::Value in Rust-facing runtime APIs for JSON payload handling.

Files:

  • crates/cli/tests/coverage/plugins_tests.rs
{crates/**/tests/**,python/tests/**,go/nemo_relay/**/*_test.go}

⚙️ CodeRabbit configuration file

{crates/**/tests/**,python/tests/**,go/nemo_relay/**/*_test.go}: Tests should cover the behavior promised by the changed API surface, including error paths and cross-request isolation where relevant.
Prefer assertions on lifecycle events, scope stacks, middleware ordering, and binding parity over shallow smoke tests.

Files:

  • crates/cli/tests/coverage/plugins_tests.rs
🔇 Additional comments (2)
crates/cli/tests/coverage/plugins_tests.rs (2)

10-10: LGTM!

Also applies to: 53-85, 197-213


1192-1282: Please confirm required Rust checks were run for this file change.

I don’t see execution evidence here; please run and share results for:

  • cargo fmt --all
  • cargo clippy --workspace --all-targets -- -D warnings
  • just test-rust

As per coding guidelines, Any Rust change must run just test-rust, Any Rust change must run cargo fmt --all, and Any Rust change must run cargo clippy --workspace --all-targets -- -D warnings.

Also applies to: 1302-1397


Walkthrough

Adds a first-class in-process NeMo Guardrails local backend: Rust provider registry and component routing, a PyO3 bridge to call a Python register_local_backend, a Python implementation enforcing rails for LLMs/tools (including streaming), tests, and documentation updates.

Changes

Local Backend Provider & Core Integration

Layer / File(s) Summary
Local Backend Provider Registry
crates/core/src/plugins/nemo_guardrails/local.rs
Defines a global mutex-protected registry for local backend provider functions, with public APIs to register/unregister providers and invoke the registered provider during initialization.
Component Routing & Validation
crates/core/src/plugins/nemo_guardrails/component.rs
Re-exports provider registration helpers, routes mode = "local" to invoke the registered provider, and rejects request_defaults in local mode with a validation diagnostic.
Core Unit Tests
crates/core/tests/unit/plugins/nemo_guardrails/component_tests.rs
Clears provider state in test reset, asserts request_defaults rejection in local mode, updates unavailable-backend error expectation, and adds a test verifying provider registration and invocation.

Python Bridge & Local Backend Module

Layer / File(s) Summary
Python Plugin Helper Extraction
crates/python/src/py_plugin.rs
Adds invoke_python_plugin_register to centralize creating a PyPluginContext, converting config, invoking Python register, draining registrations, and rolling back partial registrations on error; refactors PyPlugin::register to use it.
Native Module Initialization & Bridge
crates/python/src/lib.rs
PyO3 _native initialization registers a Rust→Python local backend provider by serializing NeMoGuardrailsConfig, locating nemo_relay._guardrails_local.register_local_backend (with a source-tree fallback), and appending returned registrations to PluginRegistrationContext.
Local Guardrails Interception Module
python/nemo_relay/_guardrails_local.py
Implements dynamic nemoguardrails loading, violation types, codec-aware input/output/tool rail enforcement, streaming monitoring for output rails, and register_local_backend that wires interceptors into PluginContext with configurable priority.

Integration Testing

Layer / File(s) Summary
Test Infrastructure & Helpers
crates/python/tests/coverage/coverage_tests.rs
Adds test harness helpers: fake nemo_guardrails generator, runtime reset/clear helpers, and module isolation utilities.
Guardrails Integration Tests
crates/python/tests/coverage/coverage_tests.rs, crates/python/tests/coverage/py_plugin_coverage_tests.rs
Adds E2E coverage for native-local provider failure path, registered local provider enforcement for LLM/tool flows (including streaming stream_first semantics), and a Python plugin registration rollback test.

Documentation Updates

Layer / File(s) Summary
Concept & Overview Documentation
docs/about-nemo-relay/concepts/plugins.mdx, docs/nemo-guardrails-plugin/about.mdx
Documents both remote and local guardrails backend modes, capability differences, and availability/initialization behavior.
Configuration Reference
docs/nemo-guardrails-plugin/configuration.mdx, docs/build-plugins/nemoguardrails.mdx
Expands configuration reference and adds a full local-mode section with rules, examples, codec/tool/streaming boundaries, and stream_first semantics.

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 35.63% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title follows Conventional Commits format with type 'feat', lowercase scope omitted (acceptable), and a concise imperative summary under 72 characters describing the main change.
Description check ✅ Passed The description includes all required template sections: Overview with confirmation checkboxes, Details with specific changes listed, Where should the reviewer start with file pointers, and Related Issues with proper action keywords.
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.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@github-actions github-actions Bot added size:XL PR is extra large Feature a new feature lang:python PR changes/introduces Python code lang:rust PR changes/introduces Rust code labels Jun 1, 2026
Signed-off-by: Alex Fournier <afournier@nvidia.com>
@afourniernv afourniernv marked this pull request as ready for review June 1, 2026 15:05
@afourniernv afourniernv requested a review from a team as a code owner June 1, 2026 15:05
@willkill07 willkill07 added this to the 0.4 milestone Jun 1, 2026
@willkill07
Copy link
Copy Markdown
Member

/ok to test 98d4915

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

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

Inline comments:
In `@crates/python/src/lib.rs`:
- Around line 112-121: The current retry unconditionally swallows any py.import
error and prepends source_python_dir; change load_guardrails_local_register_fn
to only attempt the fallback when the original PyErr is specifically a
ModuleNotFoundError for "nemo_relay" or "nemo_relay._guardrails_local": capture
the Err(err) from py.import, inspect its Python exception type (e.g. compare
against pyo3::exceptions::PyModuleNotFoundError) and/or the exception message to
ensure it indicates the module is missing, and only then check
source_python_dir, call prepend_python_path_if_missing(py, &source_python_dir)
and retry the import; for any other error type, immediately return Err(err)
unchanged (preserving the original error).

In `@crates/python/src/py_plugin.rs`:
- Around line 177-181: The call to register_fn.call1((plugin_config_py,
py_ctx.clone_ref(py))) can raise after partial registrations were made, but the
current code only drains registrations on the success path; to fix, call
register_fn.call1 and capture its Result, and if it Errs then immediately bind
the PyPluginContext (py_ctx.bind(py).borrow()) and call drain_registrations() to
rollback any partial registrations before returning the Err; keep the
successful-path behavior unchanged. Use the existing symbols register_fn.call1,
py_ctx, PyPluginContext (py_ctx.bind(py).borrow()), and drain_registrations() to
locate and implement the error-path cleanup.

In `@crates/python/tests/coverage/coverage_tests.rs`:
- Around line 344-357: The test currently only asserts check_calls length, which
misses verifying the actual guardrail kinds; update the assertions to explicitly
assert the captured guardrail sequence from result_json["check_calls"] matches
the expected rail types in order (input, output, tool_input, tool_output) and
also validate each call's input payload where relevant (use the recorded
(messages, rail_types) entries from the fake backend), replacing the len() check
with explicit equality checks against the expected sequence so order and types
are enforced (refer to check_calls, seen_request_messages, seen_tool_args, and
llm_result in the diff to locate the assertions to update).
- Around line 232-236: The tests mutate Python's sys.modules for the nemo_relay
namespace without isolating changes; add a small helper (e.g.,
snapshot_and_swap_nemo_relay or with_nemo_relay_modules) that, given a Python
GIL/python interpreter handle, snapshots all sys.modules keys starting with
"nemo_relay", clears or removes those keys, yields to a closure where you
perform the modules.set_item("nemo_relay._native", native_module.clone()) swap,
and finally restores the original snapshot even on panic; replace the direct
modules.set_item calls in
test_guardrails_local_helper_registers_and_enforces_llm_and_tool_checks,
test_guardrails_local_helper_enforces_streamed_output_rails, and
test_local_guardrails_provider_initializes_and_enforces_managed_core_calls with
this helper so each test always restores the full nemo_relay.* entries.

In `@python/nemo_relay/_guardrails_local.py`:
- Around line 455-486: The monitor task is created eagerly which leaks when the
returned async generator (guarded_provider_stream) is never iterated; move
creation of text_queue, blocked and the asyncio.create_task(...) that starts
_monitor_streaming_output_rails into the start of guarded_provider_stream so the
monitor is only created when the generator is actually consumed, keep the
existing finally block logic (await text_queue.put(None); await monitor;
re-check blocked["message"] and call _raise_streaming_output_blocked if needed),
and ensure you still use _extract_stream_text, stream and blocked inside the
generator as before.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 37c4df6f-db71-420c-b283-495a31160158

📥 Commits

Reviewing files that changed from the base of the PR and between b0557a9 and 98d4915.

📒 Files selected for processing (11)
  • crates/core/src/plugins/nemo_guardrails/component.rs
  • crates/core/src/plugins/nemo_guardrails/local.rs
  • crates/core/tests/unit/plugins/nemo_guardrails/component_tests.rs
  • crates/python/src/lib.rs
  • crates/python/src/py_plugin.rs
  • crates/python/tests/coverage/coverage_tests.rs
  • docs/about-nemo-relay/concepts/plugins.mdx
  • docs/build-plugins/nemoguardrails.mdx
  • docs/nemo-guardrails-plugin/about.mdx
  • docs/nemo-guardrails-plugin/configuration.mdx
  • python/nemo_relay/_guardrails_local.py
💤 Files with no reviewable changes (1)
  • docs/build-plugins/nemoguardrails.mdx
📜 Review details
🧰 Additional context used
📓 Path-based instructions (32)
{docs/**,README.md,CONTRIBUTING.md}

📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)

{docs/**,README.md,CONTRIBUTING.md}: For docs-only changes, run targeted checks only if commands, package names, or examples changed. Use just docs for docs-site builds and just docs-linkcheck when links changed
Run docs site build with just docs

Files:

  • docs/about-nemo-relay/concepts/plugins.mdx
  • docs/nemo-guardrails-plugin/about.mdx
  • docs/nemo-guardrails-plugin/configuration.mdx
{docs/**,README.md,CONTRIBUTING.md,**/*.md}

📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)

Run docs link validation with just docs-linkcheck when links change

Files:

  • docs/about-nemo-relay/concepts/plugins.mdx
  • docs/nemo-guardrails-plugin/about.mdx
  • docs/nemo-guardrails-plugin/configuration.mdx
{docs/**,README.md}

📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)

Verify README and docs entry points still match current package names and paths for large or public-facing changes

Files:

  • docs/about-nemo-relay/concepts/plugins.mdx
  • docs/nemo-guardrails-plugin/about.mdx
  • docs/nemo-guardrails-plugin/configuration.mdx
{docs/**,examples/**,README.md}

📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)

Verify examples still run with documented commands for large or public-facing changes

Files:

  • docs/about-nemo-relay/concepts/plugins.mdx
  • docs/nemo-guardrails-plugin/about.mdx
  • docs/nemo-guardrails-plugin/configuration.mdx
{docs/**,README.md,**/Cargo.toml,**/package.json,**/*.md}

📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)

Ensure renamed public surfaces are reflected consistently in manifests and docs for large or public-facing changes

Files:

  • docs/about-nemo-relay/concepts/plugins.mdx
  • docs/nemo-guardrails-plugin/about.mdx
  • docs/nemo-guardrails-plugin/configuration.mdx
**/*.{md,mdx,py,sh,yaml,yml,toml,json}

📄 CodeRabbit inference engine (.agents/skills/contribute-docs/SKILL.md)

Keep package names, repo references, and build commands current

Files:

  • docs/about-nemo-relay/concepts/plugins.mdx
  • docs/nemo-guardrails-plugin/about.mdx
  • python/nemo_relay/_guardrails_local.py
  • docs/nemo-guardrails-plugin/configuration.mdx
**/*.mdx

📄 CodeRabbit inference engine (.agents/skills/contribute-docs/SKILL.md)

In MDX files, top-of-file comments must use JSX comment delimiters: {/* to open and */} to close. Do not use HTML comments for MDX SPDX headers.

MDX top-of-file SPDX comments must use {/* ... */} delimiters instead of HTML comment delimiters (Must-Fix)

Files:

  • docs/about-nemo-relay/concepts/plugins.mdx
  • docs/nemo-guardrails-plugin/about.mdx
  • docs/nemo-guardrails-plugin/configuration.mdx
**/*.{html,md,mdx}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Include SPDX license header in HTML and Markdown files using HTML comment syntax

Files:

  • docs/about-nemo-relay/concepts/plugins.mdx
  • docs/nemo-guardrails-plugin/about.mdx
  • docs/nemo-guardrails-plugin/configuration.mdx
docs/**/*.{md,mdx}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Update embedded documentation snippets, patch docs, and binding-support notes if examples or supported bindings changed

Files:

  • docs/about-nemo-relay/concepts/plugins.mdx
  • docs/nemo-guardrails-plugin/about.mdx
  • docs/nemo-guardrails-plugin/configuration.mdx
docs/**

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Run just docs or ./scripts/build-docs.sh html to regenerate ignored Fern API reference pages before validation for documentation site changes

Files:

  • docs/about-nemo-relay/concepts/plugins.mdx
  • docs/nemo-guardrails-plugin/about.mdx
  • docs/nemo-guardrails-plugin/configuration.mdx
{docs/**,README.md,CONTRIBUTING.md,RELEASING.md,SECURITY.md}

⚙️ CodeRabbit configuration file

{docs/**,README.md,CONTRIBUTING.md,RELEASING.md,SECURITY.md}: Review documentation for technical accuracy against the current API, command correctness, and consistency across language bindings.
Flag stale examples, missing SPDX headers where required, and instructions that no longer match CI or pre-commit behavior.

Files:

  • docs/about-nemo-relay/concepts/plugins.mdx
  • docs/nemo-guardrails-plugin/about.mdx
  • docs/nemo-guardrails-plugin/configuration.mdx
**/*.rs

📄 CodeRabbit inference engine (.agents/skills/add-binding-feature/SKILL.md)

Use snake_case naming convention for Rust identifiers (e.g., nemo_relay_tool_call)

**/*.rs: Any Rust change must run just test-rust
Any Rust change must run cargo fmt --all
Any Rust change must run cargo clippy --workspace --all-targets -- -D warnings

**/*.rs: Run cargo fmt --all for all FFI work since it is Rust work
Run just test-rust to validate FFI changes
Run cargo clippy --workspace --all-targets -- -D warnings to enforce strict linting on FFI work

When Rust files changed as part of Go work, also run cargo fmt --all, just test-rust, and cargo clippy --workspace --all-targets -- -D warnings

**/*.rs: Run cargo fmt --all when Rust files are changed as part of Node work
Run cargo clippy --workspace --all-targets -- -D warnings when Rust files are changed as part of Node work
Run just test-rust when Rust files are changed as part of Node work

**/*.rs: Run cargo fmt --all to format all Rust code
Run cargo clippy --workspace --all-targets -- -D warnings to enforce all clippy lints as errors

**/*.rs: Run cargo fmt --all when Rust files changed as part of WebAssembly work
Run cargo clippy --workspace --all-targets -- -D warnings when Rust files changed as part of WebAssembly work

**/*.rs: If any Rust code changed, always run just test-rust
If any Rust code changed, also run cargo fmt --all
If any Rust code changed, also run cargo clippy --workspace --all-targets -- -D warnings
Run Rust formatting with cargo fmt --all
Run Rust linting with cargo clippy --workspace --all-targets -- -D warnings

**/*.rs: Use cargo fmt for Rust code formatting
Run cargo clippy -- -D warnings to lint Rust code and treat all warnings as errors
Use Rust snake_case naming convention for Rust identifiers
Include SPDX license header in all Rust source files using double-slash comment syntax
Validate Rust code with uv run pre-commit run --all-files to enforce cargo fmt formatting check, cargo clippy lints, and cargo deny aud...

Files:

  • crates/core/src/plugins/nemo_guardrails/local.rs
  • crates/core/tests/unit/plugins/nemo_guardrails/component_tests.rs
  • crates/python/src/py_plugin.rs
  • crates/core/src/plugins/nemo_guardrails/component.rs
  • crates/python/src/lib.rs
  • crates/python/tests/coverage/coverage_tests.rs
**/{Cargo.toml,**/*.rs}

📄 CodeRabbit inference engine (.agents/skills/maintain-packaging/SKILL.md)

Maintain consistency between Rust package names in Cargo.toml and their actual usage across the codebase

Files:

  • crates/core/src/plugins/nemo_guardrails/local.rs
  • crates/core/tests/unit/plugins/nemo_guardrails/component_tests.rs
  • crates/python/src/py_plugin.rs
  • crates/core/src/plugins/nemo_guardrails/component.rs
  • crates/python/src/lib.rs
  • crates/python/tests/coverage/coverage_tests.rs
**/*.{h,hpp,c,cpp,rs}

📄 CodeRabbit inference engine (.agents/skills/maintain-packaging/SKILL.md)

Ensure FFI header and library naming follows consistent conventions across platform-specific builds

Files:

  • crates/core/src/plugins/nemo_guardrails/local.rs
  • crates/core/tests/unit/plugins/nemo_guardrails/component_tests.rs
  • crates/python/src/py_plugin.rs
  • crates/core/src/plugins/nemo_guardrails/component.rs
  • crates/python/src/lib.rs
  • crates/python/tests/coverage/coverage_tests.rs
{crates/core,crates/adaptive}/**/*

📄 CodeRabbit inference engine (.agents/skills/prepare-pr/SKILL.md)

Changes to crates/core or crates/adaptive must run the full language matrix

Files:

  • crates/core/src/plugins/nemo_guardrails/local.rs
  • crates/core/tests/unit/plugins/nemo_guardrails/component_tests.rs
  • crates/core/src/plugins/nemo_guardrails/component.rs
**/*.{rs,toml}

📄 CodeRabbit inference engine (.agents/skills/rename-surfaces/SKILL.md)

Update Rust crate names and module prefixes during coordinated rename operations

Files:

  • crates/core/src/plugins/nemo_guardrails/local.rs
  • crates/core/tests/unit/plugins/nemo_guardrails/component_tests.rs
  • crates/python/src/py_plugin.rs
  • crates/core/src/plugins/nemo_guardrails/component.rs
  • crates/python/src/lib.rs
  • crates/python/tests/coverage/coverage_tests.rs
crates/core/**/*.rs

📄 CodeRabbit inference engine (.agents/skills/test-go-binding/SKILL.md)

If the change touched crates/core or shared runtime semantics, also use validate-change for broader validation

crates/core/**/*.rs: Use Json = serde_json::Value in Rust-facing runtime APIs where the existing code expects JSON payloads.
Use Result<T> with FlowError in core runtime paths. Keep errors explicit and binding-appropriate at the wrapper layer.

Files:

  • crates/core/src/plugins/nemo_guardrails/local.rs
  • crates/core/tests/unit/plugins/nemo_guardrails/component_tests.rs
  • crates/core/src/plugins/nemo_guardrails/component.rs
crates/{core,adaptive}/**

📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)

If crates/core or crates/adaptive changed, run the full matrix across Rust, Python, Go, Node.js, and WebAssembly

Files:

  • crates/core/src/plugins/nemo_guardrails/local.rs
  • crates/core/tests/unit/plugins/nemo_guardrails/component_tests.rs
  • crates/core/src/plugins/nemo_guardrails/component.rs
**/*.{rs,py,js,ts,tsx,jsx,go,sh,toml,yaml,yml,md}

📄 CodeRabbit inference engine (AGENTS.md)

Keep SPDX headers on source, docs, scripts, and configuration files. The project is Apache-2.0.

Files:

  • crates/core/src/plugins/nemo_guardrails/local.rs
  • crates/core/tests/unit/plugins/nemo_guardrails/component_tests.rs
  • crates/python/src/py_plugin.rs
  • crates/core/src/plugins/nemo_guardrails/component.rs
  • crates/python/src/lib.rs
  • crates/python/tests/coverage/coverage_tests.rs
  • python/nemo_relay/_guardrails_local.py
**/*.{rs,py,go,js,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Follow binding naming conventions: Rust and Python use snake_case, C FFI exports prefixed nemo_relay_, Go uses PascalCase for public APIs, Node.js uses camelCase.

Files:

  • crates/core/src/plugins/nemo_guardrails/local.rs
  • crates/core/tests/unit/plugins/nemo_guardrails/component_tests.rs
  • crates/python/src/py_plugin.rs
  • crates/core/src/plugins/nemo_guardrails/component.rs
  • crates/python/src/lib.rs
  • crates/python/tests/coverage/coverage_tests.rs
  • python/nemo_relay/_guardrails_local.py
crates/**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

crates/**/*.rs: Keep async behavior on the existing tokio-based model. Bindings should preserve callback and future lifetimes rather than blocking or hiding async work unexpectedly.
Use Json = serde_json::Value in Rust-facing runtime APIs for JSON payload handling.

Files:

  • crates/core/src/plugins/nemo_guardrails/local.rs
  • crates/core/tests/unit/plugins/nemo_guardrails/component_tests.rs
  • crates/python/src/py_plugin.rs
  • crates/core/src/plugins/nemo_guardrails/component.rs
  • crates/python/src/lib.rs
  • crates/python/tests/coverage/coverage_tests.rs
crates/{core,adaptive}/**/*.rs

⚙️ CodeRabbit configuration file

crates/{core,adaptive}/**/*.rs: Review the Rust runtime for async correctness, scope isolation, middleware ordering, and event lifecycle regressions.
Pay close attention to task-local/thread-local scope propagation, callback lifetimes, stream finalization, and root_uuid isolation.
Public API changes should preserve existing behavior unless tests and docs show the intended migration path.

Files:

  • crates/core/src/plugins/nemo_guardrails/local.rs
  • crates/core/tests/unit/plugins/nemo_guardrails/component_tests.rs
  • crates/core/src/plugins/nemo_guardrails/component.rs
{crates/adaptive/**/*.rs,**/*test*.{rs,py,go,ts,js},**/*adaptive*test*.{rs,py,go,ts,js},docs/plugins/adaptive/**}

📄 CodeRabbit inference engine (.agents/skills/maintain-optimizer/SKILL.md)

Maintain documented and tested validation and report behavior for adaptive surfaces

Files:

  • crates/core/tests/unit/plugins/nemo_guardrails/component_tests.rs
  • crates/python/tests/coverage/coverage_tests.rs
{crates/**/tests/**,python/tests/**,go/nemo_relay/**/*_test.go}

⚙️ CodeRabbit configuration file

{crates/**/tests/**,python/tests/**,go/nemo_relay/**/*_test.go}: Tests should cover the behavior promised by the changed API surface, including error paths and cross-request isolation where relevant.
Prefer assertions on lifecycle events, scope stacks, middleware ordering, and binding parity over shallow smoke tests.

Files:

  • crates/core/tests/unit/plugins/nemo_guardrails/component_tests.rs
  • crates/python/tests/coverage/coverage_tests.rs
crates/python/**/*.rs

📄 CodeRabbit inference engine (.agents/skills/test-python-binding/SKILL.md)

If the native Rust bridge changed, add the Rust crate tests for nemo-relay-python

Files:

  • crates/python/src/py_plugin.rs
  • crates/python/src/lib.rs
  • crates/python/tests/coverage/coverage_tests.rs
crates/{python,ffi,node,wasm}/**/*

⚙️ CodeRabbit configuration file

crates/{python,ffi,node,wasm}/**/*: Treat binding changes as public API changes. Check for parity with the other language bindings, FFI ownership/lifetime safety,
callback error propagation, stable type conversion, and consistent async/stream semantics.
Flag changes that update one binding without corresponding tests or documentation for the same surface elsewhere.

Files:

  • crates/python/src/py_plugin.rs
  • crates/python/src/lib.rs
  • crates/python/tests/coverage/coverage_tests.rs
{crates/python/src/py_api/**/*.rs,python/nemo_relay/**/*.py,python/nemo_relay/**/*.pyi}

📄 CodeRabbit inference engine (.agents/skills/add-binding-feature/SKILL.md)

Update Python native binding in crates/python/src/py_api/mod.rs with Python wrapper docstring in python/nemo_relay/<module>.py and type stubs in python/nemo_relay/*.pyi modules

Files:

  • python/nemo_relay/_guardrails_local.py
python/nemo_relay/**/*.py

📄 CodeRabbit inference engine (.agents/skills/add-binding-feature/SKILL.md)

Use snake_case naming convention for Python identifiers (e.g., nemo_relay.tools.call)

Format changed Python wrapper and test files with uv run ruff format python

Python wrapper modules live under python/nemo_relay/; the native extension is built from crates/python with maturin.

Files:

  • python/nemo_relay/_guardrails_local.py
{pyproject.toml,**/*.py}

📄 CodeRabbit inference engine (.agents/skills/maintain-packaging/SKILL.md)

Maintain consistency between Python package names in pyproject.toml and import paths used throughout the codebase

Files:

  • python/nemo_relay/_guardrails_local.py
**/*.{py,txt,toml,cfg,yaml,yml}

📄 CodeRabbit inference engine (.agents/skills/rename-surfaces/SKILL.md)

Update Python package names and top-level module imports during coordinated rename operations

Files:

  • python/nemo_relay/_guardrails_local.py
**/*.py

📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)

**/*.py: Run Python formatting with uv run ruff format python
Run Python testing with uv run pytest -k "<pattern>"

**/*.py: Use Ruff with rule sets E, F, W, I for Python linting
Use Ruff formatter with line length 120 and double quotes for Python code formatting
Run ty for Python type checking
Use Python snake_case naming convention for Python identifiers
Include SPDX license header in all Python source files using hash comment syntax
Validate Python code with uv run pre-commit run --all-files to enforce Ruff linting and formatting, and ty type checking

Files:

  • python/nemo_relay/_guardrails_local.py
python/nemo_relay/**/*

⚙️ CodeRabbit configuration file

python/nemo_relay/**/*: Review Python wrapper changes for typed API consistency, contextvars-based scope isolation, async behavior, and parity with the native extension.
Stubs and runtime implementations should stay aligned.

Files:

  • python/nemo_relay/_guardrails_local.py
🪛 LanguageTool
docs/nemo-guardrails-plugin/configuration.mdx

[style] ~294-~294: To form a complete sentence, be sure to include a subject.
Context: ...g_yamlis required. -colang_contentcan only be used withconfig_yaml. - rem...

(MISSING_IT_THERE)

🪛 Ruff (0.15.15)
python/nemo_relay/_guardrails_local.py

[warning] 59-59: Missing return type annotation for private function _load_nemoguardrails

(ANN202)


[warning] 66-69: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 70-73: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 83-83: Dynamically typed expressions (typing.Any) are disallowed in status

(ANN401)


[warning] 87-87: Dynamically typed expressions (typing.Any) are disallowed in annotated

(ANN401)


[warning] 92-92: Dynamically typed expressions (typing.Any) are disallowed in rails

(ANN401)


[warning] 93-93: Dynamically typed expressions (typing.Any) are disallowed in rail_type

(ANN401)


[warning] 94-94: Dynamically typed expressions (typing.Any) are disallowed in rail_status

(ANN401)


[warning] 122-126: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 156-160: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 163-167: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 171-171: Dynamically typed expressions (typing.Any) are disallowed in result

(ANN401)


[warning] 174-180: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 184-184: Dynamically typed expressions (typing.Any) are disallowed in rails

(ANN401)


[warning] 185-185: Dynamically typed expressions (typing.Any) are disallowed in rail_type

(ANN401)


[warning] 186-186: Dynamically typed expressions (typing.Any) are disallowed in rail_status

(ANN401)


[warning] 202-202: Dynamically typed expressions (typing.Any) are disallowed in rails

(ANN401)


[warning] 206-206: Dynamically typed expressions (typing.Any) are disallowed in rails

(ANN401)


[warning] 206-206: Dynamically typed expressions (typing.Any) are disallowed in _output_streaming_config

(ANN401)


[warning] 210-210: Dynamically typed expressions (typing.Any) are disallowed in rails

(ANN401)


[warning] 215-215: Too many return statements (10 > 6)

(PLR0911)


[warning] 215-215: Too many branches (13 > 12)

(PLR0912)


[warning] 271-271: Missing return type annotation for private function _queue_string_stream

(ANN202)


[warning] 271-271: Remove quotes from type annotation

Remove quotes

(UP037)


[warning] 281-281: Dynamically typed expressions (typing.Any) are disallowed in rails

(ANN401)


[warning] 283-283: Remove quotes from type annotation

Remove quotes

(UP037)


[warning] 300-304: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 307-307: Dynamically typed expressions (typing.Any) are disallowed in rails_config_cls

(ANN401)


[warning] 307-307: Dynamically typed expressions (typing.Any) are disallowed in _build_guardrails_config

(ANN401)


[warning] 319-319: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 324-324: Dynamically typed expressions (typing.Any) are disallowed in rails

(ANN401)


[warning] 325-325: Dynamically typed expressions (typing.Any) are disallowed in rail_type

(ANN401)


[warning] 326-326: Dynamically typed expressions (typing.Any) are disallowed in rail_status

(ANN401)


[warning] 347-347: Dynamically typed expressions (typing.Any) are disallowed in rails

(ANN401)


[warning] 348-348: Dynamically typed expressions (typing.Any) are disallowed in rail_type

(ANN401)


[warning] 349-349: Dynamically typed expressions (typing.Any) are disallowed in rail_status

(ANN401)


[warning] 376-376: Missing return type annotation for private function _make_llm_intercept

(ANN202)


[warning] 378-378: Dynamically typed expressions (typing.Any) are disallowed in rails

(ANN401)


[warning] 379-379: Dynamically typed expressions (typing.Any) are disallowed in rail_type

(ANN401)


[warning] 380-380: Dynamically typed expressions (typing.Any) are disallowed in rail_status

(ANN401)


[warning] 385-385: Missing return type annotation for private function intercept

(ANN202)


[warning] 415-415: Missing return type annotation for private function _make_llm_stream_intercept

(ANN202)


[warning] 417-417: Dynamically typed expressions (typing.Any) are disallowed in rails

(ANN401)


[warning] 418-418: Dynamically typed expressions (typing.Any) are disallowed in rail_type

(ANN401)


[warning] 419-419: Dynamically typed expressions (typing.Any) are disallowed in rail_status

(ANN401)


[warning] 425-425: Missing return type annotation for private function stream_intercept

(ANN202)


[warning] 443-446: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 450-453: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 466-466: Missing return type annotation for private function guarded_provider_stream

(ANN202)


[warning] 491-491: Missing return type annotation for private function _make_tool_intercept

(ANN202)


[warning] 493-493: Dynamically typed expressions (typing.Any) are disallowed in rails

(ANN401)


[warning] 494-494: Dynamically typed expressions (typing.Any) are disallowed in rail_type

(ANN401)


[warning] 495-495: Dynamically typed expressions (typing.Any) are disallowed in rail_status

(ANN401)


[warning] 499-499: Missing return type annotation for private function tool_intercept

(ANN202)


[warning] 527-527: Dynamically typed expressions (typing.Any) are disallowed in result

(ANN401)


[warning] 533-538: Avoid specifying long messages outside the exception class

(TRY003)

🔇 Additional comments (12)
crates/core/src/plugins/nemo_guardrails/local.rs (2)

17-37: LGTM!


39-51: Lock-free invocation is the right call. Cloning the provider handle and dropping the MutexGuard before invoking avoids re-entrancy deadlock if the provider touches the registry, and keeps the lock off the async registration path.

crates/core/src/plugins/nemo_guardrails/component.rs (2)

20-28: LGTM!

Also applies to: 454-454


960-970: Local-mode request_defaults rejection is consistent. Early-return correctly short-circuits the remote-only field checks. Note the diagnostic honors policy.unsupported_value, so an Ignore policy will pass validation and forward request_defaults to the provider — that matches policy semantics, just confirm the Python backend tolerates/ignores the field rather than erroring.

crates/core/tests/unit/plugins/nemo_guardrails/component_tests.rs (1)

43-49: Coverage and isolation look solid. reset_runtime clearing the provider at each test entry (under test_mutex) keeps the new dispatch test from leaking registration state into siblings, and the assertions inside the provider closure verify both invocation and config propagation.

Also applies to: 793-807, 995-995, 1027-1054

docs/about-nemo-relay/concepts/plugins.mdx (1)

174-177: LGTM!

docs/nemo-guardrails-plugin/about.mdx (3)

23-55: LGTM!


59-116: LGTM!


121-121: LGTM!

docs/nemo-guardrails-plugin/configuration.mdx (3)

37-59: LGTM!


61-197: LGTM!


211-345: LGTM!

Comment thread crates/python/src/lib.rs
Comment thread crates/python/src/py_plugin.rs Outdated
Comment thread crates/python/tests/coverage/coverage_tests.rs Outdated
Comment thread crates/python/tests/coverage/coverage_tests.rs Outdated
Comment thread python/nemo_relay/_guardrails_local.py
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 1, 2026

Signed-off-by: Alex Fournier <afournier@nvidia.com>
Signed-off-by: Alex Fournier <afournier@nvidia.com>
Signed-off-by: Alex Fournier <afournier@nvidia.com>
Signed-off-by: Alex Fournier <afournier@nvidia.com>
@willkill07
Copy link
Copy Markdown
Member

/ok to test 67fd1b9

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

Labels

Feature a new feature lang:python PR changes/introduces Python code lang:rust PR changes/introduces Rust code size:XL PR is extra large

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants