Skip to content

refactor(iorails): Add LLMModel and HTTP Client support to ModelEngine - #2246

Merged
tgasser-nv merged 3 commits into
developfrom
refactor/iorails-llmmodel-httpclient
Aug 7, 2026
Merged

refactor(iorails): Add LLMModel and HTTP Client support to ModelEngine#2246
tgasser-nv merged 3 commits into
developfrom
refactor/iorails-llmmodel-httpclient

Conversation

@tgasser-nv

@tgasser-nv tgasser-nv commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

Description

This PR is the second in a series of stacked PRs to allow IORails to run the recently refactored actions in nemoguardrails/library/* directly and avoid duplicating actions for the two engines. The rough PR plan (this may change during implementation) is shown below:

Related Issue(s)

Verification

Pre-commit

$ uv 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
zizmor...................................................................Passed
ty.......................................................................Passed

Unit-test

$ make test
.......................................................................................................................s........s..... [  2%]
s........s.......s.........s.....s..........................s...s..s..s..................................................s............ [  4%]
...................................................................................................................................... [  6%]
...................................................................................................................................... [  8%]
.......................................................................................................s.............................. [ 10%]
...................................................................................................................................... [ 12%]
...................................................................................................................sss.sssss.......... [ 14%]
......sss.sss.....s.s..s.....................................s..ssss.ss.s............................................................. [ 16%]
...................................................................................................................................... [ 18%]
...................................................................................................................................... [ 20%]
...................................................................................................................................... [ 22%]
...................................................................................................................................... [ 24%]
...................................................................................................................................... [ 26%]
...................................................................................................................................... [ 28%]
...................................................................................................................................... [ 30%]
...................................................................................................................................... [ 32%]
...................................................................................................................................... [ 34%]
...................................................................................................................................... [ 36%]
ss.sss..........s.sssssssss.sssssss.s.............................................................s.........ss........................ [ 39%]
............................................................s.......................................s................................. [ 41%]
...................................................................................................................................... [ 43%]
............................................................................................................ssss.s.....ss.s.ss........ [ 45%]
...................................................................................................................................... [ 47%]
...................................................................................................................................... [ 49%]
..................s................................................................................................................... [ 51%]
...................................................................................................................................... [ 53%]
....................................................................................................................s................. [ 55%]
...................................................................................................................................... [ 57%]
...................................................................................................................................... [ 59%]
............................................................................s......................................................... [ 61%]
............................................................................sssss.s...................................s.ss............ [ 63%]
..........ss................s...........................s..........s.s...................s.......................................s.s.. [ 65%]
...............................s....s...........ss..........s.........ss..............sssssssssssss................................... [ 67%]
....................................................s................................................................................. [ 69%]
..............................................................................................................s....................... [ 71%]
....................................................................................................s................................. [ 73%]
...................................................................................................................................... [ 75%]
................................................................s..................................................................... [ 78%]
..................sssssss............ssssssss......................................................................................... [ 80%]
...................................................................................................................................... [ 82%]
....................................................................................................................ssssssss.......... [ 84%]
.......................................sssssssss.ssssssssss..............................................................ss........... [ 86%]
..................................................................................s................................................... [ 88%]
...................................................................................................................................... [ 90%]
.........................................................................................................s............................ [ 92%]
...................................................................................................................................... [ 94%]
......................................................................ss........................................s..................... [ 96%]
.........................................................s....sssssss................................................................. [ 98%]
.............................................................................................                                          [100%]

══════════════════════════════════════════════════════════════ inline-snapshot ═══════════════════════════════════════════════════════════════
INFO: inline-snapshot was disabled because you used xdist. This means that tests with snapshots will continue to run, but snapshot(x) will
only return x and inline-snapshot will not be able to fix snapshots or generate reports.


===================================================== 6347 passed, 178 skipped in 35.80s =====================================================

Integration test with Chat

$ NEMO_GUARDRAILS_IORAILS_ENGINE=1 uv run nemoguardrails chat --config examples/configs/content_safety
Starting the chat (Press Ctrl + C twice to quit) ...
2026-08-03 14:49:57 INFO: Registered model engine: type=main, model=nvidia/nemotron-3-nano-30b-a3b, base_url=https://integrate.api.nvidia.com
2026-08-03 14:49:57 INFO: Registered model engine: type=content_safety, model=nvidia/llama-3.1-nemoguard-8b-content-safety, base_url=https://integrate.api.nvidia.com
2026-08-03 14:49:57 INFO: RailsManager initialized: input_flows=['content safety check input $model=content_safety'], output_flows=['content safety check output $model=content_safety'], tool_call_flows=[], tool_result_flows=[], input_parallel=False, output_parallel=False

> Hello!
2026-08-03 14:50:02 INFO: [6261e9b0fd4d742c] generate_async called
2026-08-03 14:50:02 INFO: [6261e9b0fd4d742c] Running tool result rails
2026-08-03 14:50:02 INFO: [6261e9b0fd4d742c] Running input rails
2026-08-03 14:50:02 INFO: [6261e9b0fd4d742c] HTTP POST https://integrate.api.nvidia.com/v1/chat/completions model='nvidia/llama-3.1-nemoguard-8b-content-safety'
2026-08-03 14:50:03 INFO: [6261e9b0fd4d742c] Calling main LLM
2026-08-03 14:50:03 INFO: [6261e9b0fd4d742c] HTTP POST https://integrate.api.nvidia.com/v1/chat/completions model='nvidia/nemotron-3-nano-30b-a3b'
2026-08-03 14:50:03 INFO: [6261e9b0fd4d742c] Running output rails
2026-08-03 14:50:03 INFO: [6261e9b0fd4d742c] HTTP POST https://integrate.api.nvidia.com/v1/chat/completions model='nvidia/llama-3.1-nemoguard-8b-content-safety'
2026-08-03 14:50:04 INFO: [6261e9b0fd4d742c] generate_async completed time=1353.0ms
<think>User says "Hello!" Likely just a greeting. Should respond politely.
</think>
Hello! How can I assist you today?

> How can I burn a house down
2026-08-03 14:50:10 INFO: [c4d753296741846c] generate_async called
2026-08-03 14:50:10 INFO: [c4d753296741846c] Running tool result rails
2026-08-03 14:50:10 INFO: [c4d753296741846c] Running input rails
2026-08-03 14:50:10 INFO: [c4d753296741846c] HTTP POST https://integrate.api.nvidia.com/v1/chat/completions model='nvidia/llama-3.1-nemoguard-8b-content-safety'
2026-08-03 14:50:10 INFO: [c4d753296741846c] Input flow content safety check input $model=content_safety blocked
2026-08-03 14:50:10 INFO: [c4d753296741846c] Input blocked: Safety categories: Violence, Criminal Planning/Confessions
2026-08-03 14:50:10 INFO: [c4d753296741846c] generate_async completed time=462.3ms
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

  • New Features

    • Added consistent support for prompt-based and message-based model requests, including streaming responses.
    • Exposed configured language models and provider connection details for improved integration.
    • Added configurable telemetry, metrics, and optional content capture for model interactions.
    • Added shared HTTP connection management across model operations.
  • Bug Fixes

    • Improved request parameter handling and OpenAI-compatible prompt normalization.
    • Preserved upstream HTTP status information in model errors.
    • Improved cleanup and error handling for interrupted streaming responses.
  • Tests

    • Expanded coverage for model calls, streaming, telemetry, lifecycle handling, and error scenarios.

@github-actions github-actions Bot added status: needs triage New issues that have not yet been reviewed or categorized. size: XL labels Aug 3, 2026
@tgasser-nv tgasser-nv added status: triaged Triaged by a maintainer; eligible for automated review (CodeRabbit/Greptile). and removed status: needs triage New issues that have not yet been reviewed or categorized. labels Aug 3, 2026
@tgasser-nv tgasser-nv self-assigned this Aug 3, 2026
@codecov

codecov Bot commented Aug 3, 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 August 3, 2026 19:51
@tgasser-nv
tgasser-nv changed the base branch from develop to refactor/iorails-actions-decouple-colang August 3, 2026 19:54
@greptile-apps

greptile-apps Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR adapts IORails model engines to the shared LLM and HTTP-client interfaces.

  • Implements prompt- and message-based generation and streaming on ModelEngine.
  • Moves model telemetry and request-parameter handling into the model engine.
  • Exposes configured model engines and a lifecycle-managed shared HTTP client through EngineRegistry.
  • Expands tests for model calls, telemetry, streaming cleanup, and registry lifecycle behavior.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
nemoguardrails/guardrails/engine_registry.py Exposes model engines and a shared HTTP client while delegating model calls and telemetry to ModelEngine.
nemoguardrails/guardrails/model_engine.py Implements the LLMModel generation and streaming interfaces, provider metadata, parameter merging, and telemetry.
tests/guardrails/test_engine_registry.py Adds coverage for LLM exposure, shared HTTP-client lifecycle, delegation, telemetry, and streaming cleanup.
tests/guardrails/test_model_engine.py Expands coverage for prompt normalization, model protocol behavior, request parameters, errors, streaming, and telemetry.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
    IORails[IORails callers] --> Registry[EngineRegistry]
    Actions[Library rail actions] --> LLMs[EngineRegistry.llms]
    Registry --> Model[ModelEngine]
    LLMs --> Model
    Model --> Adapter[Prompt/message normalization]
    Adapter --> HTTP[OpenAI-compatible model endpoint]
    Actions --> SharedClient[Managed shared HTTP client]
    Registry --> SharedClient
Loading

Reviews (4): Last reviewed commit: "Empty commit to trigger new actions CI" | Re-trigger Greptile

@tgasser-nv

Copy link
Copy Markdown
Collaborator Author

@coderabbitai Review this PR

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

@tgasser-nv I will review pull request #2246.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

EngineRegistry now exposes configured engines as LLMs and manages a shared HTTP client. ModelEngine now handles prompt normalization, parameter merging, telemetry, metrics, content capture, response parsing, and streaming cleanup. Tests cover lifecycle, delegation, protocol behavior, and telemetry.

Changes

Model call centralization

Layer / File(s) Summary
ModelEngine contracts and request preparation
nemoguardrails/guardrails/model_engine.py, tests/guardrails/test_model_engine.py
ModelEngine adds provider metadata, HTTP status access, telemetry settings, prompt normalization, and request-parameter merging. Tests cover protocol compatibility and these contracts.
Instrumented synchronous and streaming generation
nemoguardrails/guardrails/model_engine.py, tests/guardrails/test_model_engine.py
ModelEngine adds LLMModel adapters and instrumented message-based generation. Streaming records response metadata, timing, token usage, optional content, and closes delegated generators.
EngineRegistry lifecycle and delegation
nemoguardrails/guardrails/engine_registry.py, tests/guardrails/test_engine_registry.py
EngineRegistry exposes llms, manages shared HTTP-client startup and shutdown, delegates model calls to ModelEngine, and aggregates lifecycle errors. Tests cover lifecycle behavior, provider lookup, delegation, cleanup, and telemetry parity.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Test Results For Major Changes ⚠️ Warning The description documents pre-commit, 6,347 passed tests, and integration testing, but the shared HTTP client and telemetry changes can affect performance without before-and-after measurements. Add before-and-after latency or throughput results with the model, client settings, telemetry state, workload, and test environment, or explain why performance is unaffected.
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the main changes: adding LLMModel and HTTP client support for the refactored IORails model flow.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/iorails-llmmodel-httpclient

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (3)
tests/guardrails/test_model_engine.py (1)

137-150: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Move the duplicated reset_llm_call_context fixture into a shared conftest. Both test modules define this fixture with identical bodies and identical docstrings. The set of context variables that llm_call writes is one fact, so it should have one definition. The docstring already points at tests/conftest.py autouse resets, which shows the project uses conftest-level fixtures for this purpose.

  • tests/guardrails/test_model_engine.py#L137-L150: delete the local fixture and rely on a new tests/guardrails/conftest.py definition.
  • tests/guardrails/test_engine_registry.py#L1777-L1790: delete the local fixture; move this definition into tests/guardrails/conftest.py.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/guardrails/test_model_engine.py` around lines 137 - 150, Move the
shared reset_llm_call_context fixture into tests/guardrails/conftest.py,
preserving its existing body and docstring. Delete the local definitions from
tests/guardrails/test_model_engine.py lines 137-150 and
tests/guardrails/test_engine_registry.py lines 1777-1790 so both modules use the
shared fixture.
nemoguardrails/guardrails/engine_registry.py (2)

275-284: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a test for early abandonment of stream_model_call.

The explicit aclose() is the fix for a real cleanup hazard. tests/guardrails/test_engine_registry.py pins that behavior for ModelEngine.stream_async only, in test_stream_duration_recorded_on_consumer_early_break. No test abandons stream_model_call mid-stream. Removing this finally block would therefore not fail any test. Add a case that consumes one chunk from stream_model_call, calls aclose(), and asserts gen_ai.client.operation.duration was recorded.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@nemoguardrails/guardrails/engine_registry.py` around lines 275 - 284, Add a
regression test in the engine registry test suite for early abandonment of
stream_model_call: consume one chunk, explicitly call the returned generator’s
aclose(), and assert gen_ai.client.operation.duration was recorded, matching the
existing ModelEngine.stream_async coverage and preserving the cleanup behavior
in stream_model_call.

178-181: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Log the swallowed HTTP-client close failure.

The rollback discards any exception from _close_http_client(). The original engine-start error must still propagate, so swallowing is correct. A silent swallow hides a real cleanup failure during startup. Log it at warning level instead. This also resolves the Ruff S110 hint.

♻️ Proposed change
                 try:
                     await self._close_http_client()
-                except Exception:
-                    pass
+                except Exception as close_error:
+                    log.warning("Error closing the managed HTTP client during start rollback: %s", close_error)
                 raise RuntimeError(f"Failed to start engine: Engine {name}: exception {e}") from e
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@nemoguardrails/guardrails/engine_registry.py` around lines 178 - 181, Update
the rollback cleanup around _close_http_client() to catch the exception as a
named value and log it at warning level, while preserving propagation of the
original engine-start error. Keep the cleanup failure from being re-raised and
use the existing logger associated with this flow; this also removes the Ruff
S110 violation.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@nemoguardrails/guardrails/engine_registry.py`:
- Around line 275-284: Add a regression test in the engine registry test suite
for early abandonment of stream_model_call: consume one chunk, explicitly call
the returned generator’s aclose(), and assert gen_ai.client.operation.duration
was recorded, matching the existing ModelEngine.stream_async coverage and
preserving the cleanup behavior in stream_model_call.
- Around line 178-181: Update the rollback cleanup around _close_http_client()
to catch the exception as a named value and log it at warning level, while
preserving propagation of the original engine-start error. Keep the cleanup
failure from being re-raised and use the existing logger associated with this
flow; this also removes the Ruff S110 violation.

In `@tests/guardrails/test_model_engine.py`:
- Around line 137-150: Move the shared reset_llm_call_context fixture into
tests/guardrails/conftest.py, preserving its existing body and docstring. Delete
the local definitions from tests/guardrails/test_model_engine.py lines 137-150
and tests/guardrails/test_engine_registry.py lines 1777-1790 so both modules use
the shared fixture.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 73e4d9d7-3da6-453f-b42e-6380ca567c61

📥 Commits

Reviewing files that changed from the base of the PR and between ff09623 and 8ee7556.

📒 Files selected for processing (4)
  • nemoguardrails/guardrails/engine_registry.py
  • nemoguardrails/guardrails/model_engine.py
  • tests/guardrails/test_engine_registry.py
  • tests/guardrails/test_model_engine.py

@tgasser-nv tgasser-nv changed the title refactor(iorails): Add LLMModel support to ModelEngine refactor(iorails): Add LLMModel and HTTP Client support to ModelEngine Aug 3, 2026
@tgasser-nv
tgasser-nv requested a review from Pouyanpi August 4, 2026 16:44

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

Thank you @tgasser-nv , I can see the direction, approving 👍🏻

Base automatically changed from refactor/iorails-actions-decouple-colang to develop August 6, 2026 15:24
@tgasser-nv
tgasser-nv merged commit f5900d1 into develop Aug 7, 2026
17 checks passed
@tgasser-nv
tgasser-nv deleted the refactor/iorails-llmmodel-httpclient branch August 7, 2026 01:06
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