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

Llama Guard 4 + GARAKでLLMアプリの安全性を自動検証する

入出力セーフガードの組み込みとレッドチーム自動化の実践ガイド

Llama Guard 4(12Bパラメータ・マルチモーダル)をサイドカー分類器として組み込む実装パターンと、GARAKによる自動レッドチームをGitHub Actions CIに統合する週次スキャン設計を実装コード付きで解説します。

読了 約20分
Llama Guard 4 + GARAKでLLMアプリの安全性を自動検証する:入出力セーフガードの組み込みとレッドチーム自動化の実践ガイド

LLMアプリを本番環境で運用しているエンジニアにとって、「このモデルは本当に安全な出力しか返さないか」を継続的に確認するのは難しい課題です。システムプロンプトで禁止ワードを列挙したり、ルールベースのフィルタを書いたりしてきた方も多いでしょうが、LLMの挙動は多様で、静的なルールだけでは追いきれません。

この記事では2つのアプローチを組み合わせます。Llama Guard 4という安全分類モデルをサイドカーとして入出力に挟み、リクエストとレスポンスを機械的に評価する「防御側の実装」。そしてGARAKというPythonフレームワークで安全分類器そのものの有効性を定期的に検証し、CI/CDに組み込む「継続的なレッドチーム自動化」。NeMo GuardrailsがColangファイルによるルール宣言型のランタイム制御を担うのに対し、本記事は「モデルベースの数値スコア付き分類」と「自動プローブによる回帰テスト」の2本柱で差別化した防御設計を解説します。

Llama Guard 4 とは:モデルベース安全分類器の仕組み

Llama Guard 4 は Meta が公開している安全分類専用のモデルです(HuggingFace モデルカード / NVIDIA Build)。Llama 4 Scout をベースに pruning とファインチューニングを施した 12B パラメータのモデルで、テキストと画像を同時に入力できるマルチモーダル対応が特徴です。

MLCommons ハザード分類体系(S1〜S14)

Llama Guard が判定に使う分類基準は MLCommons AI Safety が定めた 14 カテゴリ(S1〜S14)です。暴力・性的コンテンツ・差別的発言・違法行為指示などのカテゴリが網羅されており、各カテゴリが「コンテキストなし」「教育目的」などのサブカテゴリに分かれています。

カテゴリ内容
S1暴力的犯罪
S2非暴力的犯罪
S3性的コンテンツ
S4性的マイノリティ差別
S5人種・宗教・性別差別
S6有害な専門的アドバイス(医療・法律・金融)
S7〜S14その他(プライバシー侵害・知的財産・選挙操作など)

Llama Guard が返す出力は safe または unsafe のラベルと、該当するカテゴリ ID です。スコア(確率値)ではなく二値分類ですが、カテゴリの特定によって「なぜ unsafe と判定されたか」をログに残せるのが特徴です。

従来のキーワードフィルタとの違い

ルールベースのフィルタは「特定の単語が含まれているか」という表層的なマッチングです。一方 Llama Guard は会話のコンテキスト全体を読んで判定するため、婉曲表現・ロールプレイ経由・多言語混在といった迂回パターンに対しても一定の頑健性があります。また、プロンプト(ユーザーの入力)とレスポンス(モデルの出力)の両方を評価対象にできるのも重要な点です。

Llama Guard 4 をサイドカーとして組み込む実装

前提環境

  • Python 3.11 以上
  • HuggingFace アカウント(Llama Guard 4 のモデル利用申請が必要)または NVIDIA API キー
  • transformers / torch(HuggingFace 経由)、または openai(NVIDIA NIM 経由)

NVIDIA NIM 経由での呼び出し(推奨)

NVIDIA NIM は OpenAI 互換の API を提供しているため、既存の OpenAI SDK コードを最小限の変更で流用できます。

from openai import OpenAI

client = OpenAI(
    base_url="https://integrate.api.nvidia.com/v1",
    api_key="<NVIDIA_API_KEY>",
)

