Skip to content

feat(integration): add GuardrailsMiddleware for LangChain agent#1606

Merged
Pouyanpi merged 7 commits intodevelopfrom
feat/guardrails-middleware
Feb 9, 2026
Merged

feat(integration): add GuardrailsMiddleware for LangChain agent#1606
Pouyanpi merged 7 commits intodevelopfrom
feat/guardrails-middleware

Conversation

@Pouyanpi
Copy link
Collaborator

#‪# Description

Add a new GuardrailsMiddleware class for seamless integration with LangChain's Agent Middleware system. The middleware intercepts agent conversations to apply NeMo Guardrails input/output validation before and after model calls.

This follows the LangChain Middleware pattern introduced in LangChain 1.0, providing before_model and after_model hooks for precise control over the agent loop.

Key features:

  • GuardrailsMiddleware - Full middleware with both input and output rails
  • InputRailsMiddleware - Input-only validation
  • OutputRailsMiddleware - Output-only validation
  • GuardrailViolation exception for programmatic error handling
  • Configurable blocking behavior (raise exception vs return blocked message)
  • Support for both config_path and config_yaml initialization

Usage example:

from langchain.agents import create_agent
from nemoguardrails.integrations.langchain.middleware import GuardrailsMiddleware

guardrails = GuardrailsMiddleware(
    config_path="./config",
    raise_on_violation=False,
)

agent = create_agent("openai:gpt-4o", middleware=[guardrails])

References

@codecov
Copy link

codecov bot commented Jan 29, 2026

Codecov Report

❌ Patch coverage is 89.61039% with 16 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...emoguardrails/integrations/langchain/middleware.py 88.23% 16 Missing ⚠️

📢 Thoughts on this report? Let us know!

@Pouyanpi Pouyanpi force-pushed the feat/guardrails-middleware branch from 8ed8285 to 49c598e Compare January 29, 2026 12:12
@Pouyanpi Pouyanpi changed the title Feat/guardrails middleware feat(integration): add GuardrailsMiddleware for LangChain agent Jan 29, 2026
@Pouyanpi Pouyanpi changed the base branch from develop to feat/check-methods January 29, 2026 12:41
@Pouyanpi Pouyanpi self-assigned this Feb 3, 2026
@Pouyanpi Pouyanpi added this to the v0.21 milestone Feb 3, 2026
@Pouyanpi Pouyanpi added the enhancement New feature or request label Feb 3, 2026
@Pouyanpi Pouyanpi marked this pull request as ready for review February 3, 2026 14:51
@greptile-apps
Copy link
Contributor

greptile-apps bot commented Feb 3, 2026

Greptile Overview

Greptile Summary

This PR adds a new GuardrailsMiddleware class for seamless integration with LangChain's Agent Middleware system, enabling NeMo Guardrails input/output validation in the agent loop.

Major Changes:

  • Introduced GuardrailsMiddleware base class with configurable input/output rails
  • Added InputRailsMiddleware and OutputRailsMiddleware specialized classes
  • Created GuardrailViolation exception for programmatic error handling
  • Implemented both async (abefore_model, aafter_model) and sync (before_model, after_model) hooks
  • Added comprehensive unit tests (1195 lines) and E2E tests (551 lines)
  • Updated dependencies to support LangChain agent middleware integration

Implementation Quality:

  • Well-structured code following separation of concerns
  • Proper error handling with fallback mechanisms
  • Extensive test coverage including security scenarios
  • Clean integration with existing message_utils utilities

Issue Found:

  • Method naming inconsistency in OutputRailsMiddleware.before_agent (line 260) - should be before_model to match the parent class signature

Confidence Score: 4/5

  • Safe to merge after fixing the method naming issue in OutputRailsMiddleware
  • The implementation is well-designed with comprehensive tests, but there's a method naming inconsistency that could cause issues at runtime
  • nemoguardrails/integrations/langchain/middleware.py - fix method name on line 260

Important Files Changed

Filename Overview
nemoguardrails/integrations/langchain/exceptions.py New exception class for guardrail violations, simple and well-structured
nemoguardrails/integrations/langchain/middleware.py New middleware implementation with input/output rails integration - has one naming inconsistency issue
tests/integrations/langchain/test_middleware.py Comprehensive unit tests with mocks covering all middleware scenarios
tests/integrations/langchain/test_middleware_e2e.py E2E tests with real guardrails configuration, thorough security scenario coverage

Sequence Diagram

