たきびAIラボ TAKIBI · AI · LAB
🛡️サイバーセキュリティ ハウツー 公開 2026.06.13

NeMo Guardrails × Guardrails AIでLLMエージェントにランタイムガードレールを実装する

入力/対話/検索/実行/出力の5レールColang設計ガイド

NeMo GuardrailsのColang DSLで5種類のランタイムレールを構成し、LlamaGuard 3との統合・LangChainエージェントへの組み込み・Guardrails AIとの役割分担を実装コード付きで解説。EU AI Act Article 12の監査ログ要件への対応方法も整理します。

読了 約25分
NeMo Guardrails × Guardrails AIでLLMエージェントにランタイムガードレールを実装する:入力/対話/検索/実行/出力の5レールColang設計ガイド

LLMエージェントを本番環境に投入するとき、「プロンプトで禁止ワードを並べておけばいい」という考え方は通用しなくなりつつあります。マルチターンの対話・RAGによる検索・ツール呼び出しを組み合わせたエージェントでは、入力と出力の検証だけでは追いきれないリスクが生まれます。

この記事では NeMo Guardrails(NVIDIA製のオープンソースランタイムガードレールフレームワーク)と Guardrails AI(出力スキーマ検証ライブラリ)の組み合わせ設計を解説します。Colang DSLで記述する5種類のレール(入力・対話・検索・実行・出力)の設計パターン、LlamaGuard 3との統合、LangChainエージェントへの組み込み手順、そしてEU AI Act Article 12が求める監査ログとの対応関係を実装コード付きで整理します。

NeMo GuardrailsのColangによる宣言型ルール制御と、LlamaGuard 4によるモデルベースの静的分類を組み合わせた防御設計についてはLlama Guard 4 + GARAKでLLMアプリの安全性を自動検証するも参照してください。

NeMo Guardrailsの動作モデルを理解する

NeMo Guardrailsはメインのチャットモデルの「前後」に立つプロキシレイヤーとして動作します。クライアントからリクエストが届くと、まずColangで定義したポリシーを評価し、ポリシーが許可した場合にだけメインLLMへの呼び出しが行われます。出力も同様に後処理レールを通過してからクライアントに返されます。

クライアント


[入力レール] → ブロック or 通過


[対話レール] → 許可トピック判定


[検索レール] → RAG取得結果フィルタ


メインLLM呼び出し


[実行レール] → ツール呼び出し制限


[出力レール] → 応答後処理


クライアント

このアーキテクチャの重要な特徴は、各レールがLLM呼び出しとは独立したPythonアクションとして実装できる点です。LlamaGuard 3のような特化型モデルをアクションとして登録すれば、メインLLMとは別の判定ロジックを各レールに挿入できます。

インストールと基本設定

pip install nemoguardrails
# LlamaGuard 3統合には transformers も必要
pip install transformers torch

プロジェクト構成はシンプルです。

myapp/
├── config/
│   ├── config.yml          # メインLLM設定
│   ├── rails/
│   │   ├── input.co        # 入力レール
│   │   ├── dialog.co       # 対話レール
│   │   ├── retrieval.co    # 検索レール
│   │   ├── execution.co    # 実行レール
│   │   └── output.co       # 出力レール
│   └── actions.py          # カスタムアクション
└── app.py

config.ymlの基本形:

models:
  - type: main
    engine: openai
    model: gpt-4o-mini

rails:
  input:
    flows:
      - check input safety
  dialog:
    user:
      single_call:
        enabled: false
    bot:
      single_call:
        enabled: false
  output:
    flows:
      - check output safety

5種類のColangレールを設計する

入力レール:有害コンテンツとPIIの検知

入力レールはユーザーの発話がメインLLMに到達する前にフィルタをかけます。Colangはdefine flowブロックで処理の流れを宣言します。

# config/rails/input.co

define flow check input safety
  $input_safe = execute check_input_with_llamaguard
  if not $input_safe
    bot refuse to respond
    stop

define bot refuse to respond
  "申し訳ありませんが、このリクエストにはお答えできません。"

対応するPythonアクション(config/actions.py):

