diff --git a/frontend/src/app/App.tsx b/frontend/src/app/App.tsx index fa0618f..6144bf7 100644 --- a/frontend/src/app/App.tsx +++ b/frontend/src/app/App.tsx @@ -21,6 +21,7 @@ import { LoginPage } from '@features/auth'; /* SFR-17 */ import { AIAlert } from '@features/field-ops'; /* SFR-18+19 */ import { MLOpsPage } from '@features/ai-operations'; /* SFR-20 */ import { AIAssistant } from '@features/ai-operations'; +/* SFR-20 LLM운영 */ import { LLMOpsPage } from '@features/ai-operations'; /* 기존 */ import { Dashboard } from '@features/dashboard'; import { LiveMapView, MapControl } from '@features/surveillance'; import { EventList } from '@features/enforcement'; @@ -104,6 +105,7 @@ export default function App() { } /> {/* SFR-18~20 AI 운영 */} } /> + } /> } /> {/* SFR-03 데이터허브 */} } /> diff --git a/frontend/src/app/auth/AuthContext.tsx b/frontend/src/app/auth/AuthContext.tsx index 02b01a9..2428a65 100644 --- a/frontend/src/app/auth/AuthContext.tsx +++ b/frontend/src/app/auth/AuthContext.tsx @@ -55,6 +55,7 @@ const PATH_TO_RESOURCE: Record = { '/ai-assistant': 'ai-operations:ai-assistant', '/ai-model': 'ai-operations:ai-model', '/mlops': 'ai-operations:mlops', + '/llm-ops': 'ai-operations:llm-ops', '/statistics': 'statistics:statistics', '/external-service': 'statistics:external-service', '/admin/audit-logs': 'admin:audit-logs', diff --git a/frontend/src/app/layout/MainLayout.tsx b/frontend/src/app/layout/MainLayout.tsx index e0a3e15..8159ce6 100644 --- a/frontend/src/app/layout/MainLayout.tsx +++ b/frontend/src/app/layout/MainLayout.tsx @@ -84,6 +84,7 @@ const NAV_ENTRIES: NavEntry[] = [ items: [ { to: '/ai-model', icon: Brain, labelKey: 'nav.aiModel' }, { to: '/mlops', icon: Cpu, labelKey: 'nav.mlops' }, + { to: '/llm-ops', icon: Brain, labelKey: 'nav.llmOps' }, { to: '/ai-assistant', icon: MessageSquare, labelKey: 'nav.aiAssistant' }, { to: '/external-service', icon: Globe, labelKey: 'nav.externalService' }, { to: '/data-hub', icon: Wifi, labelKey: 'nav.dataHub' }, diff --git a/frontend/src/features/ai-operations/LLMOpsPage.tsx b/frontend/src/features/ai-operations/LLMOpsPage.tsx new file mode 100644 index 0000000..a0bf73f --- /dev/null +++ b/frontend/src/features/ai-operations/LLMOpsPage.tsx @@ -0,0 +1,1269 @@ +import { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Card, CardContent } from '@shared/components/ui/card'; +import { Badge } from '@shared/components/ui/badge'; +import { PageContainer, PageHeader } from '@shared/components/layout'; +import { + Brain, Database, MessageSquare, FileText, Activity, Shield, Ban, + Server, RefreshCw, Search, Settings, Zap, GitBranch, Layers, + CheckCircle, AlertTriangle, Clock, + BarChart3, Lock, Cpu, + ArrowUpDown, ArrowRight, TrendingUp, +} from 'lucide-react'; + +/* + * SFR-20: 대규모 언어모델(LLM) 운영 기능 + * Qwen3-8B 기반 통합 LLM 운영 플랫폼 + * + * 9개 기능 영역: + * ① 모델 관리 ② 프롬프트 관리 ③ 추론 서비스 + * ④ 로그 및 추적 ⑤ 평가 및 품질 ⑥ 데이터 관리 + * ⑦ RAG 파이프라인 ⑧ 보안 및 접근제어 ⑨ 운영 모니터링 + */ + +type Tab = 'model' | 'prompt' | 'inference' | 'log' | 'eval' | 'data' | 'rag' | 'quality' | 'security' | 'monitoring'; + +const TABS: { key: Tab; label: string; icon: React.ElementType }[] = [ + { key: 'model', label: '모델 관리', icon: Brain }, + { key: 'prompt', label: '프롬프트', icon: MessageSquare }, + { key: 'inference', label: '추론 서비스', icon: Zap }, + { key: 'log', label: '로그/추적', icon: FileText }, + { key: 'eval', label: '평가/품질', icon: BarChart3 }, + { key: 'data', label: '데이터', icon: Database }, + { key: 'rag', label: 'RAG', icon: Layers }, + { key: 'quality', label: '품질개선', icon: Search }, + { key: 'security', label: '보안', icon: Shield }, + { key: 'monitoring', label: '모니터링', icon: Activity }, +]; + +// ─── ① 모델 관리 ────────────────── +const MODEL_VERSIONS = [ + { version: 'v3.2.0', name: 'Qwen3-8B-KCG-FT', status: '운영중', params: '8.0B', deploy: 'Blue', date: '2026-04-01', accuracy: 94.2, f1: 93.1, hallu: 3.2 }, + { version: 'v3.1.0', name: 'Qwen3-8B-KCG-FT', status: '대기', params: '8.0B', deploy: 'Green', date: '2026-03-15', accuracy: 92.8, f1: 91.5, hallu: 4.1 }, + { version: 'v3.0.0', name: 'Qwen3-8B-Base', status: '보관', params: '8.0B', deploy: '-', date: '2026-02-20', accuracy: 88.5, f1: 87.2, hallu: 6.8 }, + { version: 'v2.5.0', name: 'Qwen2-7B-KCG', status: '폐기', params: '7.0B', deploy: '-', date: '2025-12-10', accuracy: 85.3, f1: 84.0, hallu: 8.5 }, +]; + +const FINETUNE_HISTORY = [ + { id: 'FT-012', base: 'Qwen3-8B', dataset: '해양법령+단속사례', epochs: 5, lr: '2e-5', lora: 'r=16, alpha=32', result: 'v3.2.0', date: '2026-03-28', status: '완료' }, + { id: 'FT-011', base: 'Qwen3-8B', dataset: '해양법령+매뉴얼', epochs: 3, lr: '3e-5', lora: 'r=8, alpha=16', result: 'v3.1.0', date: '2026-03-10', status: '완료' }, + { id: 'FT-010', base: 'Qwen3-8B', dataset: '기본 도메인 적응', epochs: 2, lr: '5e-5', lora: '-', result: 'v3.0.0', date: '2026-02-15', status: '완료' }, +]; + +// ─── ② 프롬프트 관리 ────────────────── +const PROMPT_TEMPLATES = [ + { id: 'PT-001', name: '상황 요약', category: 'summary', version: 'v2.3', abWinner: 'A', lastTest: '2026-04-05', accuracy: 94.5, tokens: 450, desc: 'AI 분석 결과 요약 (위험도·선박·해역 컨텍스트)' }, + { id: 'PT-002', name: 'XAI 설명', category: 'xai', version: 'v1.8', abWinner: 'B', lastTest: '2026-04-03', accuracy: 91.2, tokens: 620, desc: '탐지 결과 근거 설명 (피처 기여도·법적 근거)' }, + { id: 'PT-003', name: '시나리오 생성', category: 'scenario', version: 'v1.5', abWinner: 'A', lastTest: '2026-03-28', accuracy: 88.7, tokens: 850, desc: '단속 시나리오 자동 생성 (기상·선박·해역 조건)' }, + { id: 'PT-004', name: 'Q&A 답변', category: 'qa', version: 'v3.1', abWinner: 'A', lastTest: '2026-04-07', accuracy: 96.3, tokens: 380, desc: '법령·지침·유사사례 기반 자연어 답변' }, +]; + +const AB_TESTS = [ + { id: 'AB-008', prompt: 'Q&A 답변', variantA: 'v3.1 (CoT 강화)', variantB: 'v3.0 (간결형)', traffic: '50/50', winner: 'A', scoreA: 96.3, scoreB: 93.1, period: '04-01~04-07', status: '완료' }, + { id: 'AB-007', prompt: 'XAI 설명', variantA: 'v1.8 (법조문 참조)', variantB: 'v1.7 (요약형)', traffic: '50/50', winner: 'B', scoreA: 89.5, scoreB: 91.2, period: '03-25~04-03', status: '완료' }, + { id: 'AB-009', prompt: '상황 요약', variantA: 'v2.4 (구조화)', variantB: 'v2.3 (내러티브)', traffic: '50/50', winner: '-', scoreA: 93.8, scoreB: 94.1, period: '04-08~진행중', status: '진행중' }, +]; + +// ─── ③ 추론 서비스 ────────────────── +const INFERENCE_ENDPOINTS = [ + { name: '/v1/chat/completions', method: 'POST', model: 'Qwen3-8B v3.2.0', engine: 'vLLM', gpu: 'H200 x2', status: '정상', rps: 24, latency: '6.2s', p99: '9.8s', queue: 3 }, + { name: '/v1/embeddings', method: 'POST', model: 'BGE-M3', engine: 'vLLM', gpu: 'H200 x1', status: '정상', rps: 85, latency: '120ms', p99: '280ms', queue: 0 }, + { name: '/v1/rerank', method: 'POST', model: 'BGE-Reranker', engine: 'FastAPI', gpu: 'H200 x1', status: '정상', rps: 45, latency: '85ms', p99: '150ms', queue: 0 }, +]; + +const INFERENCE_KPI = [ + { label: '평균 응답시간', value: '6.2초', target: '≤10초', ok: true, icon: Clock }, + { label: '동시 처리', value: '24건', target: '≥20건', ok: true, icon: Zap }, + { label: '가용성', value: '99.95%', target: '99.9%', ok: true, icon: CheckCircle }, + { label: '대기열', value: '3건', target: '≤10건', ok: true, icon: ArrowUpDown }, +]; + +// ─── ④ 로그 및 추적 ────────────────── +const LOG_ENTRIES = [ + { id: 'QRY-48291', time: '2026-04-08 18:32:15', user: '김영수', type: 'Q&A', input: 'EEZ 침범 중국어선 단속 절차는?', tokens: 842, latency: '5.8s', status: '성공', masked: 2 }, + { id: 'QRY-48290', time: '2026-04-08 18:30:02', user: '박민지', type: '상황요약', input: '서해 NLL 인근 위험도 요약 요청', tokens: 1240, latency: '8.1s', status: '성공', masked: 5 }, + { id: 'QRY-48289', time: '2026-04-08 18:28:44', user: '이준호', type: 'XAI', input: '다크베셀 DV-2024-0892 탐지 근거', tokens: 680, latency: '4.2s', status: '성공', masked: 1 }, + { id: 'QRY-48288', time: '2026-04-08 18:25:11', user: '최수진', type: 'Q&A', input: '환적 의심 선박 MMSI 4128*** 조회', tokens: 520, latency: '3.9s', status: '성공', masked: 3 }, + { id: 'QRY-48287', time: '2026-04-08 18:22:30', user: '김영수', type: '시나리오', input: '야간 불법조업 단속 시나리오 생성', tokens: 1580, latency: '11.2s', status: '타임아웃', masked: 0 }, +]; + +const TRACE_STATS = [ + { label: '오늘 요청 수', value: '1,247건' }, + { label: '평균 토큰', value: '892' }, + { label: '마스킹 처리', value: '3,891건' }, + { label: '검색 평균', value: '1.8초' }, +]; + +// ─── ⑤ 평가 및 품질 ────────────────── +const EVAL_METRICS = [ + { version: 'v3.2.0', date: '2026-04-07', hallu: 3.2, bleu: 0.72, rouge: 0.81, f1: 0.931, faithfulness: 0.94, relevancy: 0.92, context: 0.88 }, + { version: 'v3.1.0', date: '2026-03-22', hallu: 4.1, bleu: 0.68, rouge: 0.78, f1: 0.915, faithfulness: 0.91, relevancy: 0.89, context: 0.85 }, + { version: 'v3.0.0', date: '2026-03-01', hallu: 6.8, bleu: 0.61, rouge: 0.72, f1: 0.872, faithfulness: 0.86, relevancy: 0.84, context: 0.80 }, +]; + +const EVAL_KPI = [ + { label: '할루시네이션율', value: '3.2%', target: '≤5%', ok: true }, + { label: 'F1 Score', value: '0.931', target: '≥0.91', ok: true }, + { label: 'Faithfulness', value: '0.94', target: '≥0.90', ok: true }, + { label: 'BLEU', value: '0.72', target: '≥0.65', ok: true }, +]; + +// ─── ⑥ 데이터 관리 ────────────────── +const KNOWLEDGE_SOURCES = [ + { category: '해양 법령', count: 1250, lastUpdate: '2026-04-05', embedding: '완료', version: 'v3', size: '2.8GB' }, + { category: '단속 매뉴얼', count: 380, lastUpdate: '2026-04-02', embedding: '완료', version: 'v3', size: '850MB' }, + { category: '단속 사례', count: 2150, lastUpdate: '2026-04-08', embedding: '갱신중', version: 'v3→v4', size: '4.2GB' }, + { category: 'AI 분석 결과', count: 890, lastUpdate: '2026-04-08', embedding: '완료', version: 'v3', size: '1.6GB' }, + { category: '국제 협약', count: 420, lastUpdate: '2026-03-20', embedding: '완료', version: 'v2', size: '1.1GB' }, + { category: '기상/해양 데이터', count: 310, lastUpdate: '2026-04-08', embedding: '완료', version: 'v3', size: '680MB' }, +]; + +const DATA_KPI = [ + { label: '총 등록 데이터', value: '5,400건', target: '5,200+', ok: true }, + { label: '임베딩 완료율', value: '94.2%', target: '≥90%', ok: true }, + { label: '갱신 지연', value: '18시간', target: '≤24시간', ok: true }, + { label: 'R@5', value: '87.3%', target: '≥85%', ok: true }, +]; + +// ─── ⑥-b 문서 등록 관리 ────────────────── +const DOC_CATEGORIES = [ + { key: 'law', label: '법령·고시', icon: '⚖️', desc: '해양경찰 관련 법률, 시행령, 시행규칙, 고시' }, + { key: 'guideline', label: '내부 지침', icon: '📋', desc: '해양경찰청 내부 업무 지침, 훈령, 예규' }, + { key: 'manual', label: '매뉴얼', icon: '📘', desc: '단속·수사·장비 운용 매뉴얼, SOP' }, + { key: 'enforcement', label: '단속 사례', icon: '🚔', desc: '불법조업 단속 사례, 현장 보고서' }, + { key: 'investigation', label: '수사 사례', icon: '🔍', desc: '수사 기록, 판례, 처분 결과' }, +]; + +const REGISTERED_DOCS = [ + { id: 'DOC-1842', title: '배타적경제수역에서의 외국인어업 등에 대한 주권적 권리의 행사에 관한 법률', category: 'law', version: 'v3', pages: 42, date: '2026-04-05', status: '임베딩 완료' }, + { id: 'DOC-1841', title: '불법조업 외국어선 단속 업무처리 지침 (해양경찰청 훈령 제312호)', category: 'guideline', version: 'v2', pages: 68, date: '2026-04-03', status: '임베딩 완료' }, + { id: 'DOC-1840', title: '해상단속 현장조치 매뉴얼 (2026년 개정판)', category: 'manual', version: 'v4', pages: 125, date: '2026-04-01', status: '임베딩 완료' }, + { id: 'DOC-1839', title: '중국어선 EEZ 침범 단속 사례 (2026-Q1, 서해5도)', category: 'enforcement', version: 'v1', pages: 34, date: '2026-03-30', status: '임베딩 완료' }, + { id: 'DOC-1838', title: '한중어업협정 위반 수사 사례집 (2025~2026)', category: 'investigation', version: 'v2', pages: 89, date: '2026-03-28', status: '임베딩 완료' }, + { id: 'DOC-1837', title: '무허가 조업행위 처벌 기준 고시 (해수부 고시 제2026-15호)', category: 'law', version: 'v1', pages: 18, date: '2026-03-25', status: '임베딩 완료' }, + { id: 'DOC-1836', title: '야간 불법조업 단속 SOP (함정용)', category: 'manual', version: 'v3', pages: 45, date: '2026-03-22', status: '임베딩 완료' }, + { id: 'DOC-1835', title: '다크베셀 수사 절차 가이드라인', category: 'investigation', version: 'v1', pages: 56, date: '2026-03-20', status: '갱신중' }, +]; + +// ─── ⑥-c 품질개선 (불용어·필터링·분류체계·규칙) ────────────────── +const STOPWORDS = [ + { id: 1, word: '그리고', category: '접속사', status: '활성', addedBy: '시스템', date: '2026-01-15' }, + { id: 2, word: '하지만', category: '접속사', status: '활성', addedBy: '시스템', date: '2026-01-15' }, + { id: 3, word: '그러나', category: '접속사', status: '활성', addedBy: '시스템', date: '2026-01-15' }, + { id: 4, word: '따라서', category: '접속부사', status: '활성', addedBy: '시스템', date: '2026-01-15' }, + { id: 5, word: '있습니다', category: '어미', status: '활성', addedBy: '시스템', date: '2026-01-15' }, + { id: 6, word: '됩니다', category: '어미', status: '활성', addedBy: '시스템', date: '2026-01-15' }, + { id: 7, word: '해당', category: '관형사', status: '활성', addedBy: '관리자', date: '2026-02-10' }, + { id: 8, word: '관련', category: '관형사', status: '활성', addedBy: '관리자', date: '2026-02-10' }, +]; + +const PROFANITY_FILTERS = [ + { category: '욕설·비속어', count: 1240, lastUpdate: '2026-04-01', status: '활성', action: '마스킹 치환' }, + { category: '혐오 표현', count: 380, lastUpdate: '2026-04-01', status: '활성', action: '응답 차단' }, + { category: '개인정보 패턴', count: 45, lastUpdate: '2026-03-15', status: '활성', action: '마스킹 치환' }, + { category: '프롬프트 인젝션', count: 128, lastUpdate: '2026-04-05', status: '활성', action: '입력 차단' }, + { category: '민감 군사정보', count: 62, lastUpdate: '2026-03-20', status: '활성', action: '응답 차단' }, +]; + +const TAXONOMY = [ + { id: 'TX-01', name: '불법조업 유형', items: 12, desc: 'EEZ침범, 무허가, 금지구역, 휴어기 등', version: 'v3', date: '2026-04-02' }, + { id: 'TX-02', name: '선박 분류', items: 8, desc: '어선, 화물선, 유조선, 모선, 부속선 등', version: 'v2', date: '2026-03-20' }, + { id: 'TX-03', name: '해역 구분', items: 15, desc: 'NLL, EEZ, 영해, 접속수역, 공해 등', version: 'v2', date: '2026-03-15' }, + { id: 'TX-04', name: '단속 조치', items: 10, desc: '나포, 추방, 경고, 과태료, 형사입건 등', version: 'v3', date: '2026-04-01' }, + { id: 'TX-05', name: '위험도 등급', items: 5, desc: 'CRITICAL, HIGH, MEDIUM, LOW, NORMAL', version: 'v1', date: '2026-01-10' }, +]; + +const QUALITY_RULES = [ + { id: 'QR-01', name: '법조문 참조 필수', desc: '법령 관련 답변 시 해당 법률·조항 반드시 인용', status: '활성', priority: '높음' }, + { id: 'QR-02', name: '좌표 마스킹', desc: '정밀 좌표(소수점 4자리+)는 자동 마스킹 처리', status: '활성', priority: '높음' }, + { id: 'QR-03', name: 'MMSI 익명화', desc: 'MMSI 번호 뒤 3자리 *** 처리', status: '활성', priority: '높음' }, + { id: 'QR-04', name: '불확실성 표시', desc: 'AI 확신도 80% 미만 시 "추정" 문구 삽입', status: '활성', priority: '중간' }, + { id: 'QR-05', name: '출처 명시', desc: '모든 답변에 참조 문서 ID·페이지 표기', status: '활성', priority: '중간' }, + { id: 'QR-06', name: '이중언어 지원', desc: '한국어 질의에 영문 법률명 병기 시 원문 유지', status: '활성', priority: '낮음' }, +]; + +// ─── ⑦ RAG 파이프라인 ────────────────── +const RAG_PIPELINE = [ + { stage: '① 쿼리 분석', engine: 'LangChain', status: '정상', latency: '120ms', desc: '질의 의도 분류 + 키워드 추출' }, + { stage: '② 벡터 검색', engine: 'pgvector + FAISS', status: '정상', latency: '250ms', desc: 'BGE-M3 임베딩 하이브리드 검색' }, + { stage: '③ 재정렬', engine: 'BGE Reranker', status: '정상', latency: '85ms', desc: 'Top-20 → Top-5 문서 재정렬' }, + { stage: '④ 컨텍스트 조립', engine: 'LangChain', status: '정상', latency: '30ms', desc: '프롬프트 템플릿 + 검색 결과 결합' }, + { stage: '⑤ LLM 추론', engine: 'vLLM (Qwen3-8B)', status: '정상', latency: '5.8s', desc: '최종 응답 생성' }, + { stage: '⑥ 후처리', engine: 'FastAPI', status: '정상', latency: '50ms', desc: '출력 필터링 + 참조 링크 삽입' }, +]; + +const RAG_KPI = [ + { label: 'MRR', value: '0.85', target: '≥0.82', ok: true }, + { label: 'NDCG@10', value: '0.81', target: '≥0.78', ok: true }, + { label: '재색인 주기', value: '일 1회', target: '일 1회', ok: true }, + { label: 'E2E 지연', value: '6.3초', target: '≤10초', ok: true }, +]; + +const REINDEX_HISTORY = [ + { id: 'IDX-042', date: '2026-04-08 03:00', docs: 5400, duration: '45분', delta: '+23건', status: '완료' }, + { id: 'IDX-041', date: '2026-04-07 03:00', docs: 5377, duration: '43분', delta: '+15건', status: '완료' }, + { id: 'IDX-040', date: '2026-04-06 03:00', docs: 5362, duration: '42분', delta: '+8건', status: '완료' }, +]; + +// ─── ⑧ 보안 ────────────────── +const SECURITY_LAYERS = [ + { layer: '입력 필터링', desc: '프롬프트 인젝션·악성 입력 탐지 차단', status: '활성', blocked: 12, total: 1247 }, + { layer: '출력 검증', desc: '할루시네이션·부적절 응답 필터링', status: '활성', blocked: 8, total: 1235 }, + { layer: '민감정보 마스킹', desc: 'MMSI·개인정보·좌표 자동 마스킹', status: '활성', blocked: 3891, total: 3891 }, + { layer: 'RBAC 접근제어', desc: 'JWT + 역할 기반 API 접근 제어', status: '활성', blocked: 3, total: 1250 }, + { layer: 'TLS 1.3 암호화', desc: '모든 통신 구간 암호화', status: '활성', blocked: 0, total: 0 }, + { layer: '망분리 차단', desc: '외부 LLM API 호출 완전 차단', status: '활성', blocked: 0, total: 0 }, +]; + +// ─── ⑨ 운영 모니터링 ────────────────── +const GPU_NODES = [ + { name: 'gpu-node-01', gpu: 'H200 80GB', role: 'LLM 추론 (Primary)', util: 72, mem: 58, temp: 65, status: '정상' }, + { name: 'gpu-node-02', gpu: 'H200 80GB', role: 'LLM 추론 (Secondary)', util: 45, mem: 38, temp: 52, status: '정상' }, + { name: 'gpu-node-03', gpu: 'H200 80GB', role: '임베딩 + 리랭킹', util: 35, mem: 28, temp: 48, status: '정상' }, +]; + +const SERVICES = [ + { name: 'vLLM Serving', port: 8000, status: '정상', uptime: '15d 8h', version: '0.6.2' }, + { name: 'RAG Pipeline', port: 8001, status: '정상', uptime: '15d 8h', version: '1.4.0' }, + { name: 'Embedding API', port: 8002, status: '정상', uptime: '15d 8h', version: '1.2.0' }, + { name: 'MLflow Registry', port: 5000, status: '정상', uptime: '30d 2h', version: '2.16' }, + { name: 'Airflow DAG', port: 8080, status: '정상', uptime: '30d 2h', version: '2.10' }, + { name: 'ELK Stack', port: 9200, status: '정상', uptime: '30d 2h', version: '8.15' }, + { name: 'Prometheus', port: 9090, status: '정상', uptime: '30d 2h', version: '2.54' }, + { name: 'Grafana', port: 3000, status: '정상', uptime: '30d 2h', version: '11.2' }, +]; + +const ALERT_RULES = [ + { name: 'GPU 사용률 초과', condition: 'GPU util > 90%', action: '자동 스케일링', status: '활성' }, + { name: '응답 지연 초과', condition: '응답시간 > 20초', action: 'Alertmanager 알림', status: '활성' }, + { name: '오류율 초과', condition: '오류율 > 1%', action: 'Alertmanager 알림', status: '활성' }, + { name: 'VRAM 부족', condition: 'VRAM > 90%', action: '배치 큐 일시정지', status: '활성' }, + { name: 'RAG 품질 하락', condition: 'MRR < 0.80', action: '재색인 트리거', status: '활성' }, +]; + +// ─── 헬퍼 ────────────────── +function statusIntent(s: string) { + if (['정상', '활성', '완료', '운영중', '성공'].includes(s)) return 'success' as const; + if (['경고', '갱신중', '진행중', '대기'].includes(s)) return 'warning' as const; + if (['오류', '장애', '타임아웃', '폐기'].includes(s)) return 'critical' as const; + return 'muted' as const; +} + +function kpiCard(items: { label: string; value: string; target: string; ok: boolean }[]) { + return ( +
+ {items.map((k) => ( + + +
{k.label}
+
{k.value}
+
+ {k.ok ? : } + 목표 {k.target} +
+
+
+ ))} +
+ ); +} + +// ─── 탭 콘텐츠 ────────────────── + +function ModelTab() { + return ( +
+ {/* 모델 버전 */} + + +
+ +

Qwen3-8B 모델 레지스트리

+ MLflow + Blue-Green 배포 +
+
+ + + + + + + + + + + + + + + + {MODEL_VERSIONS.map((m) => ( + + + + + + + + + + + + ))} + +
버전모델명상태파라미터배포 슬롯AccuracyF1할루시네이션등록일
{m.version}{m.name}{m.status}{m.params} + {m.deploy !== '-' && {m.deploy}} + {m.deploy === '-' && -} + {m.accuracy}%{m.f1}%{m.hallu}%{m.date}
+
+
+
+ + {/* 파인튜닝 이력 */} + + +
+ +

파인튜닝 이력

+ DVC 추적 +
+
+ + + + + + + + + + + + + + + + {FINETUNE_HISTORY.map((ft) => ( + + + + + + + + + + + + ))} + +
ID베이스 모델데이터셋EpochsLRLoRA결과 버전상태날짜
{ft.id}{ft.base}{ft.dataset}{ft.epochs}{ft.lr}{ft.lora}{ft.result}{ft.status}{ft.date}
+
+
+
+ + {/* 롤백 안내 */} + + +
+ +

Blue-Green 무중단 배포

+
+
+
+
현재 운영 (Blue)
+
v3.2.0
+
트래픽 100%
+
+
+
대기 (Green)
+
v3.1.0
+
핫스탠바이
+
+
+
롤백 소요시간
+
≤ 5분
+
KPI 목표 충족
+
+
+
+
+
+ ); +} + +function PromptTab() { + return ( +
+ {/* 프롬프트 템플릿 */} + + +
+ +

프롬프트 템플릿 (4종)

+ LangChain PromptTemplate + Git 버전관리 +
+
+ {PROMPT_TEMPLATES.map((pt) => ( +
+
+
+ {pt.name} + {pt.version} +
+ 정확도 {pt.accuracy}% +
+
{pt.desc}
+
+ A/B 우승: Variant {pt.abWinner} + 토큰: ~{pt.tokens} + 최종 테스트: {pt.lastTest} +
+
+ ))} +
+
+
+ + {/* A/B 테스트 */} + + +
+ +

A/B 테스트 이력

+ 2주 주기 자동 선정 +
+
+ + + + + + + + + + + + + + + + {AB_TESTS.map((ab) => ( + + + + + + + + + + + + ))} + +
ID프롬프트Variant AVariant BA 점수B 점수우승기간상태
{ab.id}{ab.prompt}{ab.variantA}{ab.variantB}{ab.scoreA}{ab.scoreB} + {ab.winner !== '-' ? {ab.winner} : -} + {ab.period}{ab.status}
+
+
+
+
+ ); +} + +function InferenceTab() { + return ( +
+ {kpiCard(INFERENCE_KPI.map((k) => ({ label: k.label, value: k.value, target: k.target, ok: k.ok })))} + + + +
+ +

추론 엔드포인트

+ vLLM PagedAttention + H200 GPU +
+
+ + + + + + + + + + + + + + + + {INFERENCE_ENDPOINTS.map((ep) => ( + + + + + + + + + + + + ))} + +
엔드포인트모델엔진GPU상태RPS지연P99대기열
{ep.name}{ep.model}{ep.engine}{ep.gpu}{ep.status}{ep.rps}{ep.latency}{ep.p99}{ep.queue}
+
+
+
+ + {/* 스케일링 정책 */} + + +
+ +

오토 스케일링 정책

+
+
+
+
타임아웃
+
30초
+
초과 시 재시도 3회
+
+
+
큐잉 한도
+
20건
+
초과 시 자동 스케일업
+
+
+
동시 처리
+
≥ 20건
+
현재 24건 보장
+
+
+
+
+
+ ); +} + +function LogTab() { + return ( +
+ {/* 통계 */} +
+ {TRACE_STATS.map((s) => ( + + +
{s.label}
+
{s.value}
+
+
+ ))} +
+ + {/* 로그 테이블 */} + + +
+ +

요청-응답 트랜잭션 로그

+ ELK Stack + OpenTelemetry + 보존 1년 +
+
+ + + + + + + + + + + + + + + + {LOG_ENTRIES.map((log) => ( + + + + + + + + + + + + ))} + +
질의 ID시간사용자유형입력 (마스킹)토큰지연마스킹상태
{log.id}{log.time}{log.user}{log.type}{log.input}{log.tokens}{log.latency} + {log.masked > 0 && {log.masked}건} + {log.masked === 0 && -} + {log.status}
+
+
+
+
+ ); +} + +function EvalTab() { + return ( +
+ {kpiCard(EVAL_KPI.map((k) => ({ label: k.label, value: k.value, target: k.target, ok: k.ok })))} + + + +
+ +

RAGAS 자동 평가 결과 (주 1회)

+ MLflow Experiment +
+
+ + + + + + + + + + + + + + + + {EVAL_METRICS.map((e, i) => ( + + + + + + + + + + + + ))} + +
버전평가일할루시네이션BLEUROUGEF1FaithfulnessRelevancyContext
{e.version}{e.date} + {e.hallu}% + {e.bleu}{e.rouge} + = 0.91 ? 'success' : 'warning'} size="xs">{e.f1} + {e.faithfulness}{e.relevancy}{e.context}
+
+
+
+ + {/* 버전별 개선 추이 */} + + +
+ +

버전별 품질 추이

+
+
+ {EVAL_METRICS.map((e) => ( +
+ {e.version} +
+
+ {(e.f1 * 100).toFixed(1)}% +
+
+ H {e.hallu}% +
+ ))} +
+
+
+
+ ); +} + +function DataTab() { + return ( +
+ {kpiCard(DATA_KPI)} + + {/* 문서 등록 카테고리 */} + + +
+ +

문서 등록 (법령·지침·매뉴얼·사례)

+ DVC 버전관리 +
+
+ {DOC_CATEGORIES.map((dc) => ( + + ))} +
+ {/* 등록 문서 목록 */} +
+ + + + + + + + + + + + + + {REGISTERED_DOCS.map((doc) => ( + + + + + + + + + + ))} + +
ID문서명분류버전페이지등록일상태
{doc.id}{doc.title} + + {DOC_CATEGORIES.find((c) => c.key === doc.category)?.label ?? doc.category} + + {doc.version}{doc.pages}{doc.date}{doc.status}
+
+
+
+ + + +
+ +

지식 데이터 소스

+ BGE-M3 임베딩 + pgvector +
+
+ + + + + + + + + + + + + {KNOWLEDGE_SOURCES.map((ks) => ( + + + + + + + + + ))} + +
카테고리건수최종 갱신임베딩 상태버전크기
{ks.category}{ks.count.toLocaleString()}{ks.lastUpdate}{ks.embedding}{ks.version}{ks.size}
+
+
+ 총 {KNOWLEDGE_SOURCES.reduce((s, k) => s + k.count, 0).toLocaleString()}건 등록 + 총 용량 {KNOWLEDGE_SOURCES.reduce((s, k) => s + parseFloat(k.size), 0).toFixed(1)}GB +
+
+
+
+ ); +} + +function RagTab() { + return ( +
+ {kpiCard(RAG_KPI)} + + {/* 파이프라인 단계 */} + + +
+ +

RAG 파이프라인 단계

+ pgvector + FAISS + BGE Reranker +
+
+ {RAG_PIPELINE.map((stage, i) => ( +
+
+
+ {stage.stage} + {stage.status} +
+
{stage.desc}
+
+
+
엔진
+
{stage.engine}
+
+
+
지연
+
{stage.latency}
+
+ {i < RAG_PIPELINE.length - 1 && } +
+ ))} +
+
+
+ + {/* 재색인 이력 */} + + +
+ +

Airflow DAG 재색인 이력

+ 일 1회 자동 +
+
+ + + + + + + + + + + + + {REINDEX_HISTORY.map((r) => ( + + + + + + + + + ))} + +
ID실행 시각문서 수소요시간변동상태
{r.id}{r.date}{r.docs.toLocaleString()}{r.duration}{r.delta}{r.status}
+
+
+
+
+ ); +} + +function QualityTab() { + return ( +
+ {/* 불용어 관리 */} + + +
+ +

불용어 용어 보관

+ {STOPWORDS.length}개 등록 +
+
+ + + + + + + + + + + + + {STOPWORDS.map((sw) => ( + + + + + + + + + ))} + +
#불용어분류상태등록자등록일
{sw.id}{sw.word}{sw.category}{sw.status}{sw.addedBy}{sw.date}
+
+
+
+ + {/* 불용어·욕설 필터링 */} + + +
+ +

불용어 용어 보완 (욕설·유해 표현 필터링)

+
+
+ + + + + + + + + + + + {PROFANITY_FILTERS.map((pf) => ( + + + + + + + + ))} + +
카테고리등록 패턴 수최종 갱신상태처리 방식
{pf.category}{pf.count.toLocaleString()}{pf.lastUpdate}{pf.status} + {pf.action} +
+
+
+ 총 {PROFANITY_FILTERS.reduce((s, f) => s + f.count, 0).toLocaleString()}개 패턴 등록 · 입력/출력 양방향 필터링 적용 +
+
+
+ + {/* 분류체계 보관/수정 */} + + +
+ +

분류체계 보관/수정

+
+
+ + + + + + + + + + + + + {TAXONOMY.map((tx) => ( + + + + + + + + + ))} + +
ID분류체계명항목 수설명버전갱신일
{tx.id}{tx.name}{tx.items}{tx.desc}{tx.version}{tx.date}
+
+
+
+ + {/* 규칙 보완 */} + + +
+ +

규칙 보완

+
+
+ {QUALITY_RULES.map((qr) => ( +
+
+
+ {qr.id} + {qr.name} + {qr.status} + + {qr.priority} + +
+
{qr.desc}
+
+
+ ))} +
+
+
+
+ ); +} + +function SecurityTab() { + return ( +
+ + +
+ +

3단계 보안 파이프라인

+ Keycloak RBAC + JWT / TLS 1.3 +
+
+ {SECURITY_LAYERS.map((sl) => ( +
+
+
+ {sl.layer} + {sl.status} +
+
{sl.desc}
+
+ {sl.total > 0 && ( +
+
차단/처리
+
+ {sl.blocked.toLocaleString()}/{sl.total.toLocaleString()} +
+
+ )} +
+ ))} +
+
+
+ + {/* 보안 KPI */} + + +
+ +

보안 KPI

+
+
+
+
마스킹 자동화
+
100%
+
+ + 목표 100% +
+
+
+
외부 LLM 차단
+
완전차단
+
+ + 행정망 망분리 +
+
+
+
입력 필터 차단율
+
0.96%
+
+ + 12건 차단 +
+
+
+
TLS 암호화
+
TLS 1.3
+
+ + 모든 구간 +
+
+
+
+
+
+ ); +} + +function MonitoringTab() { + return ( +
+ {/* GPU 노드 */} + + +
+ +

GPU 노드 상태

+ Prometheus + Grafana +
+
+ {GPU_NODES.map((node) => ( +
+
+ {node.name} + {node.status} +
+
{node.gpu} — {node.role}
+
+
+
+ GPU 사용률 + {node.util}% +
+
+
80 ? 'bg-red-500' : node.util > 60 ? 'bg-amber-500' : 'bg-emerald-500'}`} style={{ width: `${node.util}%` }} /> +
+
+
+
+ VRAM + {node.mem}% +
+
+
+
+
+
+ 온도 + {node.temp}°C +
+
+
+ ))} +
+ + + + {/* 서비스 상태 */} + + +
+ +

서비스 상태

+
+
+ {SERVICES.map((svc) => ( +
+
+
+
{svc.name}
+
:{svc.port} · {svc.version} · {svc.uptime}
+
+
+ ))} +
+ + + + {/* 알림 규칙 */} + + +
+ +

Alertmanager 규칙

+ 이상 탐지 ≤30초 +
+
+ + + + + + + + + + + {ALERT_RULES.map((rule) => ( + + + + + + + ))} + +
규칙명조건액션상태
{rule.name}{rule.condition}{rule.action}{rule.status}
+
+
+
+
+ ); +} + +// ─── 메인 컴포넌트 ────────────────── + +export function LLMOpsPage() { + const { t } = useTranslation('ai'); + const [tab, setTab] = useState('model'); + + const renderTab = () => { + switch (tab) { + case 'model': return ; + case 'prompt': return ; + case 'inference': return ; + case 'log': return ; + case 'eval': return ; + case 'data': return ; + case 'rag': return ; + case 'quality': return ; + case 'security': return ; + case 'monitoring': return ; + } + }; + + return ( + + + + {/* 탭 네비게이션 */} +
+ {TABS.map((t) => { + const Icon = t.icon; + return ( + + ); + })} +
+ + {renderTab()} +
+ ); +} diff --git a/frontend/src/features/ai-operations/index.ts b/frontend/src/features/ai-operations/index.ts index e39648f..e1d5f54 100644 --- a/frontend/src/features/ai-operations/index.ts +++ b/frontend/src/features/ai-operations/index.ts @@ -1,3 +1,4 @@ export { AIModelManagement } from './AIModelManagement'; export { MLOpsPage } from './MLOpsPage'; export { AIAssistant } from './AIAssistant'; +export { LLMOpsPage } from './LLMOpsPage'; diff --git a/frontend/src/lib/i18n/locales/en/ai.json b/frontend/src/lib/i18n/locales/en/ai.json index 593ba9c..4284958 100644 --- a/frontend/src/lib/i18n/locales/en/ai.json +++ b/frontend/src/lib/i18n/locales/en/ai.json @@ -7,6 +7,10 @@ "title": "MLOps / LLMOps", "desc": "SFR-18/19 | ML & LLM experiment, deployment, monitoring" }, + "llmOps": { + "title": "LLM Operations", + "desc": "SFR-20 | Qwen3-8B model management, prompts, inference, RAG, evaluation, security & monitoring" + }, "assistant": { "title": "AI Decision Support (Q&A)", "desc": "SFR-20 | Natural language query with RAG-based law, case & prediction answers" diff --git a/frontend/src/lib/i18n/locales/en/common.json b/frontend/src/lib/i18n/locales/en/common.json index c3f032b..f3dbc55 100644 --- a/frontend/src/lib/i18n/locales/en/common.json +++ b/frontend/src/lib/i18n/locales/en/common.json @@ -20,6 +20,7 @@ "reports": "Reports", "aiModel": "AI Model", "mlops": "MLOps", + "llmOps": "LLM Ops", "aiAssistant": "AI Q&A", "dataHub": "Data Hub", "systemConfig": "Settings", diff --git a/frontend/src/lib/i18n/locales/ko/ai.json b/frontend/src/lib/i18n/locales/ko/ai.json index 3d11351..3dd8bb7 100644 --- a/frontend/src/lib/i18n/locales/ko/ai.json +++ b/frontend/src/lib/i18n/locales/ko/ai.json @@ -7,6 +7,10 @@ "title": "MLOps/LLMOps", "desc": "SFR-18/19 | 기계학습·대규모 언어모델 실험·배포·모니터링 통합" }, + "llmOps": { + "title": "LLM 운영 관리", + "desc": "SFR-20 | Qwen3-8B 모델 관리·프롬프트·추론·RAG·평가·보안·모니터링 통합 운영" + }, "assistant": { "title": "AI 의사결정 지원 (Q&A)", "desc": "SFR-20 | 자연어 질의 → 법령·지침·유사사례·예측결과 통합 답변 · RAG 기반" diff --git a/frontend/src/lib/i18n/locales/ko/common.json b/frontend/src/lib/i18n/locales/ko/common.json index c910f2e..9e68ca4 100644 --- a/frontend/src/lib/i18n/locales/ko/common.json +++ b/frontend/src/lib/i18n/locales/ko/common.json @@ -20,6 +20,7 @@ "reports": "보고서 관리", "aiModel": "AI 모델관리", "mlops": "MLOps", + "llmOps": "LLM 운영", "aiAssistant": "AI 의사결정 지원", "dataHub": "데이터 허브", "systemConfig": "환경설정",