Skip to content

feat(iorails): Add tool-calling rails to RailsManager and ModelRegistry#2030

Merged
tgasser-nv merged 16 commits into
developfrom
feat/iorails-tool-calling-rails
Jun 23, 2026
Merged

feat(iorails): Add tool-calling rails to RailsManager and ModelRegistry#2030
tgasser-nv merged 16 commits into
developfrom
feat/iorails-tool-calling-rails

Conversation

@tgasser-nv

@tgasser-nv tgasser-nv commented Jun 16, 2026

Copy link
Copy Markdown
Collaborator

Description

This PR adds Tool Rails to RailsManager and ModelRegistry. It adds two ToolRailActions, tool call validation and tool result validation.

  • tool call validation : Ensures the tool being called is available for use, and the argument schema matches the tool invocation itself.
  • tool result validation : Ensures the result of a tool call has a call_id matching a previous ToolCall id, that function names, and the response is parseable.

There's no local integration of tool-calling in this PR, since that only makes sense in the next PR in the stack where we integrate into the IORails top-level class.

PR Stack

Related Issue(s)

Stacked PR on top of #2024 (which added non-streaming tool-calling).

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

$ make test
env -u OPENAI_API_KEY -u NVIDIA_API_KEY -u LIVE_TEST -u LIVE_TEST_MODE -u TEST_LIVE_MODE poetry run pytest -n auto --dist worksteal
================================================================ test session starts =================================================================
platform darwin -- Python 3.13.2, pytest-8.4.2, pluggy-1.6.0
rootdir: /Users/tgasser/projects/nemo_guardrails_worktree/feat/iorails-tool-calling-rails
configfile: pytest.ini
testpaths: tests, benchmark/tests
plugins: anyio-4.12.1, langsmith-0.7.12, xdist-3.8.0, httpx-0.35.0, asyncio-0.26.0, profiling-1.8.1, cov-7.0.0
asyncio: mode=Mode.STRICT, asyncio_default_fixture_loop_scope=function, asyncio_default_test_loop_scope=function
10 workers [4932 items]
s................sss...................................................................................................^B....................... [  2%]
...............................ssss...........................s............................................................................... [  5%]
.........................................sssssss............ss.ssssss......................................................................... [  8%]
......................................................................................ss...................................................... [ 11%]
................................................................................................s............................................. [ 14%]
.............................................................................................................................................. [ 17%]
.............................................................................................................................................. [ 20%]
...........................................s.................................................................................................. [ 23%]
.............................................................................................................................................. [ 25%]
.............................................................................................................................................. [ 28%]
.......................................................................................................................................s...... [ 31%]
.......ss....................................s.s.s.s.s.s.s.................................................................................... [ 34%]
.............................................................................................................................................. [ 37%]
.............................................................................................................................................. [ 40%]
................................................................................................................ssssssss......ssssss..ss..s... [ 43%]
................ssssssss...................................................................................................................... [ 46%]
....................................sssss..........................................................s.s........................................ [ 48%]
.......sssssss.............................s...............................s.............................................................sssss [ 51%]
ssssss.sssssss...........................ss.............s......s......................................................................ss...... [ 54%]
.................sssss..............................................................................ss...........s............................ [ 57%]
.............................................................................................................................................. [ 60%]
...................................................................sss.ss..................................................................... [ 63%]
.............................................................................................................................................. [ 66%]
................................s............................................................................................................. [ 69%]
...................................................................................................................sssss.s.s.................. [ 71%]
.............................................................................................................................................. [ 74%]
.............s.................s.........sssssssssssss............................s.................................................s......... [ 77%]
..............ss.....................................................................................ssssssss............s.................... [ 80%]
......................................sss................................ss...ss.............................................................. [ 83%]
.....s........................................................................................................................................ [ 86%]
...............................................s.............................................................................................. [ 89%]
.......................................................s...................................................................................... [ 92%]
.....s..................................................................................................................................ssssss [ 95%]
s.ss.s.ss...............................................................................................................ss.ss................. [ 97%]
..................................s.....................................................................                                       [100%]
========================================================= 4752 passed, 180 skipped in 34.27s =========================================================

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-06-16 14:50:33 INFO: Registered model engine: type=main, model=meta/llama-3.3-70b-instruct, base_url=https://integrate.api.nvidia.com
2026-06-16 14:50:33 INFO: Registered model engine: type=content_safety, model=nvidia/llama-3.1-nemoguard-8b-content-safety, base_url=https://integrate.api.nvidia.com
2026-06-16 14:50:33 INFO: Registered model engine: type=topic_control, model=nvidia/llama-3.1-nemoguard-8b-topic-control, base_url=https://integrate.api.nvidia.com
2026-06-16 14:50:33 INFO: Registered API engine: name=jailbreak_detection, url=https://ai.api.nvidia.com/v1/security/nvidia/nemoguard-jailbreak-detect
2026-06-16 14:50:33 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'], tool_call_flows=[], tool_result_flows=[], input_parallel=False, output_parallel=False

> Hello!
2026-06-16 14:50:36 INFO: [f019b48478ab4426] generate_async called
2026-06-16 14:50:36 INFO: [f019b48478ab4426] Running input rails
2026-06-16 14:50:36 INFO: [f019b48478ab4426] HTTP POST https://integrate.api.nvidia.com/v1/chat/completions model='nvidia/llama-3.1-nemoguard-8b-content-safety'
2026-06-16 14:50:37 INFO: [f019b48478ab4426] HTTP POST https://integrate.api.nvidia.com/v1/chat/completions model='nvidia/llama-3.1-nemoguard-8b-topic-control'
2026-06-16 14:50:37 INFO: [f019b48478ab4426] HTTP POST https://ai.api.nvidia.com/v1/security/nvidia/nemoguard-jailbreak-detect
2026-06-16 14:50:37 INFO: [f019b48478ab4426] Calling main LLM
2026-06-16 14:50:37 INFO: [f019b48478ab4426] HTTP POST https://integrate.api.nvidia.com/v1/chat/completions model='meta/llama-3.3-70b-instruct'
2026-06-16 14:50:44 INFO: [f019b48478ab4426] Running output rails
2026-06-16 14:50:44 INFO: [f019b48478ab4426] HTTP POST https://integrate.api.nvidia.com/v1/chat/completions model='nvidia/llama-3.1-nemoguard-8b-content-safety'
2026-06-16 14:50:45 INFO: [f019b48478ab4426] generate_async completed time=8929.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-06-16 14:50:51 INFO: [afc9fbfaf1126f0e] generate_async called
2026-06-16 14:50:51 INFO: [afc9fbfaf1126f0e] Running input rails
2026-06-16 14:50:51 INFO: [afc9fbfaf1126f0e] HTTP POST https://integrate.api.nvidia.com/v1/chat/completions model='nvidia/llama-3.1-nemoguard-8b-content-safety'
2026-06-16 14:50:51 INFO: [afc9fbfaf1126f0e] Input flow content safety check input $model=content_safety blocked
2026-06-16 14:50:51 INFO: [afc9fbfaf1126f0e] Input blocked: Safety categories: Violence, Criminal Planning/Confessions
2026-06-16 14:50:51 INFO: [afc9fbfaf1126f0e] generate_async completed time=451.5ms
I'm sorry, I can't respond to that.

AI Assistance

  • No AI tools were used.
  • AI tools were used; a human reviewed and can explain every change (tool: ___).

Checklist

  • I've read the CONTRIBUTING guidelines.
  • This PR links to a triaged issue assigned to me.
  • My PR title follows the project commit convention.
  • I've updated the documentation if applicable.
  • I've added tests if applicable.
  • I've noted any verification beyond CI and any checks I couldn't run.
  • I did not update generated changelog files manually.
  • I addressed all CodeRabbit, Greptile, and other review comments, or replied with why no change is needed.
  • @mentions of the person or team responsible for reviewing proposed changes.