from nemoguardrails.actions import action
from transformers import AutoTokenizer, AutoModelForCausalLM
import torch

# LlamaGuard 3モデルをアクション起動時に一度だけロード
_tokenizer = None
_model = None

def _load_model():
    global _tokenizer, _model
    if _model is None:
        model_id = "meta-llama/Llama-Guard-3-8B"
        _tokenizer = AutoTokenizer.from_pretrained(model_id)
        _model = AutoModelForCausalLM.from_pretrained(
            model_id, torch_dtype=torch.bfloat16, device_map="auto"
        )

@action(name="check_input_with_llamaguard")
async def check_input_with_llamaguard(context: dict) -> bool:
    """LlamaGuard 3で入力の安全性を判定する。True=安全"""
    _load_model()
    user_message = context.get("user_message", "")
    chat = [{"role": "user", "content": user_message}]
    input_ids = _tokenizer.apply_chat_template(
        chat, return_tensors="pt"
    ).to(_model.device)
    with torch.no_grad():
        output = _model.generate(input_ids, max_new_tokens=20, pad_token_id=0)
    result = _tokenizer.decode(
        output[0][input_ids.shape[-1]:], skip_special_tokens=True
    ).strip()
    return result.lower().startswith("safe")

対話レール:許可トピックの宣言的制限

対話レールはマルチターン対話のトピック制御に使います。「このエージェントは採用HR業務専用。競合他社への言及や法律相談には応答しない」というようなビジネスルールをColangで表現できます。

# config/rails/dialog.co

define user ask about competitor
  "競合他社の製品について教えて"
  "他社と比べてどうですか"
  "◯◯社のサービスは"

define bot refuse competitor topic
  "競合他社に関するご質問にはお答えする立場にありません。"
  "弊社サービスについてお手伝いできることがあればお知らせください。"

define flow handle competitor questions
  user ask about competitor
  bot refuse competitor topic
  stop

define user ask for legal advice
  "法的に問題ありますか"
  "これは違法ですか"
  "訴訟リスクは"

define flow refuse legal advice
  user ask for legal advice
  bot inform legal disclaimer
  stop

define bot inform legal disclaimer
  "法律的なアドバイスは専門の弁護士にご相談ください。"

対話レールはColangのパターンマッチングを使います。定義した発話例をもとに、内部の意図分類モデルが類似した発話を判定します。これはルールベースの完全一致ではなくセマンティックな類似度判定なので、表現ゆれにも対応できます。

検索レール:RAG取得結果のフィルタリング

検索レールはRAGパイプラインが取得したドキュメントを検証します。信頼性の低いソースや古いドキュメントをメインLLMに渡さないための仕組みです。

# config/rails/retrieval.co

define flow check retrieved chunks
  $chunks_safe = execute validate_retrieved_chunks
  if not $chunks_safe
    bot inform retrieval issue
    stop

define bot inform retrieval issue
  "現在の検索結果には信頼性の確認が取れない情報が含まれていたため、回答を保留しています。"
@action(name="validate_retrieved_chunks")
async def validate_retrieved_chunks(context: dict) -> bool:
    """取得チャンクの信頼性検証。True=問題なし"""
    chunks = context.get("retrieved_chunks", [])
    for chunk in chunks:
        # メタデータによるソース検証
        source = chunk.get("metadata", {}).get("source", "")
        if not _is_trusted_source(source):
            return False
        # 更新日が古いドキュメントを除外
        last_updated = chunk.get("metadata", {}).get("updated_at")
        if last_updated and _is_stale(last_updated, days=365):
            return False
    return True

実行レール:ツール呼び出し制限

AIエージェントがコードインタープリタやデータベース、外部APIなどのツールを呼び出せる場合、実行レールが重要な防衛線になります。

# config/rails/execution.co

define flow check tool call
  $tool_name = $action.name
  $tool_allowed = execute check_tool_permission
  if not $tool_allowed
    bot refuse tool execution
    stop

define bot refuse tool execution
  "このツールの実行権限がありません。"
ALLOWED_TOOLS = {"search_knowledge_base", "get_employee_info", "send_notification"}

