Fast Path First 전략으로 비용을 최적화한 고정확도 욕설 필터링 서비스
git clone <repository-url>
cd filtering
npm install.env 파일을 생성하고 다음 변수들을 설정하세요:
# 서버
PORT=3000
# PostgreSQL
DATABASE_URL=postgresql://postgres:postgres@localhost:5433/filtering
# Redis
REDIS_HOST=localhost
REDIS_PORT=6380
REDIS_PASSWORD= # 비어있으면 비밀번호 없이 실행, 설정하면 해당 비밀번호 사용
# Ollama Cloud
OLLAMA_API_KEY=your-ollama-api-keydocker-compose up -d이 명령으로 다음 서비스가 시작됩니다:
- PostgreSQL (포트 5433)
- Redis (포트 6380)
npm run prisma:generate
npm run prisma:migrate# 개발 모드
npm run start:dev
# 프로덕션 빌드
npm run build
npm run start:prod서버가 http://localhost:3000에서 실행됩니다.
MalJosim은 AI 기반 욕설 필터링 서비스입니다. Fast Path First 전략을 통해 95-98%의 AI 호출을 감소시키면서도 높은 정확도로 욕설 및 부적절한 표현을 감지합니다.
- ⚡ Fast Path First: Trie 기반 매칭으로 대부분의 케이스를 빠르게 처리
- 🎯 높은 정확도: 부분 매칭 구분 및 회피 패턴 감지로 False Positive/False Negative 최소화
- 🔄 실시간 필터링: Redis 캐싱으로 빠른 응답 속도
- 🤖 AI 통합: Ollama Cloud LLM을 활용한 문맥 기반 분석
- 🏢 멀티 테넌트 지원: 클라이언트별 금칙어 정책 오버라이드
입력 텍스트
↓
[1] 텍스트 분석 (회피 패턴 감지)
↓
[2] 텍스트 정규화
↓
[3] 토큰화
↓
[4] Fast Path: Trie 기반 매칭
├─ 전체 단어 매칭 발견 → 심각도 높으면 즉시 차단 ✅
└─ 부분 매칭만 발견 → AI 호출
↓
[5] Slow Path: AI/RAG 호출 (조건부)
├─ 회피 패턴 감지 (suspiciousScore > 0.3)
├─ 매칭 없음 + 짧은 텍스트 (≤ 20자)
└─ 부분 매칭만 있음
↓
[6] 최종 필터링 결과 반환
목표: AI 호출 비용 최소화 (95-98% 감소)
조건:
- ✅ 즉시 차단: 전체 단어 매칭 + dictionaryScore ≥ 0.7
⚠️ 경고만: 전체 단어 매칭 + dictionaryScore < 0.7 (AI 호출 생략)- 🤖 AI 호출: 회피 패턴 감지, 매칭 없음, 부분 매칭만 있는 경우
다음 6가지 회피 패턴을 자동으로 감지합니다:
- Leetspeak:
시8,cibal(한글 + 숫자/영문 혼용) - Repetition:
시발발(문자 반복) - Jamo Separation:
ㅅㅣ발(자모 분리) - Zero Width:
시\u200b발(보이지 않는 문자 삽입) - Space Separation:
시 발(공백으로 단어 분리)
"시발점" 같은 False Positive를 방지하기 위해 전체 매칭과 부분 매칭을 구분합니다.
- 전체 매칭: 매칭된 단어의 위치가 토큰의 경계와 정확히 일치 (예: "시발")
- 부분 매칭: 매칭된 단어가 토큰의 일부인 경우 (예: "시발점" → "시발")
- 부분 매칭은 가중치 50% 감소
- Trie 데이터 구조: 메모리 기반 금칙어 트리로 O(n) 시간 복잡도
- 네트워크 I/O 제로: Redis 조회 없이 메모리에서 직접 매칭
- 스마트 로드: 서버 시작 시 Redis에 데이터가 있으면 Redis → Trie, 없으면 PostgreSQL → Redis + Trie
클라이언트별로 금칙어 정책을 오버라이드할 수 있습니다.
// 클라이언트별 심각도 재정의
ClientBadWord {
clientId: "client-123",
wordId: "word-uuid",
overrideSeverity: "LOW" // 글로벌은 HIGH지만 이 클라이언트는 LOW
}# 방법 1: 파일을 통해 요청 (한글 인코딩 보존)
echo '{"text":"시발","clientId":"test-client"}' > test.json
curl -X POST http://localhost:3000/filter/check \
-H "Content-Type: application/json" \
--data-binary @test.json
# 방법 2: Postman/Insomnia 사용 (권장)
# POST http://localhost:3000/filter/check
# Body: {"text": "시발", "clientId": "test-client"}응답:
{
"status": "block",
"text": "시발",
"isProfanity": true,
"dictionaryScore": 0.9,
"matchedWords": [
{
"word": "시발",
"normalizedWord": "시발",
"severity": "HIGH",
"category": "",
"isPartialMatch": false
}
],
"hasEvasionPattern": false,
"suspiciousScore": 0
}Status 값:
allow: 허용warning: 경고 (낮은 심각도)block: 차단
# 금칙어 생성
curl -X POST http://localhost:3000/bad-words \
-H "Content-Type: application/json" \
-d '{
"word": "시발",
"normalizedWord": "시발",
"severity": "HIGH",
"category": "PROFANITY"
}'
# 금칙어 목록 조회
curl http://localhost:3000/bad-words?page=1&limit=10
# 금칙어 수정
curl -X PATCH http://localhost:3000/bad-words/{id} \
-H "Content-Type: application/json" \
-d '{
"severity": "CRITICAL"
}'
# 금칙어 삭제
curl -X DELETE http://localhost:3000/bad-words/{id}- Framework: NestJS 10.x
- Language: TypeScript 5.x
- Database: PostgreSQL 16 (Prisma ORM)
- Cache: Redis 7
- LLM: Ollama Cloud (gpt-oss:120b) - ChatOllama 사용
- 프롬프트 관리: 파일 기반 프롬프트 템플릿 시스템
- Container: Docker & Docker Compose
filtering/
├── src/
│ ├── ai/ # AI 서비스
│ │ ├── llm.service.ts # Ollama Cloud LLM 통합
│ │ └── prompts/ # 프롬프트 파일
│ ├── bad-word/ # 금칙어 관리 API
│ ├── cache/ # Redis 캐시 레이어
│ ├── database/ # Prisma 데이터베이스
│ ├── filter/ # 필터링 핵심 로직
│ │ ├── normalization/ # 텍스트 정규화 및 회피 패턴 감지
│ │ └── tokenization/ # 토큰화
│ └── health/ # Health Check API
├── prisma/ # 데이터베이스 스키마
├── docs/ # 문서
└── docker-compose.yml # 인프라 설정
금칙어 원본 및 정규화된 형태를 저장합니다.
| 컬럼 | 타입 | 설명 |
|---|---|---|
id |
UUID | 고유 ID |
word |
String | 원본 금칙어 |
normalizedWord |
String | 정규화된 형태 |
severity |
Severity | 심각도 (LOW/MEDIUM/HIGH/CRITICAL) |
category |
Category | 카테고리 |
isActive |
Boolean | 활성화 여부 |
aliases |
String[] | 별칭/변형 리스트 |
클라이언트별 금칙어 정책 오버라이드를 저장합니다.
자세한 스키마는 docs/rdb-schema.md 참고
# 개발 모드 실행
npm run start:dev
# 프로덕션 빌드
npm run build
npm run start:prod
# Prisma
npm run prisma:generate # Prisma Client 생성
npm run prisma:migrate # 마이그레이션 실행
npm run prisma:studio # Prisma Studio 실행
# 코드 포맷팅
npm run format
# 린팅
npm run lint
# 테스트
npm run test
npm run test:watch
npm run test:cov- 95-98% AI 호출 감소: 대부분의 케이스를 Trie 기반 매칭으로 처리
- 평균 응답 시간: Fast Path < 10ms, Slow Path < 500ms
- Write-through 캐싱: PostgreSQL 변경 시 Redis 자동 동기화
- Redis 키 구조:
bad_words:global: 글로벌 금칙어 Setbad_words:normalized: 정규화된 단어 → 상세 정보 Hashbad_words:client:{clientId}: 클라이언트별 금칙어 Set
- 공백 분리 패턴: "시 발" 같은 경우 현재 AI로 전달됨. Fast Path에서 처리하도록 개선 예정
- 테스트 코드 부재: 단위/통합 테스트 추가 예정
- 에러 핸들링: 전역 예외 필터 추가 예정
- 한글 인코딩: curl에서 JSON을 직접 전달할 때 한글이 깨질 수 있음. 파일을 통해 요청하거나 Postman/Insomnia 사용 권장
- 테스트 코드 작성
- 에러 핸들링 강화
- 로깅 시스템 구축
- API 문서화 (Swagger)
- 성능 최적화 (Redis 파이프라인, 배치 처리)
- 모니터링 (Prometheus, Grafana)
- 수평 확장 지원
- 맥락 기반 필터링 강화
- 다국어 지원
- 관리 UI 대시보드
- 프로젝트 상세 문서 - 전체 아키텍처 및 설계 결정
- 데이터베이스 스키마 문서 - 상세 스키마 설명
- Fork the Project
- Create your Feature Branch (
git checkout -b feature/AmazingFeature) - Commit your Changes (
git commit -m 'Add some AmazingFeature') - Push to the Branch (
git push origin feature/AmazingFeature) - Open a Pull Request
이 프로젝트는 MIT 라이선스 하에 배포됩니다.
프로젝트에 대한 질문이나 제안사항이 있으시면 이슈를 등록해주세요.