Summary by CodeRabbit

Release Notes

  • New Features
    • Added tool call validation to ensure AI-generated function calls target only allowed tools with valid arguments
    • Added tool result validation to verify tool results properly correspond to prior tool calls
    • Extended guardrails manager with configurable safety flows for tool invocations and tool execution results

@tgasser-nv tgasser-nv changed the base branch from develop to feat/iorails-tool-calling-streaming June 16, 2026 19:45
@codecov

codecov Bot commented Jun 16, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@tgasser-nv tgasser-nv marked this pull request as ready for review June 16, 2026 20:30
@tgasser-nv tgasser-nv self-assigned this Jun 16, 2026
@greptile-apps

greptile-apps Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds two new tool-calling rail validators — ToolCallRailAction and ToolResultRailAction — to RailsManager and ModelRegistry, along with the provider-neutral Tool/ToolResult/Toolset internal types and per-engine adapters (OpenAI/NIM) that normalize provider wire shapes into those types.

  • Tool call validation: on every LLM response, each generated tool call is allowlist-checked against the declared Toolset and its arguments are validated against the tool's JSON Schema via jsonschema; undeclared or schema-invalid calls are blocked before execution.
  • Tool result validation: incoming tool results are checked for call_id linkage to a prior call, name consistency, and well-formed content shape; orphaned, duplicate, or malformed results are blocked before being forwarded to the next LLM turn.
  • Integration: EngineRegistry.parse_tools / extract_tool_results expose the normalization seam, RailsManager wires both flows with proper telemetry spans and content capture, and previous reviewer concerns (content capture on tool spans, Toolset mutability) are fully resolved in this version.

Confidence Score: 5/5

Safe to merge; the new rail actions are local, model-free validators that fail closed on any unexpected error, and both flows default to no-op when unconfigured so existing deployments are unaffected.

The core data types (Tool, ToolResult, Toolset) are frozen and immutable. The validation logic is purely structural with no LLM calls. Both rail actions route exceptions through _guarded, converting them to blocking results rather than crashes. The RailsManager wiring reuses the tested _run_rails_sequential with proper coroutine cleanup. Unit, integration, and end-to-end tests cover the full seam including streaming assembly, and all 4752 tests pass.

No files require special attention; the implementation is straightforward and the test coverage is thorough across all changed files.

Important Files Changed