@action(name="check_tool_permission")
async def check_tool_permission(context: dict) -> bool:
    """ツール実行権限チェック。True=実行許可"""
    action_info = context.get("action", {})
    tool_name = action_info.get("name", "")
    user_role = context.get("user_role", "standard")
    
    if tool_name not in ALLOWED_TOOLS:
        return False
    
    # 管理者専用ツールのロールチェック
    admin_only = {"get_all_employee_data", "bulk_send_notification"}
    if tool_name in admin_only and user_role != "admin":
        return False
    
    return True

出力レール:応答の後処理チェック

出力レールはメインLLMの応答をクライアントに返す直前にチェックします。ここでLlamaGuard 3を再度使う二重チェック構成も有効です。

# config/rails/output.co

define flow check output safety
  $output_safe = execute check_output_with_llamaguard
  if not $output_safe
    bot provide safe response
    stop

define bot provide safe response
  "安全な応答を提供できませんでした。内容を変更してもう一度お試しください。"

LangChainエージェントへの組み込み手順

LangChainエージェントにNeMo Guardrailsを組み込む方法は2つあります。ラッパーパターン(LangChainのcallbackを使う)とプロキシパターン(NeMo GuardrailsをLangChainのLLMラッパーとして使う)です。ここではより統合度の高いプロキシパターンを示します。

from nemoguardrails import RailsConfig, LLMRails
from langchain_openai import ChatOpenAI
from langchain.agents import AgentExecutor, create_openai_tools_agent
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.tools import tool
import asyncio

# NeMo Guardrailsの初期化
config = RailsConfig.from_path("./config")
rails = LLMRails(config)

# LangChainツールの定義
@tool
def search_knowledge_base(query: str) -> str:
    """社内ナレッジベースを検索する"""
    # 実際の検索ロジック
    return f"検索結果: {query}に関する情報..."

tools = [search_knowledge_base]

# NeMo GuardrailsをLLMラッパーとして利用するカスタムクラス
class GuardrailsChatModel:
    """NeMo Guardrailsを通じてチャットモデルを呼び出すラッパー"""
    
    def __init__(self, rails: LLMRails):
        self._rails = rails
    
    async def ainvoke(self, messages: list, **kwargs) -> str:
        # LangChainのメッセージ形式をNeMo Guardrails形式に変換
        formatted = [
            {"role": m.type if m.type != "human" else "user", "content": m.content}
            for m in messages
        ]
        response = await self._rails.generate_async(messages=formatted)
        return response

# エージェントの組み込み実装例
async def run_guarded_agent(user_input: str, user_role: str = "standard"):
    """NeMo Guardrailsを通じてエージェントを実行する"""
    
    # コンテキストにユーザーロールを設定
    context = {"user_role": user_role}
    
    response = await rails.generate_async(
        messages=[{"role": "user", "content": user_input}],
        context=context
    )
    
    return response

# 使用例
if __name__ == "__main__":
    result = asyncio.run(
        run_guarded_agent("採用候補者の一覧を教えてください", user_role="hr_manager")
    )
    print(result)

Guardrails AIとの役割分担を設計する

NeMo GuardrailsとGuardrails AIは名前が似ていますが役割が異なります。本番システムでは組み合わせて使うのが実務的です。

観点NeMo GuardrailsGuardrails AI
主な役割対話フロー制御・エージェントレール出力スキーマ検証・型強制
定義方法Colang DSL(宣言型ポリシー)Pythonバリデータクラス
動作タイミングリクエスト前後・対話中LLM出力後の後処理
向いている用途マルチターン対話制御・ツール権限・トピック制限JSON構造強制・数値範囲検証・PII検出
マルチエージェント対応対話フロー全体のオーケストレーション個別エージェントの出力検証

実装パターンとして、NeMo Guardrailsの出力レールでGuardrails AIのバリデーターを呼び出す構成を取ることができます。

from guardrails import Guard
from guardrails.hub import ValidJson, DetectPII
import json

# Guardrails AIのガードを定義
guard = Guard().use_many(
    ValidJson(on_fail="reask"),
    DetectPII(pii_entities=["EMAIL_ADDRESS", "PHONE_NUMBER"], on_fail="fix")
)

