최종 검증일: 2026-01-07
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│ CTRL+F 시스템 구성도 │
└─────────────────────────────────────────────────────────────────────────────────────────┘
┌─────────────┐
│ 사용자 │
│ (Browser) │
└──────┬──────┘
│
▼
┌────────────────────────┐
│ ctrlf-front │
│ (React) │
│ │
│ - 채팅 UI │
│ - 관리자 대시보드 │
│ - 교육 영상 뷰어 │
└───────────┬────────────┘
│
│ REST API / SSE / WebSocket
▼
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│ ctrlf-back (Spring Boot) │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Auth │ │ User │ │ Education │ │ Policy │ │ Video │ │
│ │ Service │ │ Service │ │ Service │ │ Service │ │ Service │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ FAQ │ │ Quiz │ │ Telemetry │ │ Storage │ │
│ │ Service │ │ Service │ │ Service │ │ Service │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │
└────────────────────────────────────────┬────────────────────────────────────────────────┘
│
│ Internal API (X-Internal-Token)
▼
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│ ctrlf-ai (FastAPI) - AI Gateway │
│ :8000 │
│ │
│ ┌──────────────────────────────────────────────────────────────────────────────────┐ │
│ │ API Layer (FastAPI Routers) │ │
│ │ │ │
│ │ /ai/chat/* /ai/quiz/* /ai/faq/* /ai/gap/* /internal/ai/* /ws/* │ │
│ └──────────────────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌──────────────────────────────────────┴───────────────────────────────────────────┐ │
│ │ Service Layer │ │
│ │ │ │
│ │ ChatService QuizService FaqService GapService VideoRenderer SourceSetOrch │ │
│ └──────────────────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌──────────────────────────────────────┴───────────────────────────────────────────┐ │
│ │ Client Layer │ │
│ │ │ │
│ │ LLMClient MilvusClient RAGFlowClient BackendClient TTSProvider Storage │ │
│ └──────────────────────────────────────────────────────────────────────────────────┘ │
└────────────────────────────────────────┬────────────────────────────────────────────────┘
│
┌──────────────────────────┼──────────────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────────────────┐ ┌─────────────────────────┐ ┌─────────────────────────┐
│ LLM Server (vLLM) │ │ ctrlf-ragflow │ │ Milvus │
│ │ │ │ │ │
│ EXAONE-3.5-7.8B │ │ - Document Parsing │ │ - Vector Storage │
│ (OpenAI API 호환) │ │ - Chunking │ │ - Similarity Search │
│ │ │ - RAG Search │ │ │
│ :1237 │ │ :8080 │ │ :19540 │
└─────────────────────────┘ └─────────────────────────┘ └─────────────────────────┘
┌──────────────────────────┬──────────────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────────────────┐ ┌─────────────────────────┐ ┌─────────────────────────┐
│ Elasticsearch │ │ Kibana │ │ AWS S3 / MinIO │
│ │ │ │ │ │
│ - Log Storage │ │ - Log Dashboard │ │ - Video Storage │
│ - Search │ │ - Visualization │ │ - Document Storage │
│ │ │ │ │ │
│ :9200 │ │ :5601 │ │ │
└─────────────────────────┘ └─────────────────────────┘ └─────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│ ctrlf-ai (FastAPI) 내부 구조 │
└─────────────────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│ app/ │
├─────────────────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────────────────────┐ │
│ │ api/v1/ (API Layer) │ │
│ │ │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │ chat.py │ │chat_ │ │ quiz_ │ │ faq.py │ │ gap_ │ │ health.py│ │ │
│ │ │ │ │stream.py │ │generate │ │ │ │suggest │ │ │ │ │
│ │ │ /ai/chat │ │/ai/chat/ │ │/ai/quiz/ │ │/ai/faq/ │ │/ai/gap/ │ │ /health │ │ │
│ │ │ /messages│ │stream │ │generate │ │generate │ │policy- │ │ /ready │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │edu/sugg │ └──────────┘ │ │
│ │ └──────────┘ │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │source_ │ │rag_ │ │render_ │ │ws_render │ │feedback │ │ │
│ │ │sets.py │ │documents │ │jobs.py │ │_progress │ │.py │ │ │
│ │ │ │ │.py │ │ │ │.py │ │ │ │ │
│ │ │/internal │ │/internal │ │/internal │ │/ws/ │ │/internal │ │ │
│ │ │/ai/ │ │/ai/rag- │ │/ai/ │ │videos/ │ │/ai/ │ │ │
│ │ │source- │ │documents │ │render- │ │{id}/ │ │feedback │ │ │
│ │ │sets/ │ │/ingest │ │jobs │ │render- │ │ │ │ │
│ │ │{id}/ │ │ │ │ │ │progress │ │ │ │ │
│ │ │start │ │ │ │ │ │(WS) │ │ │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │
│ └─────────────────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────────────────┐ │
│ │ services/ (Business Logic) │ │
│ │ │ │
│ │ ┌────────────────────┐ ┌────────────────────┐ ┌────────────────────┐ │ │
│ │ │ chat_service.py │ │ chat_stream_ │ │ quiz_generate_ │ │ │
│ │ │ │ │ service.py │ │ service.py │ │ │
│ │ │ - PII Masking │ │ │ │ │ │ │
│ │ │ - Intent Class. │ │ - NDJSON Stream │ │ - Quiz Gen │ │ │
│ │ │ - RAG Search │ │ - Chunked Response│ │ - QC Validation │ │ │
│ │ │ - LLM Generate │ │ │ │ │ │ │
│ │ │ - Response Build │ │ │ │ │ │ │
│ │ └────────────────────┘ └────────────────────┘ └────────────────────┘ │ │
│ │ │ │
│ │ ┌────────────────────┐ ┌────────────────────┐ ┌────────────────────┐ │ │
│ │ │ faq_service.py │ │ gap_suggestion_ │ │ source_set_ │ │ │
│ │ │ │ │ service.py │ │ orchestrator.py │ │ │
│ │ │ - FAQ Draft Gen │ │ │ │ │ │ │
│ │ │ - Batch Generate │ │ - Gap Analysis │ │ - Doc Processing │ │ │
│ │ │ - RAG Search │ │ - Suggestion Gen │ │ - RAGFlow Ingest │ │ │
│ │ └────────────────────┘ └────────────────────┘ └────────────────────┘ │ │
│ │ │ │
│ │ ┌────────────────────┐ ┌────────────────────┐ ┌────────────────────┐ │ │
│ │ │ video_renderer_ │ │ render_job_ │ │ script_generation_ │ │ │
│ │ │ mvp.py │ │ runner.py │ │ service.py │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ - TTS Generation │ │ - Job Management │ │ - Script Gen │ │ │
│ │ │ - Video Assembly │ │ - Progress Track │ │ - Scene Planning │ │ │
│ │ │ - S3 Upload │ │ - Callback │ │ │ │ │
│ │ └────────────────────┘ └────────────────────┘ └────────────────────┘ │ │
│ │ │ │
│ │ ┌────────────────────┐ ┌────────────────────┐ │ │
│ │ │ intent_service │ │ pii_service.py │ │ │
│ │ │ │ │ │ │ │
│ │ │ - Rule Router │ │ - INPUT Masking │ │ │
│ │ │ - LLM Router │ │ - OUTPUT Masking │ │ │
│ │ │ - Intent Classify │ │ - LOG Masking │ │ │
│ │ └────────────────────┘ └────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────────────────┐ │
│ │ clients/ (External Connections) │ │
│ │ │ │
│ │ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │ │
│ │ │ llm_client.py │ │ milvus_client.py │ │ ragflow_client.py│ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ - Chat Complet. │ │ - Vector Search │ │ - RAG Search │ │ │
│ │ │ - Streaming │ │ - Similarity │ │ - Doc Ingest │ │ │
│ │ │ - OpenAI API │ │ │ │ - Chunk Fetch │ │ │
│ │ └──────────────────┘ └──────────────────┘ └──────────────────┘ │ │
│ │ │ │
│ │ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │ │
│ │ │ backend_client │ │ tts_provider.py │ │ storage_provider │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ - User Data │ │ - gTTS │ │ - Local │ │ │
│ │ │ - Education │ │ - AWS Polly │ │ - S3 │ │ │
│ │ │ - Callback │ │ - GCP TTS │ │ - Presigned URL │ │ │
│ │ └──────────────────┘ └──────────────────┘ └──────────────────┘ │ │
│ │ │ │
│ │ ┌──────────────────┐ │ │
│ │ │ http_client.py │ │ │
│ │ │ │ │ │
│ │ │ - httpx Async │ │ │
│ │ │ - Connection │ │ │
│ │ │ Pooling │ │ │
│ │ └──────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────────────────┐ │
│ │ core/ (Infrastructure) │ │
│ │ │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ config.py │ │ logging.py │ │ exceptions │ │ metrics.py │ │ │
│ │ │ │ │ │ │ │ │ │ │ │
│ │ │ - Settings │ │ - JSON Log │ │ - Custom │ │ - Latency │ │ │
│ │ │ - Env Vars │ │ - ELK Format │ │ Errors │ │ - Counters │ │ │
│ │ │ - Validation │ │ - Context │ │ - Upstream │ │ │ │ │
│ │ └──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────────────────┐ │
│ │ telemetry/ (Observability) │ │
│ │ │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ context.py │ │ publisher.py │ │ emitters.py │ │ models.py │ │ │
│ │ │ │ │ │ │ │ │ │ │ │
│ │ │ - Request │ │ - Async Queue│ │ - Event Emit │ │ - Telemetry │ │ │
│ │ │ Context │ │ - Batch Send │ │ - Fire&Forget│ │ Events │ │ │
│ │ │ - Trace ID │ │ - Retry │ │ │ │ │ │ │
│ │ └──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│ 채팅 요청 처리 파이프라인 │
└─────────────────────────────────────────────────────────────────────────────────────────┘
사용자 질문
│
▼
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│ 1. PII Masking (INPUT) │
│ │
│ "홍길동 사원의 연차 규정이 뭐야?" → "OOO 사원의 연차 규정이 뭐야?" │
│ │
│ - 이름, 전화번호, 이메일 등 개인정보 마스킹 │
│ - PII_ENABLED=true 시 활성화 │
└─────────────────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│ 2. Intent Classification │
│ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Rule Router │ ──YES──▶│ High Confidence│ ──YES──▶│ Skip LLM │ │
│ │ (Pattern Match)│ │ (>= 0.85) │ │ Router │ │
│ └─────────────────┘ └────────┬────────┘ └─────────────────┘ │
│ │ NO │
│ ▼ │
│ ┌─────────────────┐ │
│ │ LLM Router │ │
│ │ (EXAONE) │ │
│ └─────────────────┘ │
│ │
│ Output: IntentResult { intent_type, route_type, domain, confidence } │
│ │
│ Intent Types: POLICY_QA, EDUCATION_QA, SMALL_TALK, BACKEND_QUERY, ... │
│ Route Types: RAG_INTERNAL, LLM_ONLY, BACKEND_API, MIXED_BACKEND_RAG │
└─────────────────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│ 3. RAG Search (route=RAG_INTERNAL) │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────────────┐ │
│ │ Retrieval Backend Selection │ │
│ │ │ │
│ │ RETRIEVAL_BACKEND=ragflow ──▶ RAGFlow API Search │ │
│ │ RETRIEVAL_BACKEND=milvus ──▶ Milvus Vector Search │ │
│ └─────────────────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────────────┐ │
│ │ RAGFlow Search Flow │ │
│ │ │ │
│ │ Query ──▶ RAGFlow API ──▶ Document Chunks ──▶ Relevance Scoring │ │
│ │ │ │
│ │ dataset_id 매핑: POLICY → 사내규정, EDUCATION → 직무교육/법정의무교육 │ │
│ └─────────────────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────────────┐ │
│ │ Milvus Search Flow │ │
│ │ │ │
│ │ Query ──▶ Embedding (OpenAI/BGE-M3) ──▶ Vector Search ──▶ Top-K Results │ │
│ │ │ │
│ │ Collection: ragflow_chunks_openai (3072 dim) / ragflow_chunks_sroberta (768) │ │
│ └─────────────────────────────────────────────────────────────────────────────────┘ │
│ │
│ Output: sources[] = [{ content, doc_id, score, metadata }, ...] │
└─────────────────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│ 4. Quality Gate (Low Relevance Check) │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────────────┐ │
│ │ L2 Distance Gate │ │
│ │ │ │
│ │ min_score > RAG_MAX_L2_DISTANCE (1.5) ──▶ Low Relevance │ │
│ │ max_score < RAG_MIN_MAX_SCORE (0.55) ──▶ Low Relevance │ │
│ └─────────────────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────────────┐ │
│ │ Anchor Keyword Gate │ │
│ │ │ │
│ │ 질문의 핵심 키워드가 검색 결과에 없으면 ──▶ Soft Demote │ │
│ └─────────────────────────────────────────────────────────────────────────────────┘ │
│ │
│ Low Relevance 판정 시: │
│ - RAG_QUALITY_HARD_DROP_ENABLED=true ──▶ sources=[] (RAG 답변 금지) │
│ - RAG_QUALITY_HARD_DROP_ENABLED=false ──▶ 경고 로그만 │
└─────────────────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│ 5. LLM Prompt Building │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────────────┐ │
│ │ System Prompt │ │
│ │ │ │
│ │ "당신은 CTRL+F 기업 내부 AI 어시스턴트입니다..." │ │
│ │ "아래 검색 결과를 기반으로 답변하세요..." │ │
│ │ "검색 결과에 없는 내용은 추측하지 마세요..." │ │
│ └─────────────────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────────────┐ │
│ │ RAG Context Injection │ │
│ │ │ │
│ │ [검색 결과 1] │ │
│ │ 문서: 사내규정_연차휴가.pdf │ │
│ │ 내용: 연차휴가는 1년 만근 시 15일이 부여되며... │ │
│ │ │ │
│ │ [검색 결과 2] │ │
│ │ ... │ │
│ └─────────────────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────────────┐ │
│ │ Conversation History │ │
│ │ │ │
│ │ User: 이전 질문... │ │
│ │ Assistant: 이전 답변... │ │
│ │ (최대 4턴, 2000 토큰 제한) │ │
│ └─────────────────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│ 6. LLM Response Generation │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────────────┐ │
│ │ LLM Server (vLLM) │ │
│ │ │ │
│ │ Model: LGAI-EXAONE/EXAONE-3.5-7.8B-Instruct │ │
│ │ API: OpenAI-compatible /v1/chat/completions │ │
│ │ │ │
│ │ Alternative: OpenAI gpt-4o-mini (관리자 선택 시) │ │
│ └─────────────────────────────────────────────────────────────────────────────────┘ │
│ │
│ Timeout: TIMEOUT_LLM_SIMPLE_SEC (30s) / TIMEOUT_LLM_COMPLEX_SEC (60s) │
│ │
│ Fallback: LLM 실패 시 에러 응답 반환 (error_type: UPSTREAM_TIMEOUT/UPSTREAM_ERROR) │
└─────────────────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│ 7. Answer Guard (Post-Processing) │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────────────┐ │
│ │ Citation Validation │ │
│ │ │ │
│ │ CITATION_VALIDATION_STRICT=true 시: │ │
│ │ - RAG sources에 없는 조항 인용 ──▶ 차단 │ │
│ │ - 출처 없는 답변 ──▶ 경고 │ │
│ └─────────────────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────────────┐ │
│ │ Statistical Claim Validation │ │
│ │ │ │
│ │ STATISTICAL_CLAIM_VALIDATION=true 시: │ │
│ │ - "TOP 5", "가장 많이", "N%" 등 통계 주장 ──▶ sources 필수 │ │
│ └─────────────────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│ 8. PII Masking (OUTPUT) │
│ │
│ LLM 응답에서 개인정보 마스킹 (혹시 모를 유출 방지) │
└─────────────────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│ 9. Telemetry & Logging │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────────────┐ │
│ │ TelemetryPublisher.enqueue(event) │ │
│ │ │ │
│ │ - CHAT_TURN event │ │
│ │ - latency (rag_latency_ms, llm_latency_ms) │ │
│ │ - trace_id, user_id, conversation_id │ │
│ └─────────────────────────────────────────────────────────────────────────────────┘ │
│ │
│ Fire-and-forget: 비동기 배치 전송 (채팅 응답에 영향 없음) │
└─────────────────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│ 10. Response Build │
│ │
│ { │
│ "answer": "연차휴가는 1년 만근 시 15일이 부여됩니다...", │
│ "sources": [ │
│ { "doc_id": "...", "content": "...", "score": 0.92 } │
│ ], │
│ "meta": { │
│ "intent_type": "POLICY_QA", │
│ "route_type": "RAG_INTERNAL", │
│ "rag_latency_ms": 120, │
│ "llm_latency_ms": 850 │
│ } │
│ } │
└─────────────────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│ 문서 인덱싱 파이프라인 │
└─────────────────────────────────────────────────────────────────────────────────────────┘
Backend (Spring)
│
│ POST /internal/ai/source-sets/{sourceSetId}/start
│ X-Internal-Token: ctrlf-b2a-xxx
│
▼
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│ AI Gateway - SourceSet Orchestrator │
│ │
│ 1. 요청 수신 ──▶ 202 Accepted 즉시 반환 (비동기 처리) │
│ │
│ 2. 백그라운드 처리 시작 │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────────────────┐ │
│ │ For each document in sourceSet │ │
│ │ │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ Download │ ──▶ │ RAGFlow │ ──▶ │ Poll Status │ │ │
│ │ │ File (S3) │ │ Ingest │ │ Until DONE │ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │
│ │ │ │
│ │ Timeout: RAGFLOW_POLL_TIMEOUT_SEC (40분) │ │
│ │ Retry: RAGFLOW_MAX_RETRY_COUNT (2회) │ │
│ └─────────────────────────────────────────────────────────────────────────────────┘ │
│ │
│ 3. 완료 콜백 │
│ │ │
│ ▼ │
└─────────────────────────────────────────────────────────────────────────────────────────┘
│
│ POST /internal/source-sets/{sourceSetId}/complete
│ { "status": "COMPLETED", "processed": 10, "failed": 0 }
│
▼
Backend (Spring)
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│ 영상 렌더링 파이프라인 │
└─────────────────────────────────────────────────────────────────────────────────────────┘
Backend (Spring)
│
│ POST /internal/ai/render-jobs
│ { "jobId": "job-001", "videoId": "video-001" }
│
▼
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│ AI Gateway - Video Renderer │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────────────┐ │
│ │ 1. Render Spec Fetch │ │
│ │ │ │
│ │ Backend API 호출 ──▶ Script + Scene 정보 조회 ──▶ Snapshot 저장 │ │
│ └─────────────────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────────────────┐ │
│ │ 2. TTS Generation │ │
│ │ │ │
│ │ For each scene: │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ Text Split │ ──▶ │ TTS Request │ ──▶ │ Audio Merge │ │ │
│ │ │ (Sentences) │ │ (gTTS/Polly)│ │ (per Scene) │ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │
│ │ │ │
│ │ TTS Provider: gTTS (기본) / AWS Polly / GCP TTS │ │
│ └─────────────────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────────────────┐ │
│ │ 3. Video Assembly │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────────────────────────────────┐ │ │
│ │ │ VIDEO_VISUAL_STYLE = "basic" │ │ │
│ │ │ - 단색 배경 + 텍스트 오버레이 │ │ │
│ │ │ - 자막 (caption) 타임라인 동기화 │ │ │
│ │ └─────────────────────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────────────────────────────────┐ │ │
│ │ │ VIDEO_VISUAL_STYLE = "animated" │ │ │
│ │ │ - Scene 이미지 + Ken Burns 효과 │ │ │
│ │ │ - Fade 전환 (VIDEO_FADE_DURATION: 0.5s) │ │ │
│ │ └─────────────────────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ Output: 1920x1080, 30fps MP4 │ │
│ └─────────────────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────────────────┐ │
│ │ 4. Storage Upload │ │
│ │ │ │
│ │ STORAGE_PROVIDER = "local" ──▶ Local Filesystem │ │
│ │ STORAGE_PROVIDER = "s3" ──▶ AWS S3 Direct │ │
│ │ STORAGE_PROVIDER = "backend_presigned" ──▶ Backend Presigned URL │ │
│ │ │ │
│ │ Retry: STORAGE_UPLOAD_RETRY_MAX (3회) │ │
│ └─────────────────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────────────────┐ │
│ │ 5. WebSocket Progress │ │
│ │ │ │
│ │ /ws/videos/{video_id}/render-progress │ │
│ │ │ │
│ │ { "status": "PROCESSING", "step": "GENERATE_TTS", "progress": 25 } │ │
│ │ { "status": "PROCESSING", "step": "ASSEMBLE_VIDEO", "progress": 75 } │ │
│ │ { "status": "COMPLETED", "progress": 100, "video_url": "..." } │ │
│ └─────────────────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────────────────┘
│
│ POST /internal/videos/{videoId}/render-complete
│ { "status": "COMPLETED", "videoUrl": "..." }
│
▼
Backend (Spring)
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│ 서비스 간 통신 흐름 │
└─────────────────────────────────────────────────────────────────────────────────────────┘
┌─────────────┐ REST/SSE ┌─────────────┐ Internal API ┌─────────────┐
│ ctrlf- │ ◀─────────────▶ │ ctrlf- │ ◀────────────────▶ │ ctrlf-ai │
│ front │ │ back │ X-Internal-Token │ (FastAPI) │
│ (React) │ │ (Spring) │ │ │
└─────────────┘ └──────┬──────┘ └──────┬──────┘
│ │
│ │
┌────────────────────┼──────────────────────────────────┼────────────┐
│ │ │ │
▼ ▼ ▼ │
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ Database │ │ AWS S3 │ │ LLM Server │ │
│ (MySQL) │ │ (Storage) │ │ (vLLM) │ │
└─────────────┘ └─────────────┘ └─────────────┘ │
│ │
│ OpenAI API │
│ Compatible │
┌──────────────────────────────────────────────────────┘ │
│ │
▼ │
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ ctrlf- │ │ Milvus │ │Elasticsearch│ │
│ ragflow │ │ (Vector) │ │ (Logs) │ │
└─────────────┘ └─────────────┘ └─────────────┘ │
│ │ │ │
└────────────────────┴────────────────────┴──────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│ 인증 토큰 체계 │
├─────────────────────────────────────────────────────────────────────────────────────────┤
│ │
│ Backend → AI: │
│ ┌─────────────────────────────────────────────────────────────────────────────────┐ │
│ │ Header: X-Internal-Token: ${BACKEND_INTERNAL_TOKEN} │ │
│ │ 용도: /internal/ai/* 엔드포인트 인증 │ │
│ └─────────────────────────────────────────────────────────────────────────────────┘ │
│ │
│ AI → Backend: │
│ ┌─────────────────────────────────────────────────────────────────────────────────┐ │
│ │ Header: X-Internal-Token: ${BACKEND_SERVICE_TOKEN} │ │
│ │ 용도: 콜백 및 데이터 조회 API 인증 │ │
│ └─────────────────────────────────────────────────────────────────────────────────┘ │
│ │
│ AI → RAGFlow: │
│ ┌─────────────────────────────────────────────────────────────────────────────────┐ │
│ │ Header: Authorization: Bearer ${RAGFLOW_API_KEY} │ │
│ │ 용도: RAGFlow API 인증 │ │
│ └─────────────────────────────────────────────────────────────────────────────────┘ │
│ │
│ RAGFlow → AI (Callback): │
│ ┌─────────────────────────────────────────────────────────────────────────────────┐ │
│ │ Header: X-Internal-Token: ${AI_CALLBACK_TOKEN} │ │
│ │ 용도: RAGFlow ingest 완료 콜백 인증 │ │
│ └─────────────────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────────────────┘