Filename Overview
nemoguardrails/guardrails/tool_schema.py New file: frozen Tool and ToolResult dataclasses, immutable Toolset backed by a single dict index, and validate_arguments wrapping jsonschema; well-designed with duplicate-key rejection and proper SchemaError handling.
nemoguardrails/guardrails/tool_rail_action.py New base class for model-free tool rails; _guarded wraps any synchronous check in an action span and converts unexpected exceptions to blocking results (fail-closed), consistent with RailAction's error contract.
nemoguardrails/guardrails/actions/tool_call_action.py New ToolCallRailAction: iterates tool calls, checks each against the toolset allowlist, then validates arguments via jsonschema; correctly falls back to call.type for hosted/server tools.
nemoguardrails/guardrails/actions/tool_result_action.py New ToolResultRailAction: validates call_id linkage to prior calls, name consistency, and content shape; blocks on duplicate call IDs and missing/orphaned results; previous review concern about content type check is fully addressed.
nemoguardrails/guardrails/rails_manager.py Adds tool_call_flows/tool_result_flows constructor params, _build_tool_actions with duplicate/direction guards, and are_tool_calls_safe/are_tool_results_safe; correctly reuses _run_rails_sequential with proper coroutine cleanup.
nemoguardrails/guardrails/model_engine.py Adds _parse_tools_openai/NIM parsers and _extract_tool_results_openai/NIM extractors with fallback to OpenAI shape for unknown engines; parse_tools and extract_tool_results methods properly skip malformed entries.
nemoguardrails/guardrails/engine_registry.py Adds parse_tools and extract_tool_results delegation methods that merge body_param_defaults with per-call llm_params before parsing; raises KeyError/TypeError appropriately for unknown or non-model engines.
tests/guardrails/test_tool_rails_e2e.py End-to-end tests exercising the full parse→validate seam for both non-streaming and streaming tool calls, plus tool result linkage; mocks only aiohttp transport so real ModelEngine parsing runs.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Caller
    participant EngineRegistry
    participant ModelEngine
    participant RailsManager
    participant ToolCallRailAction
    participant ToolResultRailAction

    Caller->>EngineRegistry: parse_tools(model_type, llm_params)
    EngineRegistry->>ModelEngine: parse_tools(merged_params)
    ModelEngine-->>EngineRegistry: Toolset
    EngineRegistry-->>Caller: Toolset

    Caller->>EngineRegistry: "model_call(messages, tools=...)"
    EngineRegistry->>ModelEngine: HTTP POST /v1/chat/completions
    ModelEngine-->>EngineRegistry: "LLMResponse(tool_calls=[...])"
    EngineRegistry-->>Caller: LLMResponse

    Caller->>RailsManager: are_tool_calls_safe(tool_calls, toolset)
    RailsManager->>ToolCallRailAction: run(toolset, tool_calls)
    Note over ToolCallRailAction: Allowlist check (name in toolset) + Schema validation (jsonschema)
    ToolCallRailAction-->>RailsManager: "RailResult(is_safe=True/False)"
    RailsManager-->>Caller: RailResult

    Note over Caller: Execute tools if safe, collect results

    Caller->>EngineRegistry: extract_tool_results(model_type, messages)
    EngineRegistry->>ModelEngine: extract_tool_results(messages)
    ModelEngine-->>EngineRegistry: list[ToolResult]
    EngineRegistry-->>Caller: list[ToolResult]

    Caller->>RailsManager: are_tool_results_safe(tool_results, prior_calls)
    RailsManager->>ToolResultRailAction: run(tool_results, prior_calls)
    Note over ToolResultRailAction: call_id linkage + name consistency + content shape
    ToolResultRailAction-->>RailsManager: "RailResult(is_safe=True/False)"
    RailsManager-->>Caller: RailResult
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Caller
    participant EngineRegistry
    participant ModelEngine
    participant RailsManager
    participant ToolCallRailAction
    participant ToolResultRailAction

    Caller->>EngineRegistry: parse_tools(model_type, llm_params)
    EngineRegistry->>ModelEngine: parse_tools(merged_params)
    ModelEngine-->>EngineRegistry: Toolset
    EngineRegistry-->>Caller: Toolset

    Caller->>EngineRegistry: "model_call(messages, tools=...)"
    EngineRegistry->>ModelEngine: HTTP POST /v1/chat/completions
    ModelEngine-->>EngineRegistry: "LLMResponse(tool_calls=[...])"
    EngineRegistry-->>Caller: LLMResponse

    Caller->>RailsManager: are_tool_calls_safe(tool_calls, toolset)
    RailsManager->>ToolCallRailAction: run(toolset, tool_calls)
    Note over ToolCallRailAction: Allowlist check (name in toolset) + Schema validation (jsonschema)
    ToolCallRailAction-->>RailsManager: "RailResult(is_safe=True/False)"
    RailsManager-->>Caller: RailResult

    Note over Caller: Execute tools if safe, collect results

    Caller->>EngineRegistry: extract_tool_results(model_type, messages)
    EngineRegistry->>ModelEngine: extract_tool_results(messages)
    ModelEngine-->>EngineRegistry: list[ToolResult]
    EngineRegistry-->>Caller: list[ToolResult]

    Caller->>RailsManager: are_tool_results_safe(tool_results, prior_calls)
    RailsManager->>ToolResultRailAction: run(tool_results, prior_calls)
    Note over ToolResultRailAction: call_id linkage + name consistency + content shape
    ToolResultRailAction-->>RailsManager: "RailResult(is_safe=True/False)"
    RailsManager-->>Caller: RailResult