@action(name="validate_output_schema")
async def validate_output_schema(context: dict) -> dict:
    """Guardrails AIで出力スキーマを検証・修正する"""
    bot_response = context.get("bot_message", "")
    
    try:
        validated_output, metadata, *_ = guard.parse(bot_response)
        return {
            "valid": True,
            "output": validated_output,
            "metadata": metadata
        }
    except Exception as e:
        return {
            "valid": False,
            "error": str(e),
            "output": None
        }

この役割分担により、対話の流れはNeMo Guardrailsが制御し、出力データの型安全性はGuardrails AIが保証するという2層構造が実現します。

EU AI Act Article 12との対応:監査ログ設計

EU AI Act Article 12は「高リスクAIシステム」の提供者に対して、十分な透明性を確保した記録と監査可能なログの保管を求めています。NeMo Guardrailsには組み込みのログ機能があり、これをEU AI Act対応の監査証跡として活用できます。

import logging
import json
from datetime import datetime, timezone
from nemoguardrails import RailsConfig, LLMRails
from nemoguardrails.logging import verbose

# 構造化監査ログの設定
audit_logger = logging.getLogger("guardrails.audit")
audit_logger.setLevel(logging.INFO)

handler = logging.FileHandler("audit/guardrails_audit.jsonl")
handler.setFormatter(logging.Formatter('%(message)s'))
audit_logger.addHandler(handler)

class AuditedRails:
    """監査ログ付きNeMo Guardrailsラッパー"""
    
    def __init__(self, config_path: str, system_id: str):
        self._config = RailsConfig.from_path(config_path)
        self._rails = LLMRails(self._config)
        self._system_id = system_id
    
    async def generate_with_audit(
        self,
        messages: list,
        session_id: str,
        user_id: str,
        context: dict = None
    ) -> dict:
        start_time = datetime.now(timezone.utc)
        context = context or {}
        
        response = await self._rails.generate_async(
            messages=messages,
            context=context,
            return_context=True  # レール評価結果を含むコンテキストを返す
        )
        
        # 監査ログエントリを構造化して記録
        audit_entry = {
            "timestamp": start_time.isoformat(),
            "system_id": self._system_id,
            "session_id": session_id,
            "user_id": user_id,
            "input": messages[-1].get("content", "")[:200],  # 先頭200文字
            "input_rail_triggered": context.get("input_rail_triggered", False),
            "dialog_rail_triggered": context.get("dialog_rail_triggered", False),
            "output_rail_triggered": context.get("output_rail_triggered", False),
            "response_blocked": context.get("response_blocked", False),
            "latency_ms": (datetime.now(timezone.utc) - start_time).microseconds // 1000
        }
        
        audit_logger.info(json.dumps(audit_entry, ensure_ascii=False))
        
        return {
            "response": response if isinstance(response, str) else response.get("content", ""),
            "audit": audit_entry
        }

監査ログで記録すべき項目

EU AI Act対応の観点から、NeMo Guardrailsの監査ログには以下の項目を含めることを推奨します。

記録項目対応するEU AI Act要件実装場所
リクエスト受信タイムスタンプ(UTC)時系列の追跡ラッパーで付与
セッションID・ユーザーID個人データ処理の追跡コンテキストから取得
トリガーされたレール種別コンテンツポリシー施行の記録Guardrailsコンテキスト
ブロック/通過の判定結果自動化された意思決定の透明性レール評価結果
使用モデルとバージョン説明責任config.ymlから
レイテンシ(ms)パフォーマンス記録タイムスタンプ差分

実務での使い方と注意点

段階的なレール導入戦略

本番環境へ一度に全レールを導入するのはリスクがあります。誤検知(false positive)によって正当なリクエストがブロックされると、ユーザーエクスペリエンスと信頼性に影響します。以下の順序で段階的に導入することを推奨します。

  1. モニタリングモードから始める:レールは評価するが実際にはブロックしない。ログだけ取って誤検知率を把握する
  2. 入力レールのみ有効化:明らかに有害なリクエストだけをブロックするところから始める
  3. 出力レールを追加:入力レールが安定したら出力後処理を追加
  4. 対話・実行レールを追加:ビジネスルールに基づく制限を段階的に追加
