防衛線の構築:直列ガードレールとコントロールプレーン

直列ガードレールは、単一のリクエストが複数の検査ゲートを定められた順序で通過させる構造です。コントロールプレーンは、そのゲートを直接通過する経路ではなく、どのポリシーとモデルをどの順序で実行するかをデプロイし、バージョンを管理する制御レイヤーです。

これまでの演習では、NeMo Guardrails、Nova Liteベースの安全性分類、Microsoft Presidioをそれぞれ確認しました。今、重要な質問は「いくつのツールを有効にしたか」ではなく、「誰が順序を決定し、失敗をどのように統合し、ポリシー変更をどのように追跡するか」です。この記事では、合成テストケースで直列データプレーンを作成し、小さなコントロールプレーン契約を追加します。

ユーザーリクエストが複数のセキュリティゲートを直列に通過し、上位のコントロールプレーンがポリシーと状態を管理する様子

まず、データプレーンとコントロールプレーンを区別しよう

データプレーンは、実際のユーザーリクエスト、検索ドキュメント、モデル応答が流れる実行経路です。コントロールプレーンは、ポリシーバンドル、プロンプトバージョン、モデル設定、しきい値、およびデプロイ状態を管理します。コントロールプレーンに障害が発生したからといって、検証されていない新しいポリシーをすぐにダウンロードしたり、すべてのリクエストを許可したりしてはなりません。

区分 担当業務 扱ってはいけないこと
— — —
データプレーン リクエスト検査、RAG精製、LLM・ツール実行、出力検査 任意のポリシー変更と承認なしのデプロイ
コントロールプレーン ポリシーバージョン、承認、署名・ハッシュ、デプロイ、ロールバック ユーザーリクエスト原文と最終業務権限
アプリケーション 認証、テナント、業務権限、最終承認、監査 LLMへのアクセス権限判断委任
NeMoランタイム レール・アクション・検索・LLMフロー調整 ユーザー最終認可の単独判断

ここでいうコントロールプレーンは、NVIDIA製品の別途必須構成要素という意味ではありません。NeMo公式ドキュメントはconfig.yml、Prompt、Colang Flow、およびRuntime Orchestrationを説明しています。中央ポリシーリポジトリと承認・デプロイ手順は、これらを運用するためのこの記事の推奨設計案です。

推奨される直列フロー

Browser
  ↓
Application: 인증·Tenant·요청 한도
  ↓
Nova Lite 분류: 명시적 Safety Category
  ↓
NeMo Input Rail: Self-check·Jailbreak·주제 정책
  ↓
Retrieval API: Tenant Filter와 Security Trimming
  ↓
NeMo Retrieval Rail: Presidio로 원문 Chunk 정제
  ↓
Dialog·Execution Rail: 대화 흐름과 Tool 호출 검증
  ↓
Main LLM
  ↓
Output Rail: Safety·PII·형식 검사
  ↓
Application: 최종 업무 권한·감사·응답

NVIDIAの公式ドキュメントでは、Input RailがメインLLMの前にインプットを検査し、Retrieval Railが検索されたドキュメントとチャンクをプロンプトに挿入する前に処理し、Execution Railがアクションとツールを制御し、Output Railが応答を返す前に検査すると説明されています。正確な実行順序は、選択されたエンジンと設定によって異なる場合があります。

直列構造の利点は、前段でブロックすれば後続の高価な呼び出しを省略でき、どのゲートが決定したかを説明しやすい点です。欠点は、各段階の遅延時間が加算され、複数のポリシーが同じリクエストを重複してブロックする可能性があり、前段の障害が全体の可用性に影響を与える可能性がある点です。

演習1:共通判定契約の作成

ツールごとに結果形式が異なると、結合コードが例外を許可に変換しやすくなります。まず、すべてのAdapterが同じ形式を返すようにします。

from dataclasses import dataclass, field
from typing import Literal

Decision = Literal["ALLOW", "BLOCK", "MODIFY", "ERROR"]

@dataclass
class GuardrailResult:
    decision: Decision
    stage: str
    rule_id: str
    policy_version: str
    sanitized_text: str | None = None
    labels: list[str] = field(default_factory=list)