Loading

Reviews (6): Last reviewed commit: "Merge tools from statis config and infer..." | Re-trigger Greptile

Comment thread nemoguardrails/guardrails/rails_manager.py
Comment thread nemoguardrails/guardrails/tool_schema.py Outdated
@tgasser-nv tgasser-nv marked this pull request as draft June 22, 2026 15:01
Base automatically changed from feat/iorails-tool-calling-streaming to develop June 22, 2026 16:05
@tgasser-nv tgasser-nv force-pushed the feat/iorails-tool-calling-rails branch from 22a5d8c to 9f2eb51 Compare June 22, 2026 17:36
@tgasser-nv tgasser-nv marked this pull request as ready for review June 22, 2026 18:51
@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 Jun 22, 2026

Copy link
Copy Markdown
Contributor

@tgasser-nv Sure, I'll review the PR now!

✅ Action performed

Full review finished.

@tgasser-nv tgasser-nv requested review from Pouyanpi and cparisien June 22, 2026 19:07
@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds a full "tool rails" feature to NeMo Guardrails. It introduces provider-neutral Tool, Toolset, and ToolResult datatypes, provider-aware parsing/extraction in ModelEngine and EngineRegistry, a ToolRailAction base class with fail-closed error handling, two concrete rail actions (ToolCallRailAction for allowlist+schema validation and ToolResultRailAction for structural linkage validation), and wires these into RailsManager via new are_tool_calls_safe/are_tool_results_safe methods with telemetry spans. Comprehensive unit and end-to-end tests cover all layers.

Changes

Tool Call and Result Validation Rails

Layer / File(s) Summary
Provider-neutral tool schema types and argument validation
nemoguardrails/guardrails/tool_schema.py, tests/guardrails/test_tool_schema.py
Defines Tool, Toolset, ToolResult frozen dataclasses and validate_arguments using jsonschema; unit tests cover key computation, duplicate rejection, immutability, and schema validation edge cases.
ToolRailAction base class with fail-closed _guarded wrapper
nemoguardrails/guardrails/tool_rail_action.py, tests/guardrails/test_tool_rail_action.py
Adds ToolRailAction base with requires_model=False and _guarded() that wraps a check in an OTel action span and converts any exception into an unsafe RailResult; tests verify passthrough and exception-catch behavior.
Provider-aware tool parsing and result extraction
nemoguardrails/guardrails/model_engine.py, nemoguardrails/guardrails/engine_registry.py, tests/guardrails/test_model_engine.py, tests/guardrails/test_engine_registry.py
Adds OpenAI/NIM parser and extractor helpers in ModelEngine with dispatch maps, ModelEngine.parse_tools()/extract_tool_results() public methods, and delegation wrappers on EngineRegistry; tests cover field mapping, malformed input, fallback behavior, and error handling for unknown engine types.
ToolCallRailAction and ToolResultRailAction implementations
nemoguardrails/guardrails/actions/tool_call_action.py, nemoguardrails/guardrails/actions/tool_result_action.py, tests/guardrails/test_tool_call_action.py, tests/guardrails/test_tool_result_action.py
ToolCallRailAction._validate enforces tool name allowlisting and argument schema validation; ToolResultRailAction._validate checks call_id presence, linkage to a prior call, name consistency, and content type; both delegate through _guarded; unit tests cover safe, blocked, and batch-failure scenarios.
RailsManager wiring, public safety methods, and telemetry dispatchers
nemoguardrails/guardrails/rails_manager.py, tests/guardrails/test_rails_manager.py
Extends RailsManager.__init__ with tool_call_flows/tool_result_flows, adds _build_tool_actions registry lookup, introduces are_tool_calls_safe()/are_tool_results_safe() public API, and adds _run_tool_call_rail()/_run_tool_result_rail() span-wrapped dispatchers with optional payload capture; tests cover initialization, safety outcomes, and OTel span attribute assertions.
Shared test helpers and end-to-end tool rail tests
tests/guardrails/tool_helpers.py, tests/guardrails/test_tool_rails_e2e.py
Adds WEATHER_SCHEMA, assert_blocked, and make_tool_conversation shared fixtures; e2e tests build a real EngineRegistry+RailsManager stack with mocked aiohttp for both non-streaming JSON and SSE paths, asserting safety outcomes for allowed/blocked tool calls and linked/unlinked tool results.

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant EngineRegistry
  participant ModelEngine
  participant RailsManager
  participant ToolCallRailAction
  participant ToolResultRailAction

  rect rgba(70, 130, 180, 0.5)
    Note over Caller, ModelEngine: Tool parsing (pre-inference)
    Caller->>EngineRegistry: parse_tools(model_type, llm_params)
    EngineRegistry->>ModelEngine: parse_tools(llm_params)
    ModelEngine-->>EngineRegistry: Toolset
    EngineRegistry-->>Caller: Toolset
  end

  rect rgba(100, 160, 100, 0.5)
    Note over Caller, RailsManager: Tool call validation (post-inference)
    Caller->>RailsManager: are_tool_calls_safe(tool_calls, toolset)
    RailsManager->>ToolCallRailAction: run(toolset, tool_calls)
    Note over ToolCallRailAction: allowlist + jsonschema validation
    ToolCallRailAction-->>RailsManager: RailResult
    RailsManager-->>Caller: RailResult
  end

  rect rgba(180, 100, 70, 0.5)
    Note over Caller, ModelEngine: Tool result extraction
    Caller->>EngineRegistry: extract_tool_results(model_type, messages)
    EngineRegistry->>ModelEngine: extract_tool_results(messages)
    ModelEngine-->>EngineRegistry: list[ToolResult]
    EngineRegistry-->>Caller: list[ToolResult]
  end

  rect rgba(150, 80, 180, 0.5)
    Note over Caller, RailsManager: Tool result validation
    Caller->>RailsManager: are_tool_results_safe(tool_results, prior_calls)
    RailsManager->>ToolResultRailAction: run(tool_results, prior_calls)
    Note over ToolResultRailAction: call_id linkage + name + content checks
    ToolResultRailAction-->>RailsManager: RailResult
    RailsManager-->>Caller: RailResult
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • NVIDIA-NeMo/Guardrails#2016: Extends ModelEngine and message handling for OpenAI-style tool_calls, providing the underlying plumbing (tool-call data shapes, streaming delta assembly) that this PR's parsing, extraction, and validation rails build upon.

Suggested labels

enhancement, status: in review, size: L

Suggested reviewers

  • cparisien
  • Pouyanpi
🚥 Pre-merge checks | ✅ 5 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 38.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.
Title check ✅ Passed The PR title accurately describes the main feature being added: tool-calling rails to RailsManager and ModelRegistry components.
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 contains major changes (1,574+ LoC across 16 files adding tool-calling rails) with comprehensive test results documented: pre-commit all passed, 4752 unit tests passed/180 skipped, and integrati...

✏️ 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 feat/iorails-tool-calling-rails

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.

Actionable comments posted: 4

🤖 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 `@nemoguardrails/guardrails/actions/tool_result_action.py`:
- Around line 69-73: The isinstance check for result.content on line 69
currently accepts any list without validating the structure of items within it,
which allows lists containing non-parseable item types. Strengthen the
validation by adding an additional check after confirming content is a list:
iterate through the list items and verify that each item is a dict-like object
(isinstance check against dict). If any list item is not dict-like, return a
RailResult with is_safe=False and an appropriate reason message indicating
malformed list item structure. This ensures the rail enforces structural
validity for list content.
- Around line 50-60: Before building the calls_by_id lookup dictionary from
prior_calls, add validation to detect and reject duplicate non-empty IDs. Check
if any call.id values appear more than once in prior_calls and return a
RailResult with is_safe=False and an appropriate reason message when duplicates
are detected. Only proceed to construct the calls_by_id dictionary and process
tool_results after this validation passes.

In `@nemoguardrails/guardrails/model_engine.py`:
- Around line 313-324: The code is parsing function tools without validating
that each function has a valid non-empty name, which causes duplicate key errors
when creating a Toolset later. Add a validation check after the isinstance check
to verify that function.get("name") returns a non-empty value, and skip the
entry by using continue if the name is missing or empty, ensuring only
well-formed tools with valid names are appended to the parsed list.

In `@nemoguardrails/guardrails/rails_manager.py`:
- Around line 218-219: The dictionary comprehension building the `rails`
variable uses `flow` as the key, which means duplicate flow strings in
`self.tool_call_flows` will overwrite earlier entries and cause un-awaited
coroutines. Before the dict comprehension that calls
`self._run_tool_call_rail()`, either deduplicate `self.tool_call_flows` (by
converting to a set then back to a list) or add validation to ensure no
duplicates exist in the flows. Apply the same fix to the similar dictionary
comprehension at lines 231-232.
🪄 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: CHILL

Plan: Enterprise

Run ID: e839b17e-d34a-4974-a58c-0e53050c1834

📥 Commits

Reviewing files that changed from the base of the PR and between 321d742 and b64f337.

📒 Files selected for processing (16)
  • nemoguardrails/guardrails/actions/tool_call_action.py
  • nemoguardrails/guardrails/actions/tool_result_action.py
  • nemoguardrails/guardrails/engine_registry.py
  • nemoguardrails/guardrails/model_engine.py
  • nemoguardrails/guardrails/rails_manager.py
  • nemoguardrails/guardrails/tool_rail_action.py
  • nemoguardrails/guardrails/tool_schema.py
  • tests/guardrails/test_engine_registry.py
  • tests/guardrails/test_model_engine.py
  • tests/guardrails/test_rails_manager.py
  • tests/guardrails/test_tool_call_action.py
  • tests/guardrails/test_tool_rail_action.py
  • tests/guardrails/test_tool_rails_e2e.py
  • tests/guardrails/test_tool_result_action.py
  • tests/guardrails/test_tool_schema.py
  • tests/guardrails/tool_helpers.py

Comment thread nemoguardrails/guardrails/actions/tool_result_action.py Outdated
Comment thread nemoguardrails/guardrails/actions/tool_result_action.py Outdated
Comment thread nemoguardrails/guardrails/model_engine.py
Comment thread nemoguardrails/guardrails/rails_manager.py

@Pouyanpi Pouyanpi left a comment

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.

@tgasser-nv should be good to merge after fixing following potential bugs 👍🏻

Comment thread nemoguardrails/guardrails/actions/tool_call_action.py Outdated
Comment thread nemoguardrails/guardrails/model_engine.py
Comment thread nemoguardrails/guardrails/actions/tool_result_action.py Outdated
@Pouyanpi

Copy link
Copy Markdown
Collaborator

@tgasser-nv for the future PRs, please change the status from status: needs triage to status: triaged when you open a PR.

@tgasser-nv tgasser-nv added the status: triaged Triaged by a maintainer; eligible for automated review (CodeRabbit/Greptile). label Jun 23, 2026
@tgasser-nv tgasser-nv merged commit fd176fc into develop Jun 23, 2026
9 checks passed
@tgasser-nv tgasser-nv deleted the feat/iorails-tool-calling-rails branch June 23, 2026 16:55
@tgasser-nv tgasser-nv mentioned this pull request Jun 29, 2026
11 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size: XL status: triaged Triaged by a maintainer; eligible for automated review (CodeRabbit/Greptile).

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants