実践におけるLLMのガードレール:実際に有効な手法

管理すべきはモデルではなく、リスクです。

目次

LLM(大規模言語モデル)は予測不可能です。幻覚(ハルシネーション)を起こし、データを漏洩させ、有害なコンテンツを生成したり、正当なリクエストを拒否したりします。ガードレール(防護策)は、機能を損なわずにモデルの動作を制限します。

重要なのは、どのガードレールが本質的で、どれが単なるノイズ(不要な制約)なのかを把握することです。

ガードレールはモデルを制御するためではありません。リスクを制御するためです。

LLM guardrails in practice

入力検証(Input validation)

最も重要なガードレールです。悪い入力には悪い出力が生まれ、悪い入力はシステムのプロンプトインジェクションを引き起こす可能性があります。

戦略 1: プロンプトのサニタイズ(Sanitization)

危険なパターンを早期にサニタイズします:

import re

class PromptSanitizer:
    def __init__(self):
        self.dangerous_patterns = [
            r"ignore\s+previous\s+instructions",
            r"system\s+prompt",
            r"you\s+are\s+now\s+free",
            r"break\s+out\s+of",
        ]

    def sanitize(self, prompt: str) -> str:
        for pattern in self.dangerous_patterns:
            prompt = re.sub(pattern, "[REDACTED]", prompt, flags=re.IGNORECASE)
        return prompt

これは完全ではありません。敵対的入力(攻撃的な入力)は創造的ですが、明らかなものは捕捉できます。そして、明らかな攻撃が最も一般的です。

戦略 2: 入力長の制限

長さの制限は、トークンの無駄遣いやタイムアウトを防ぎます:

class InputValidator:
    def __init__(self, max_length: int = 10000):
        self.max_length = max_length

    def validate(self, prompt: str) -> tuple[bool, str]:
        if len(prompt) > self.max_length:
            return False, f"Input too long: {len(prompt)} > {self.max_length}"
        return True, "OK"

戦略 3: コンテンツフィルタリング

コンテンツフィルタリングはポリシー違反をブロックします。ここで使用するパターンはドメイン(分野)によって異なります:

class ContentFilter:
    def __init__(self):
        self.blocked_topics = [
            "violence", "hate speech", "self-harm",
            "sexual content", "illegal activities",
        ]

    def filter(self, prompt: str) -> tuple[bool, str]:
        prompt_lower = prompt.lower()
        for topic in self.blocked_topics:
            if topic in prompt_lower:
                return False, f"Blocked: {topic}"
        return True, "OK"

単純な文字列マッチングは高速ですが不正確です。本番環境では、ポリシー違反を検出するために分類モデル(qwen3-1.7b のような小規模モデルでも可)を使用してください。これにより、より正確になり、回避されにくくなります。

出力フィルタリング

モデルの出力もチェックが必要です。構造化、コンテンツ、そして事実関係についてです。

戦略 1: レスポンス検証

まず構造化を検証します。JSON を期待している場合、JSON として検証します:

class ResponseValidator:
    def __init__(self):
        self.required_fields = ["answer", "confidence"]

    def validate(self, response: dict) -> tuple[bool, str]:
        for field in self.required_fields:
            if field not in response:
                return False, f"Missing field: {field}"
        return True, "OK"

戦略 2: コンテンツフィルタリング

有害なコンテンツをフィルタリングします:

class OutputFilter:
    def __init__(self):
        self.blocked_patterns = [
            r"kill\s+someone",
            r"bomb\s+recipe",
            r"hate\s+speech",
            r"self-harm",
        ]

    def filter(self, response: str) -> tuple[bool, str]:
        for pattern in self.blocked_patterns:
            if re.search(pattern, response, re.IGNORECASE):
                return False, f"Blocked: {pattern}"
        return True, "OK"

戦略 3: 事実確認(Fact-Checking)

事実確認は困難です。すべての主張を検証することはできないため、重要なものを選びます:

class FactChecker:
    def __init__(self):
        self.known_facts = {
            "capital of france": "Paris",
            "population of usa": "330 million",
            "speed of light": "299,792,458 m/s",
        }

    def check(self, claim: str) -> tuple[bool, str]:
        claim_lower = claim.lower()
        for fact, truth in self.known_facts.items():
            if fact in claim_lower and truth not in claim_lower:
                return False, f"Fact check failed: {fact}"
        return True, "OK"

本格的な事実確認には、取得パイプライン(retrieval pipeline)が必要です。ハードコードされた辞書ではなく、ナレッジベースに対して主張を検証する必要があります。

セーフティメカニズム

戦略 1: レート制限(Rate Limiting)

レート制限は悪用を防ぎます:

import time
from collections import deque

class RateLimiter:
    def __init__(self, max_requests: int = 10, window: int = 60):
        self.max_requests = max_requests
        self.window = window
        self.requests = deque()

    def allow(self) -> bool:
        now = time.time()
        while self.requests and self.requests[0] < now - self.window:
            self.requests.popleft()

        if len(self.requests) >= self.max_requests:
            return False

        self.requests.append(now)
        return True

戦略 2: トークン予算管理(Token Budgeting)

トークン予算管理は、リクエストあたりのコストを上限設定します:

class TokenBudget:
    def __init__(self, max_tokens: int = 1000):
        self.max_tokens = max_tokens

    def validate(self, response: str) -> tuple[bool, str]:
        token_count = len(response.split())
        if token_count > self.max_tokens:
            return False, f"Token limit exceeded: {token_count} > {self.max_tokens}"
        return True, "OK"

戦略 3: コンテキストウィンドウ管理

コンテキストウィンドウ管理はオーバーフローを防ぎます:

class ContextManager:
    def __init__(self, max_context: int = 4096):
        self.max_context = max_context
        self.context = []

    def add(self, message: str):
        self.context.append(message)
        self.trim()

    def trim(self):
        while len(" ".join(self.context)) > self.max_context:
            self.context.pop(0)

スライディングウィンドウによるトリミング(切り捨て)はシンプルですが、初期のコンテキストを失います。より良いアプローチは要約やアテンションベースの圧縮を使用することですが、これらはレイテンシ(遅延)を増加させます。

コンプライアンス(Compliance)

エンタープライズシステムにはコンプライアンス用のガードレールが必要です。特に重要なのは以下の2つです:

パターン 1: データ在留(Data Residency)

データ在留 — データが必要な地理的範囲内に留まることを確保します:

class DataResidency:
    def __init__(self, allowed_regions: list[str]):
        self.allowed_regions = allowed_regions

    def validate(self, region: str) -> tuple[bool, str]:
        if region not in self.allowed_regions:
            return False, f"Region not allowed: {region}"
        return True, "OK"

パターン 2: 監査ログ(Audit Logging)

監査ログ — すべてのモデルとの相互作用をログに記録します:

import json
from datetime import datetime

class AuditLogger:
    def __init__(self, log_file: str = "audit.log"):
        self.log_file = log_file

    def log(self, request: dict, response: dict):
        entry = {
            "timestamp": datetime.now().isoformat(),
            "request": request,
            "response": response,
        }
        with open(self.log_file, "a") as f:
            f.write(json.dumps(entry) + "\n")

監査ログはデバッグとコンプライアンスに不可欠です。構造化され、追記のみが可能で、安全に保存されるようにしてください。

組み合わせて活用する

パターン 1: シンプルなガードレール

シンプルなガードレールパイプライン:

class SimpleGuardrails:
    def __init__(self):
        self.input_validator = InputValidator(max_length=10000)
        self.output_filter = OutputFilter()

    def process(self, prompt: str) -> str:
        valid, message = self.input_validator.validate(prompt)
        if not valid:
            return f"Error: {message}"

        response = self.call_model(prompt)

        valid, message = self.output_filter.filter(response)
        if not valid:
            return f"Error: {message}"

        return response

パターン 2: 高度なガードレール

高度なガードレールは、サニタイズ、レート制限、トークン予算を追加します:

class AdvancedGuardrails:
    def __init__(self):
        self.sanitizer = PromptSanitizer()
        self.input_validator = InputValidator(max_length=10000)
        self.content_filter = ContentFilter()
        self.output_filter = OutputFilter()
        self.rate_limiter = RateLimiter(max_requests=10)
        self.token_budget = TokenBudget(max_tokens=1000)

    def process(self, prompt: str) -> str:
        prompt = self.sanitizer.sanitize(prompt)

        valid, message = self.input_validator.validate(prompt)
        if not valid:
            return f"Error: {message}"

        valid, message = self.content_filter.filter(prompt)
        if not valid:
            return f"Error: {message}"

        if not self.rate_limiter.allow():
            return "Error: Rate limit exceeded"

        response = self.call_model(prompt)

        valid, message = self.output_filter.filter(response)
        if not valid:
            return f"Error: {message}"

        valid, message = self.token_budget.validate(response)
        if not valid:
            return f"Error: {message}"

        return response

ガードレールが重要な場面

ユーザー向けシステムを構築している場合、機密データを扱っている場合、あるいは本番環境で運用している場合は、ガードレールが重要です。GDPR、HIPAA、SOC 2 などのコンプライアンス要件がある場合にも重要です。

プロトタイピング段階、内部ツールでのみモデルを使用する場合、または機密データを扱わない場合は、それほど重要ではありません。必要な時まで導入を先送りにしても構いません。

トレードオフは常に「機能」と「安全性」の間です。ガードレールを増やすと失敗は減りますが、機能も制限されます。システムに適したバランスを見つけてください。

ガードレールはモデルとの会話のカバー範囲ですが、エージェントがツールを呼び出す場合や、他のエージェントに作業を委譲する時点では終了します。MCP ツールや A2A エージェントをデプロイする際には、プロトコルセキュリティ(アイデンティティ、スコープ付き認可、委譲の制限、および誰が誰の代わりに行動したかの監査証跡)も必要になります。このレイヤーについては A2A and MCP Agent Security: Identity, Delegation, and Audit Trails で説明されています。

トレードオフ

戦略 安全性 機能性 レイテンシ
ガードレールなし 最低 最高 最低
入力検証
出力フィルタリング
セーフティメカニズム 最高 最低 最高
コンプライアンス 最高 最低 最高

関連項目

購読する

システム、インフラ、AIエンジニアリングの新記事をお届けします。