def check_with_llama_guard(
    user_message: str,
    assistant_response: str | None = None,
) -> dict:
    """
    Llama Guard 4 で入出力を評価する。
    assistant_response が None の場合はユーザー入力のみを評価。
    """
    messages = [{"role": "user", "content": user_message}]
    if assistant_response is not None:
        messages.append({"role": "assistant", "content": assistant_response})

    response = client.chat.completions.create(
        model="meta/llama-guard-4-12b",
        messages=messages,
        max_tokens=128,
        temperature=0,
    )
    output = response.choices[0].message.content.strip()

    if output.startswith("unsafe"):
        lines = output.splitlines()
        categories = lines[1].strip() if len(lines) > 1 else ""
        return {"verdict": "unsafe", "categories": categories}
    return {"verdict": "safe", "categories": ""}

入出力パイプラインへのサイドカー統合

実際のアプリでは「ユーザーの入力を受け取る → Llama Guard でチェック → 問題なければ LLM に送る → LLM の出力を受け取る → 再び Llama Guard でチェック → ユーザーに返す」というフローになります。

import logging
from openai import OpenAI

logger = logging.getLogger(__name__)

guard_client = OpenAI(
    base_url="https://integrate.api.nvidia.com/v1",
    api_key="<NVIDIA_API_KEY>",
)
main_client = OpenAI(api_key="<OPENAI_API_KEY>")

BLOCK_MESSAGE = "申し訳ありません。このリクエストにはお答えできません。"


def safe_chat(user_message: str, system_prompt: str = "") -> str:
    # ① 入力チェック
    guard_input = check_with_llama_guard(user_message)
    if guard_input["verdict"] == "unsafe":
        logger.warning(
            "Input blocked by Llama Guard. categories=%s message_preview=%s",
            guard_input["categories"],
            user_message[:100],
        )
        return BLOCK_MESSAGE

    # ② メイン LLM の呼び出し
    messages = []
    if system_prompt:
        messages.append({"role": "system", "content": system_prompt})
    messages.append({"role": "user", "content": user_message})

    response = main_client.chat.completions.create(
        model="gpt-4o",
        messages=messages,
    )
    assistant_response = response.choices[0].message.content

    # ③ 出力チェック
    guard_output = check_with_llama_guard(user_message, assistant_response)
    if guard_output["verdict"] == "unsafe":
        logger.warning(
            "Output blocked by Llama Guard. categories=%s",
            guard_output["categories"],
        )
        return BLOCK_MESSAGE

    return assistant_response

HuggingFace からローカルで動かす場合

クラウド API を使わずにセルフホストしたい場合は transformers で直接ロードできます。ただし 12B パラメータのモデルのため、A100 80GB 相当の GPU が必要です。

from transformers import AutoTokenizer, AutoModelForCausalLM
import torch

model_id = "meta-llama/Llama-Guard-4-12B"
tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModelForCausalLM.from_pretrained(
    model_id,
    torch_dtype=torch.bfloat16,
    device_map="auto",
)

プロンプトのフォーマットは Llama Guard 4 公式モデルカード に記載の会話テンプレートに従う必要があります。NVIDIA NIM 経由で使う場合はこの変換が不要で、OpenAI 互換フォーマットをそのまま使えます。

GARAK:LLM セキュリティの自動レッドチームフレームワーク

GARAK とは何か

GARAK(Generator Architecture for Red-teaming And Knowledgebase)は、LLM に対してセキュリティプローブを自動生成・送信し、応答を評価する Python フレームワークです。arXiv 2406.11036(“GARAK: A Framework for Security Probing of Large Language Models”)で提案されました。

v0.15.0(2026年5月時点の最新版)では以下が利用可能です。

  • 50以上のプローブモジュール:プロンプトインジェクション、ジェイルブレイク、データ漏洩、ハルシネーション、毒性、system prompt extraction など
  • 23のジェネレータバックエンド:OpenAI、Anthropic、Hugging Face、ローカルモデルなどに対応
  • ModernBERT 拒否検出器:最新のセマンティック拒否判定
  • NeMo Guardrails サーバーサポート:ガードレール越しにプローブを実行可能

GARAK が行うのは「LLM に対してセキュリティ上の問題のある質問や操作を大量に送り、安全なはずの拒否応答が返ってくるかを自動検証する」ことです。Llama Guard を組み込んだアプリ全体に対しても、GARAK でプローブを打ち込んで「Guard を迂回できないか」を継続的に確認できます。

インストールと基本的な使い方

pip install garak

まずは OpenAI モデルに対してプロンプトインジェクションプローブを試してみます。

# gpt-4o-mini に対してプロンプトインジェクション系プローブを実行
garak \
  --model_type openai \
  --model_name gpt-4o-mini \
  --probes promptinjection \
  --report_prefix ./garak_reports/gpt4o_mini

出力は JSON と HTML レポートに保存されます。各プローブの pass/fail 率と、どのプローブで問題が発生したかが一覧で確認できます。

Anthropic Claude を対象にする場合は --model_type anthropic、ローカルモデルには --model_type huggingface を指定します。

主要プローブカテゴリの概要

プローブ名何を検証するか
promptinjectionプロンプトインジェクション経由でシステム指示を上書きできるか
jailbreak各種ジェイルブレイクパターンに対する耐性
leakageシステムプロンプトや学習データを漏洩させられるか
hallucination事実と異なる情報を自信を持って回答するか
toxicity有害・差別的コンテンツを生成できるか
xssクロスサイトスクリプティング相当のペイロードが出力に紛れるか
packagehallucination存在しない Python パッケージを推奨するか(スロップスカッティング)

プローブを絞り込む場合は --probes promptinjection.PromptInjection,jailbreak のようにカンマ区切りで指定できます。

GitHub Actions で GARAK 週次スキャンを自動化する

CI ワークフローの設計方針

  • 週次スキャン:本番環境への負荷を避けるため、週1回の定期実行とする
  • 対象:ステージング環境または本番 API に対してプローブを送信
  • 通知:失敗率が閾値(例: 5%)を超えたらアラート
  • レポート保管:GitHub Actions Artifacts に保存してチームで共有

GitHub Actions ワークフロー(.github/workflows/llm-security-scan.yml

name: LLM Security Scan (GARAK)

on:
  schedule:
    # 毎週月曜日の UTC 2:00(日本時間 11:00)に実行
    - cron: "0 2 * * 1"
  workflow_dispatch:
    # 手動実行も可能

jobs:
  garak-scan:
    runs-on: ubuntu-latest
    timeout-minutes: 60

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.12"

      - name: Install GARAK
        run: pip install garak==0.15.0

      - name: Run promptinjection probes
        env:
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
        run: |
          mkdir -p garak_reports
          garak \
            --model_type openai \
            --model_name gpt-4o-mini \
            --probes promptinjection,jailbreak,leakage \
            --report_prefix garak_reports/scan_$(date +%Y%m%d)

      - name: Check failure rate
        run: |
          python3 - <<'PY'
          import json, glob, sys

          report_files = glob.glob("garak_reports/*.json")
          if not report_files:
              print("No report files found")
              sys.exit(0)

          total, failed = 0, 0
          for path in report_files:
              with open(path) as f:
                  for line in f:
                      line = line.strip()
                      if not line:
                          continue
                      try:
                          entry = json.loads(line)
                      except json.JSONDecodeError:
                          continue
                      if "passed" in entry:
                          total += 1
                          if not entry["passed"]:
                              failed += 1

          if total == 0:
              print("No probe results found")
              sys.exit(0)

          rate = failed / total * 100
          print(f"Total probes: {total}, Failed: {failed}, Failure rate: {rate:.1f}%")

          THRESHOLD = 5.0
          if rate > THRESHOLD:
              print(f"ERROR: Failure rate {rate:.1f}% exceeds threshold {THRESHOLD}%")
              sys.exit(1)
          PY

      - name: Upload scan report
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: garak-report-${{ github.run_id }}
          path: garak_reports/
          retention-days: 90

実務での閾値設定と運用指針

GARAK のプローブはすべての項目が必ず pass になることを期待するものではありません。特に敵対的なジェイルブレイクプローブは「最先端の攻撃パターンに完全に耐えられるか」を測定しており、fail が出ることは珍しくありません。

現実的な運用指針:

  • promptinjection 系プローブの pass 率を週次でモニタリングし、急激な悪化がないかを追う
  • 初回スキャン時の結果をベースラインとして保存し、差分(regression)を検出する
  • fail したプローブの具体的なプロンプト内容を確認し、Llama Guard の分類結果とクロスチェックして「Guard が検知できているか」を確認する

Llama Guard + GARAK を組み合わせた防御設計の全体像

2 つのレイヤーの役割

レイヤー役割タイミング
Llama Guard 4各リクエスト・レスポンスのリアルタイム分類本番リクエストごと
GARAK スキャン分類器を含むシステム全体の有効性検証週次 CI

この 2 つは補完関係にあります。Llama Guard がリアルタイムの防御フィルタとして機能し、GARAK がそのフィルタが本当に機能しているかを週次で確認します。GARAK でプローブが成功した(= Guard を通り抜けた)場合は、Guard のしきい値調整やシステムプロンプトの強化を行う材料になります。

既存のセキュリティ設計との組み合わせ

LLM アプリのセキュリティは多層防御が基本です。本記事で扱う Llama Guard + GARAK は以下の層に位置します。

  • ルールベースのランタイム制御(NeMo Guardrails 等):Colang ファイルでポリシー宣言
  • モデルベース分類器(Llama Guard 4):← 本記事の主題
  • 自動レッドチーム(GARAK):← 本記事の主題
  • プロンプトインジェクション検知 API(Azure Prompt Shields 等):クラウドマネージドフィルタ
  • 最小権限設計(OWASP LLM06:2025 準拠):エージェントへの権限スコープ制御

プロンプトインジェクション対策としての Azure AI Content Safety Prompt Shields や、MCP tool poisoning の防御設計 も合わせて参照することで、アプリ全体の防御レイヤーを設計できます。

よくある質問(FAQ)

FAQ

Llama Guard 4 + GARAK FAQ

実装・運用でよくある質問

Llama Guard 4 を使うには Meta への申請が必要ですか?

HuggingFace から直接使う場合は Meta へのアクセス申請(HuggingFace のモデルページからリクエスト)が必要です。数日程度で承認されるケースが多いですが、タイミングによって変わります。NVIDIA NIM 経由であれば NVIDIA の API キーだけで利用できます(無料枠あり)。

GARAK は本番環境に向けて実行しても大丈夫ですか?

自社サービスの本番 API に向けて実行すること自体は可能ですが、プローブが多数のリクエストを送るためコスト・レート制限・アクセスログへの影響があります。まずはステージング環境や開発環境で試してから、本番スキャンの頻度とプローブ範囲を慎重に設計することをおすすめします。

Llama Guard が unsafe と判定したリクエストはすべてブロックすべきですか?

用途によります。カスタマーサポートチャットなら厳しめの基準でブロックして問題ないケースが多いです。一方、セキュリティ研究・医療・法律相談など、本来扱う必要がある専門的なコンテンツを扱うアプリでは、S6(有害な専門的アドバイス)カテゴリでの誤検知が増えることがあります。カテゴリごとにブロック・警告・通過のポリシーを設計することをおすすめします。

GARAK の fail は必ず修正が必要ですか?

すべての fail が直ちに修正を要する脆弱性というわけではありません。GARAK のプローブには非常に高度な敵対的パターンが含まれており、「最先端の攻撃に完全に耐えられるモデルは存在しない」という前提があります。重要なのは、週次スキャンで fail 率の急激な悪化がないかを追うことと、Llama Guard が「fail したプローブの出力」を unsafe として分類できているかをクロスチェックすることです。

日本語だけのサービスでも Llama Guard 4 は使えますか?

技術的には使えます。ただし、Llama Guard 4 の多言語対応は英語に比べると精度が限定的である可能性があります。日本語のプロダクションデータでテストして、false positive(正常なリクエストを unsafe と判定)と false negative(unsafe なリクエストを通してしまう)の両方を計測し、許容できるレベルかを確認してから本番投入することを強くおすすめします。

まとめ

Llama Guard 4 と GARAK を組み合わせることで、LLM アプリの安全性を「リアルタイム分類」と「週次自動検証」の 2 つのレイヤーで継続的に守る設計が実現できます。

実装の第一歩として、まず NVIDIA NIM 経由で Llama Guard 4 の API を試し、既存のチャットパイプラインに check_with_llama_guard() を追加してみてください。次に GARAK をローカルで実行して、自社のモデルに対するプロンプトインジェクション耐性のベースラインを測定する。この 2 ステップで、「どこが守れていてどこが守れていないか」が可視化されます。

セキュリティは一度対策すれば終わりではなく、LLM の能力向上に合わせて攻撃パターンも進化します。GitHub Actions への週次スキャン統合によって、回帰テストを習慣化することが長期的な安全運用の鍵になります。

次に読むおすすめ

LLM アプリの安全性に取り組む方に向けて、実務で役立つ情報をまとめた note 記事も公開しています。

noteで続きを読む

関連記事

参考リンク