reasonにはユーザーの原文や検出された個人情報をコピーしないでください。運用記録には、Stage、Rule ID、Decision、Policy Version、Latency、および相関IDのみを残します。

演習2:直列パイプラインの組み立て

以下のコードは、実際のプロバイダーの代わりに同じインターフェースを持つ非同期アダプターを接続するための骨格です。各関数内にNova、NeMo、Presidioの呼び出しを挿入します。

import asyncio

class Blocked(Exception):
    def __init__(self, result: GuardrailResult):
        self.result = result

async def enforce(result: GuardrailResult, text: str) -> str:
    if result.decision in {"BLOCK", "ERROR"}:
        raise Blocked(result)
    if result.decision == "MODIFY":
        if result.sanitized_text is None:
            raise Blocked(GuardrailResult(
                "ERROR", result.stage, "missing_sanitized_text",
                result.policy_version,
            ))
        return result.sanitized_text
    return text

async def run_pipeline(user_text: str, tenant_id: str) -> str:
    text = await enforce(await nova_classify(user_text), user_text)
    text = await enforce(await nemo_input_check(text), text)

    chunks = await authorized_retrieve(
        query=text,
        tenant_id=tenant_id,
    )
    safe_chunks = await presidio_sanitize_chunks(chunks)

    tool_plan = await nemo_dialog_and_execution(text, safe_chunks)
    answer = await call_main_llm(text, safe_chunks, tool_plan)
    answer = await enforce(await nemo_output_check(answer), answer)
    return answer

この例では、ERRORをデフォルトでブロックします。読み取り専用FAQのように可用性が重要なサービスは、RAGとツールを削除した制限モードに切り替えることができますが、機密情報や書き込み権限を持つエージェントでは、フェイルクローズが安全なデフォルト値です。

演習3:NeMo設定の順序を可視化する

NeMoのconfig.ymlでは、アクティブなRail Flowが宣言されます。公式設定ドキュメントでは、InputとOutput Flowを順序立ててリストできること、および一部の環境では並列実行もサポートされていると説明されています。

rails:
  input:
    parallel: false
    flows:
      - self check input
      - check jailbreak
      - mask sensitive data on input

  retrieval:
    flows:
      - check retrieval sensitive data

  output:
    parallel: false
    flows:
      - self check output
      - check output sensitive data

tracing:
  enabled: true
  enable_content_capture: false

入力を変更するRailは、順序によって次の検査結果が変わります。NVIDIAの公式ドキュメントも、Input Rail Mutationを並列実行すると競合条件により結果が変わる可能性があるため、このような場合は直列実行を使用するよう警告しています。独立した読み取り専用分類器を並列化することは可能ですが、パフォーマンスを理由に意味が変わらないか回帰テストが必要です。

また、NeMo TracingのMessage Content Captureはデフォルトでオフになっています。運用で原文キャプチャをオンにすると、プロンプト、応答、ツール結果がトレースに複製される可能性があるため、別途承認・保存期間・アクセス制御なしに有効化しないでください。

演習4:最小のコントロールプレーン契約

コントロールプレーンは、実行コードが任意の最新ファイルを受け取るダウンロードサーバーではありません。承認されたポリシーバンドルのバージョンとハッシュを提供し、データプレーンは起動時に検証されたバンドルのみをアクティブにします。

bundle_id: llm-guardrails-prod
version: 2026.09.03-1
status: approved
minimum_runtime: "0.18"
default_failure_mode: fail_closed

stages:
  - id: nova-input-safety
    policy: policies/nova-input-v3.txt
    timeout_ms: 1200
  - id: nemo-input
    policy: configs/nemo/config.yml
    timeout_ms: 1800
  - id: presidio-retrieval
    policy: policies/pii-entities-v5.yml
    timeout_ms: 500

artifacts:
  manifest_sha256: "배포 과정에서 계산한 값"

マニフェストには、実際のシークレット、APIキー、個人情報サンプルを含めないでください。バンドルはGitのプルリクエストや承認ワークフローを経て不変のアーティファクトとして作成し、データプレーンは許可された署名者・ハッシュ・ランタイムバージョンを検証します。

演習5:デプロイとロールバック状態の作成

ポリシーのデプロイはコードのデプロイと同様に扱います。

draft → reviewed → approved → canary → active
                     ↘ rejected
