실무에서의 LLM 가드레일: 실제로 효과적인 접근법

모델이 아닌 리스크를 통제하십시오.

Page content

대형 언어 모델(LLM)은 예측 불가능합니다. 환각(hallucination) 현상이 발생하거나, 데이터가 유출되거나, 유해한 콘텐츠를 생성하거나, 합법적인 요청을 거부하기도 합니다. 가드레일(Guardrails)은 모델의 성능을 희생하지 않으면서도 모델의 행동을 제한합니다.

여기서의 핵심은 어떤 가드레일이 중요한지, 그리고 어떤 것은 그저 소음(noise)에 불과한지를 아는 것입니다.

가드레일은 모델을 통제하는 것이 아닙니다. 위험을 통제하는 것입니다.

실전에서의 LLM 가드레일

입력 검증

가장 중요한 가드레일입니다. 잘못된 입력은 잘못된 출력을 낳으며, 잘못된 입력은 시스템의 프롬프트 인젝션(prompt injection)을 유발하기도 합니다.

전략 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"입력이 너무 깁니다: {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"차단됨: {topic}"
        return True, "OK"

단순 문자열 매칭은 빠르지 않지만 부정확합니다. 프로덕션 환경에서는 정책 위반을 감지하기 위해 분류기 모델(classifier model)을 사용하세요. 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"필수 필드 누락: {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"차단됨: {pattern}"
        return True, "OK"

전략 3: 사실 확인

사실 확인은 더 어렵습니다. 모든 주장을 검증할 수는 없으므로, 중요한 것들만 선택하세요:

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}"
        return True, "OK"

진짜 사실 확인을 위해서는 검색 파이프라인(retrieval pipeline)이 필요합니다. 하드코딩된 사전이 아닌 지식 베이스(knowledge base)와 주장을 비교해야 합니다.

안전 메커니즘

전략 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: 토큰 예산 관리

토큰 예산 관리는 요청당 비용을 상한선으로 제한합니다:

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_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)

슬라이딩 윈도우 방식의 트리밍은 간단하지만 초기 컨텍스트를 잃게 됩니다. 더 나은 접근법은 요약(summarization)이나 어텐션 기반 압축을 사용하는 것이지만, 이는 지연 시간을 증가시킵니다.

규정 준수

엔터프라이즈 시스템은 규정 준수 가드레일이 필요합니다. 가장 중요한 두 가지가 있습니다:

패턴 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}"
        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")

감사 로그는 디버깅과 규정 준수에 필수적입니다. 구조화되고 부속만 가능(append-only)하며 안전하게 저장되도록 하세요.

통합 적용

패턴 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"오류: {message}"

        response = self.call_model(prompt)

        valid, message = self.output_filter.filter(response)
        if not valid:
            return f"오류: {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"오류: {message}"

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

        if not self.rate_limiter.allow():
            return "오류: 속도 제한 초과"

        response = self.call_model(prompt)

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

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

        return response

가드레일이 중요한 시점

가드레일은 사용자 대상 시스템을 구축하거나, 민감한 데이터를 처리하거나, 프로덕션 환경에서 실행할 때 중요합니다. 또한 GDPR, HIPAA, SOC 2와 같은 규정 준수 요구 사항이 있을 때도 중요합니다.

프로토타이핑을 하거나, 모델이 내부 도구에만 사용되거나, 민감한 데이터를 처리하지 않는다면 중요하지 않습니다. 필요할 때까지는 생략하세요.

이것은 항상 성능과 안전 사이의 tradeoff(상충 관계)입니다. 더 많은 가드레일은 더 적은 실패를 의미하지만 동시에 더 적은 성능을 의미합니다. 시스템에 적합한 균형을 찾으세요.

가드레일은 모델의 대화까지 커버하지만, 에이전트가 도구를 호출하거나 작업을 다른 에이전트에 넘기는 지점에서는 멈춥니다. MCP 도구나 A2A 에이전트를 배포하면 프로토콜 보안 — 신원, 범위별 권한 부여, 위임 한도, 그리고 누가 누구의 명의로 행동할 수 있는지에 대한 감사 추적 — 도 필요합니다. 이 레이어는 A2A 및 MCP 에이전트 보안: 신원, 위임, 감사 추적에서 다룹니다.

상충 관계

전략 안전성 성능 지연 시간
가드레일 없음 가장 낮음 가장 높음 가장 낮음
입력 검증 높음 중간 낮음
출력 필터링 높음 중간 낮음
안전 메커니즘 가장 높음 가장 낮음 가장 높음
규정 준수 가장 높음 가장 낮음 가장 높음

관련 자료

구독하기

시스템, 인프라, AI 엔지니어링에 관한 새 글을 받아보세요.