이 글은 누구를 위한 것인가
- UGC(사용자 생성 콘텐츠) 플랫폼에서 유해 콘텐츠 관리가 필요한 팀
- 규칙 기반 필터를 LLM 기반으로 업그레이드하려는 팀
- 모더레이션 자동화와 인간 검토의 최적 비율을 찾는 운영팀
들어가며
"씨발"이 욕설인지, 친구 사이의 격의 없는 표현인지는 맥락에 따라 다르다. 단어 매칭 필터는 이것을 구분할 수 없다. LLM 기반 모더레이션은 문맥을 이해하고 의도를 판단한다.
이 글은 bluefoxdev.kr의 AI 안전성 시스템 설계 를 참고하여 작성했습니다.
1. 모더레이션 분류 체계
[콘텐츠 카테고리별 처리 전략]
카테고리 1 - 즉시 삭제 (신뢰도 무관):
아동 성착취물 (CSAM)
테러리즘 관련
카테고리 2 - 자동 삭제 (신뢰도 > 0.95):
명백한 스팸
욕설/혐오 표현
개인정보 노출
카테고리 3 - 인간 검토 대기 (신뢰도 0.7-0.95):
맥락 의존적 혐오 표현
성적 암시
허위정보 의심
카테고리 4 - 자동 통과 (신뢰도 < 0.3):
명백히 무해한 콘텐츠
[모더레이션 결정 트리]
콘텐츠 입력
→ LLM 1차 분류 (빠른 분류, 저렴한 모델)
→ 신뢰도 < 0.7: LLM 2차 검토 (더 정확한 모델)
→ 여전히 불확실: 인간 검토 큐
→ 명백 위반: 즉시 처리
2. LLM 모더레이션 파이프라인
import anthropic
from dataclasses import dataclass
from enum import Enum
class ModerationCategory(Enum):
SAFE = "safe"
SPAM = "spam"
HATE_SPEECH = "hate_speech"
SEXUAL = "sexual"
VIOLENCE = "violence"
PERSONAL_INFO = "personal_info"
MISINFORMATION = "misinformation"
@dataclass
class ModerationResult:
category: ModerationCategory
confidence: float
action: str # auto_approve, auto_reject, human_review
reasoning: str
severity: str # low, medium, high, critical
client = anthropic.Anthropic()
async def moderate_content(content: str, content_type: str = "text") -> ModerationResult:
"""LLM 기반 콘텐츠 모더레이션"""
# 1단계: 빠른 분류 (Haiku - 저렴하고 빠름)
quick_response = client.messages.create(
model="claude-haiku-4-5-20251001",
max_tokens=200,
messages=[{
"role": "user",
"content": f"""다음 {content_type} 콘텐츠를 모더레이션하세요.
콘텐츠: {content[:500]}
JSON으로만 응답:
{{
"category": "safe|spam|hate_speech|sexual|violence|personal_info|misinformation",
"confidence": 0.0-1.0,
"severity": "low|medium|high|critical"
}}"""
}]
)
import json
quick_result = json.loads(quick_response.content[0].text)
confidence = quick_result["confidence"]
# 낮은 신뢰도면 더 정확한 모델로 재검토
if 0.4 < confidence < 0.85:
detailed_response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=500,
messages=[{
"role": "user",
"content": f"""다음 콘텐츠를 상세하게 모더레이션하세요.
한국 문화 맥락을 고려하여 판단하세요.
콘텐츠: {content}
JSON으로만 응답:
{{
"category": "safe|spam|hate_speech|sexual|violence|personal_info|misinformation",
"confidence": 0.0-1.0,
"severity": "low|medium|high|critical",
"reasoning": "판단 이유 (한 문장)"
}}"""
}]
)
final_result = json.loads(detailed_response.content[0].text)
else:
final_result = quick_result
final_result["reasoning"] = ""
# 액션 결정
cat = ModerationCategory(final_result["category"])
conf = final_result["confidence"]
sev = final_result.get("severity", "low")
if cat == ModerationCategory.SAFE and conf > 0.7:
action = "auto_approve"
elif sev == "critical" or (conf > 0.95 and cat != ModerationCategory.SAFE):
action = "auto_reject"
else:
action = "human_review"
return ModerationResult(
category=cat,
confidence=conf,
action=action,
reasoning=final_result.get("reasoning", ""),
severity=sev,
)
async def bulk_moderate(contents: list[dict], batch_size: int = 50) -> list[dict]:
"""대량 콘텐츠 일괄 모더레이션"""
import asyncio
results = []
for i in range(0, len(contents), batch_size):
batch = contents[i:i + batch_size]
batch_results = await asyncio.gather(*[
moderate_content(c["text"], c.get("type", "text"))
for c in batch
])
for content, result in zip(batch, batch_results):
results.append({
"content_id": content["id"],
"action": result.action,
"category": result.category.value,
"confidence": result.confidence,
})
# 레이트 리밋 방지
await asyncio.sleep(0.5)
return results
마무리
LLM 모더레이션의 핵심은 "신뢰도에 따른 자동/수동 분기"다. 신뢰도 95% 이상인 경우만 자동 처리하고, 나머지는 인간 검토 큐에 넣는다. 오탐(False Positive) 발생 시 이의 신청 프로세스가 반드시 있어야 한다. 모더레이션 결정은 항상 기록하고, 이의 신청 결과로 모델을 개선하는 피드백 루프가 핵심이다.