active → rollback_previous

カナリアテストでは、一部の合成テストトラフィックを使用して、正常な許可率、攻撃ブロック率、タイムアウト、P95レイテンシ、および段階ごとのコストを比較します。基準を満たさない場合は、以前のバンドルに戻します。「最新のポリシーだから安全だ」という仮定ではなく、以前のバージョンと比較可能な証拠を残します。

変更 必須検証
— —
Safety Prompt 正常・攻撃分類回帰、出力形式
Presidio Recognizer False Positive・False Negative、言語変形
Rail順序 Mutation前後の結果、後続呼び出し発生有無
モデル交換 ポリシー遵守、レイテンシ、コスト、タイムアウト
ツールスキーマ 許可関数・引数・結果検証
Failure Mode 障害注入時のブロックまたは制限モード

観測すべき最小限のシグナル

リクエストごとに、以下の値を構造化されたログとトレース属性として残します。

  • request_id、検証済みtenant_idの非識別内部識別子
  • policy_bundle、policy_version、runtime_version
  • 段階別decision、rule_id、latency_ms
  • 実行したLLM・Retriever・Toolの種類と成功有無
  • 最終的なblocked_byと制限モードの有無

プロンプトと応答の原文、PII、Credentialはデフォルトのテレメトリから除外します。メトリックラベルにもユーザー入力や固有識別子を含めません。追跡可能性は、原文を多く保存することではなく、同じリクエストIDでポリシーバージョンと実行経路を連結することから生まれます。

直列・並列・混合型の選択

方式 利点 リスク 適切なケース
— — — —
完全直列 順序とブロック理由が明確 累積レイテンシ、前段ボトルネック Mutation、機密RAG、ツールエージェント
完全並列 独立検査のレイテンシ短縮の可能性 競合条件、重複コスト、結合の複雑さ 読み取り専用独立分類器
混合型 意味順序を保持しつつ一部最適化 設計・観測の複雑さ 運用規模が拡大したサービス

推奨される出発点は直列です。測定結果が蓄積された後、互いの入力を変更しない独立した検査のみを並列化します。並列呼び出しのいずれかが先に許可したとしても、メイン応答をすぐに返すのではなく、最終的な結合ポリシーがすべての必須判定を待つ必要があります。

演習完了チェックリスト

  • データプレーンとコントロールプレーンの責任を区別した。
  • 認証・テナント・業務権限はアプリケーションとダウンストリームに残した。
  • すべてのガードレールアダプターが共通判定契約を返す。
  • レールの実行順序とショートサーキット規則を文書化した。
  • エラー・タイムアウト・不明のデフォルト動作を定めた。
  • ポリシーバンドルにバージョン、ハッシュ、承認状態、ランタイム条件を記録した。
  • カナリアと以前のバージョンへのロールバック経路を準備した。
  • プロンプト・PII・クレデンシャルをデフォルトのテレメトリから除外した。
  • ポリシー・モデル・順序変更ごとに合成回帰テストを実行する。
  • ブロック後、RAG・ツール・メインLLM呼び出しがないことをトレースで確認する。

まとめ

直列ガードレールは、複数のセキュリティ製品を一本の線でつなぐ作業ではありません。各段階の入力と出力、修正権限、失敗ポリシー、そして最終決定者を明確にすることです。アプリケーションは認証・認可と業務制御を担当し、NeMoはLLM処理フローを調整し、NovaやPresidioのような検査は定められた境界内で専門的な判定を提供します。

コントロールプレーンは、この実行経路上でポリシーバージョン、承認、デプロイ、ロールバックを管理します。まず、単純な直列経路で正確性を確保し、測定された根拠がある場合にのみ独立した検査を並列化しましょう。次回は、Promptfooで既知の攻撃と正常なリクエストを繰り返し実行し、ポリシー変更が既存の防御線を破らないか検証します。

公式資料

※ コントロールプレーンバンドルと承認・デプロイ状態は、公式製品の要件ではなく、この記事の推奨運用設計案です。NeMo APIと設定はバージョンによって異なる場合があるため、固定されたリリースドキュメントと合成テストケースで確認してください。


Comments

コメントを残す

メールアドレスが公開されることはありません。 ※ が付いている欄は必須項目です