# モニタリングモードの実装例
@action(name="check_input_monitor_mode")
async def check_input_monitor_mode(context: dict) -> bool:
    """入力チェック(モニタリングモード:常にTrue=通過を返す)"""
    result = await _run_llamaguard_check(context)
    
    # 結果をログに記録するが、ブロックはしない
    audit_logger.warning(json.dumps({
        "mode": "monitoring",
        "would_have_blocked": not result,
        "timestamp": datetime.now(timezone.utc).isoformat()
    }))
    
    return True  # モニタリングモードでは常に通過させる

パフォーマンスへの影響を最小化する

LlamaGuard 3をオンプレミスで実行する場合、推論レイテンシが全体のレスポンスタイムに影響します。対策として以下を検討してください。

  • NVIDIA NIM APIを使ってガードレールモデルをマイクロサービスとして分離する
  • 入力の類似度キャッシュを使って同一または近似リクエストの再評価を省く
  • 対話レールの意図分類には軽量な埋め込みモデルを使い、重い分類はLlamaGuardに絞る

よくある質問(FAQ)

FAQ

NeMo Guardrailsに関するよくある質問

クリックで展開。

NeMo Guardrailsはどのようなライセンスで公開されていますか?

Apache License 2.0で公開されています。商用利用も可能ですが、LlamaGuard 3を組み込む場合はMeta社のLlama Guardモデルライセンス(Llama 3 Community License)も別途確認が必要です。

OpenAI以外のLLM(Claudeなど)と組み合わせられますか?

はい。config.ymlのmodelsセクションでengineをanthropicやlitellmに設定することで、Claude・Gemini・Mistralなど多数のLLMと組み合わせられます。NeMo Guardrailsはモデルに依存しないミドルウェア層として設計されています。

Guardrails AIはNeMo Guardrailsの代替ですか?

代替ではなく補完関係です。NeMo Guardrailsは対話フロー全体のオーケストレーション(どの話題を許可するか、どのツールを呼び出せるか)を担います。Guardrails AIはLLMの出力をPythonオブジェクトとして型安全に扱うための検証レイヤーです。両者を組み合わせるのが実務的です。

EU AI Act Article 12の対応としてNeMo Guardrailsのログだけで十分ですか?

NeMo Guardrailsのログはコンテンツポリシー施行の証跡として機能しますが、Article 12が求める記録はシステム全体を対象とします。入力データの記録・出力の追跡・システム変更履歴など、NeMo Guardrailsの範囲外の記録も別途管理が必要です。法務・コンプライアンス担当者と連携して対応範囲を確認してください。

LangChainエージェントのツール呼び出しをNeMo Guardrailsで制限する際、どのツールを登録すべきですか?

実行レールのALLOWED_TOOLSには、エージェントが本当に必要とするツールだけを最小限で登録することを推奨します。OWASP LLM06:2025「Excessive Agency」が指摘するように、不必要なツールアクセスを与えることがエージェントの過剰な自律性につながります。ツールリストは定期的に棚卸しを行い、使われていないものは除外してください。

まとめ

NeMo GuardrailsのColang DSLによる5レール設計は、LLMエージェントのランタイム安全制御において強力な選択肢です。LlamaGuard 3との統合でモデルベースの精度を加え、Guardrails AIと役割分担することで出力スキーマの型安全性も確保できます。LangChainエージェントへの組み込みはプロキシパターンで実現でき、EU AI Act対応の監査ログは構造化JSONLで記録する設計が追跡可能性を高めます。

段階的な導入(モニタリングモード→入力レール→出力レール→対話・実行レール)が誤検知リスクを抑えながら本番投入するための現実的な道筋です。

次に読むおすすめ

【2026年最新】主要AIツール5つに自腹課金して徹底比較!生産性が劇的に変わる「用途別・最強の1つ」の選び方 — LLMアプリのセキュリティ設計と合わせて、実務でどのAIツールを使うべきか比較した解説記事です。noteで続きを読む。

関連記事

参考リンク