sequenceDiagram
    participant User
    participant Agent as LangChain Agent
    participant GM as GuardrailsMiddleware
    participant Rails as LLMRails
    participant Model as LLM

    User->>Agent: Send message
    Agent->>GM: before_model(state, runtime)
    GM->>GM: Check enable_input_rails
    GM->>GM: Check _has_input_rails()
    GM->>GM: Convert messages to dicts
    GM->>Rails: check_async(messages)
    Rails-->>GM: RailsResult (PASSED/BLOCKED)
    
    alt Input Blocked
        GM->>GM: _handle_guardrail_failure()
        alt raise_on_violation=True
            GM-->>Agent: Raise GuardrailViolation
        else raise_on_violation=False
            GM->>GM: Create blocked AI message
            GM-->>Agent: Return {messages, jump_to: "end"}
        end
    else Input Passed
        GM-->>Agent: Return None (continue)
        Agent->>Model: Call LLM
        Model-->>Agent: Generate response
        Agent->>GM: after_model(state, runtime)
        GM->>GM: Check enable_output_rails
        GM->>GM: Check _has_output_rails()
        GM->>GM: Get last AI message
        GM->>GM: Convert messages to dicts
        GM->>Rails: check_async(messages)
        Rails-->>GM: RailsResult (PASSED/BLOCKED)
        
        alt Output Blocked
            GM->>GM: _handle_guardrail_failure()
            alt raise_on_violation=True
                GM-->>Agent: Raise GuardrailViolation
            else raise_on_violation=False
                GM->>GM: Replace last message with blocked message
                GM-->>Agent: Return {messages: modified}
            end
        else Output Passed
            GM-->>Agent: Return None (continue)
            Agent-->>User: Return response
        end
    end
Loading

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

4 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Copy link
Collaborator

@tgasser-nv tgasser-nv left a comment

Choose a reason for hiding this comment

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

Can you run a local integration test to make sure this works ?

@Pouyanpi
Copy link
Collaborator Author

Pouyanpi commented Feb 4, 2026

@tgasser-nv I've done proper e2e tests and verifications. Will share them with QA 👍🏻

Base automatically changed from feat/check-methods to develop February 5, 2026 06:43
@Pouyanpi Pouyanpi force-pushed the feat/guardrails-middleware branch from 165b58e to ce01f0d Compare February 5, 2026 08:38
Copy link
Collaborator

@tgasser-nv tgasser-nv left a comment

Choose a reason for hiding this comment

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

Looks good, thanks for updating the Runtime argument. Can you make sure QA have good coverage on these methods for integration testing?

Add a new `check_async` method to `LLMRails` that allows standalone
validation of messages against input/output rails without requiring a
full conversation flow.

**Key features:**
- Automatically determines which rails to run based on message roles:
  - User messages only → input rails
  - Assistant messages only → output rails
  - Both user and assistant → input and output rails
- Returns a simple `RailsResult` with status (PASSED/MODIFIED/BLOCKED),
content, and blocking rail name
…age replacement

- Add RailType enum (INPUT, OUTPUT) to options.py
- Add optional rail_types parameter to check_async/check to override
auto-detection
- Middleware now passes rail_types=[RailType.INPUT] from abefore_model
and
  rail_types=[RailType.OUTPUT] from aafter_model
- Fix _replace_last_ai_message to find actual AIMessage index instead of
  assuming messages[-1]
- Add unit tests for explicit rail type passing and message replacement
@Pouyanpi Pouyanpi force-pushed the feat/guardrails-middleware branch from 2618f46 to cb62b05 Compare February 9, 2026 14:29
@Pouyanpi Pouyanpi merged commit e999078 into develop Feb 9, 2026
7 checks passed
@Pouyanpi Pouyanpi deleted the feat/guardrails-middleware branch February 9, 2026 14:41
Pouyanpi added a commit that referenced this pull request Feb 9, 2026
* feat(langchain): add GuardrailsMiddleware for LangChain agent integration
* feat(middleware): add explicit rail_types to check_async and fix message replacement

- Add RailType enum (INPUT, OUTPUT) to options.py
- Add optional rail_types parameter to check_async/check to override
auto-detection
- Middleware now passes rail_types=[RailType.INPUT] from abefore_model
and
  rail_types=[RailType.OUTPUT] from aafter_model
- Fix _replace_last_ai_message to find actual AIMessage index instead of
  assuming messages[-1]
- Add unit tests for explicit rail type passing and message replacement
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants