# Architecture ## 전체 아키텍처 ``` ┌─────────────────────┐ │ Synology Chat │ 사용자 인터페이스 │ (NAS 192.168.1.227)│ └─────────┬───────────┘ │ Outgoing Webhook ▼ ┌────────────────────────────────────────────────────────────┐ │ bot-n8n (맥미니 Docker :5678) — 37노드 파이프라인 │ │ │ │ ⓪ 토큰 검증 + Rate Limit (username별 10초/5건) │ │ │ │ ① 규칙 기반 프리필터 │ │ └─ 인사/감사 정규식 매칭 → 하드코딩 local 응답 │ │ │ │ ② 명령어 체크 (/설정, /모델, /성격, /문서등록, /보고서) │ │ └─ 권한 체크 (ADMIN_USERNAMES allowlist) │ │ │ │ ③ GPU Qwen 9B 분류 v2 (10초 타임아웃) │ │ → {intent, response_tier, needs_rag, rag_target, ...} │ │ └─ 실패 시 fallback → api_light │ │ │ │ ④ [needs_rag=true] 멀티-컬렉션 RAG 검색 │ │ documents + tk_company + chat_memory │ │ → bge-m3 임베딩 → Qdrant 검색 → reranker → top-3 │ │ │ │ ⑤ response_tier 3단계 분기 │ │ ├─ local → Qwen 9B 직접 답변 │ │ ├─ api_light → Claude Haiku │ │ └─ api_heavy → 예산 체크 → Claude Opus (or 다운그레이드) │ │ │ │ ⑥ 응답 전송 + chat_logs + api_usage_monthly │ │ ⑦ [비동기] Qwen 메모리 판단 → 가치 있으면 벡터화 │ │ └─ classification_logs 기록 │ └──┬──────────┬───────────┬───────────┬──────────────────────┘ │ │ │ │ ▼ ▼ ▼ ▼ ┌──────┐ ┌────────┐ ┌─────────┐ ┌──────────────┐ │bot- │ │Qdrant │ │Ollama │ │Ollama (GPU) │ │postgres│ │:6333 │ │:11434 │ │192.168.1.186 │ │:15478│ │3컬렉션 │ │bge-m3 │ │:11434 │ │12테이블│ │documents│ │reranker│ │qwen3.5:9b │ │ │ │tk_company││비전모델 │ │(분류+응답) │ │ │ │chat_memory│ │ │ │ └──────┘ └────────┘ └─────────┘ └──────────────┘ ``` ## 3단계 라우팅 상세 ### response_tier 판단 (Qwen 9B 분류기 v2) | tier | 모델 | 비용 | 대상 | |------|------|------|------| | **local** | Qwen 9B (GPU) | 무료 | 인사, 잡담, 단순 확인, 감사, 짧은 반응 | | **api_light** | Claude Haiku | ~$0.8/1M in, $4/1M out | 요약, 번역, RAG 정리, 비교 분석 | | **api_heavy** | Claude Opus | ~$15/1M in, $75/1M out | 법률 해석, 다중 문서 분석, 보고서 작성 | **비용 최적화 목표:** - ~40% → local (무료, 프리필터+Qwen 직접 답변) - ~50% → Haiku (저비용) - ~10% → Opus (복잡한 질문만) ### 분류기 v2 출력 스키마 ```json { "intent": "greeting|question|calendar|reminder|mail|photo|command|report|other", "response_tier": "local|api_light|api_heavy", "needs_rag": true, "rag_target": ["documents", "tk_company", "chat_memory"], "department_hint": "안전|생산|구매|품질|null", "report_domain": "안전|시설설비|품질|null", "query": "검색용 쿼리" } ``` ### 프리필터 → 분류기 → 모델 라우팅 흐름 ``` 메시지 수신 │ ├─ 프리필터 매칭 (인사/감사 정규식) │ └─ 매칭 → 하드코딩 local 응답 (GPU 서버 미호출) │ └─ 미매칭 → Qwen 9B 분류기 ├─ response_tier=local → Qwen 9B 직접 답변 ├─ response_tier=api_light → Claude Haiku └─ response_tier=api_heavy → 예산 체크 ├─ 예산 내 → Claude Opus └─ 초과 → Claude Haiku (다운그레이드) ``` ## 3-컬렉션 RAG 상세 ### 컬렉션 구조 | 컬렉션 | 용도 | 벡터 차원 | 필터 | |--------|------|----------|------| | `documents` | 개인/일반 문서, 메일 요약 | 1024 (bge-m3) | 없음 | | `tk_company` | TK 회사 문서, 현장 리포트 | 1024 (bge-m3) | department, year, doc_type | | `chat_memory` | 가치 있는 대화 기억 | 1024 (bge-m3) | username, topic, intent | ### 멀티-컬렉션 검색 흐름 ``` rag_target에 따라 동적 검색: - 단일 컬렉션 → top-10 - 2개 → 각 top-7 - 3개 → 각 top-5 각 컬렉션별 필터: - documents: 필터 없음 - tk_company: department + year 필터 - chat_memory: username 필터 합산 → bge-reranker 리랭킹 (실패 시 Qdrant score 정렬) → top-3 출처 표시: [회사/안전/절차서] 고소작업 안전절차 - "작업 전 반드시..." [개인문서] Safety Engineering - "Workers at height..." [이전대화/2026-03-10] "고소작업은 2m 이상..." ``` ### tk_company payload ``` text, year(인덱스), department(인덱스), doc_type(인덱스), title, source_file, file_hash, chunk_index, total_chunks, uploaded_by, created_at(인덱스) ``` ### chat_memory payload ``` text, feature, intent, username(인덱스), topic(인덱스), timestamp ``` ## 선택적 대화 메모리 ``` 응답 전송 (즉시) ↓ [비동기] Qwen 9B Memorization Check: "저장: 사실 정보, 결정사항, 선호, 지시, 기술 정보" "무시: 인사, 잡담, 날씨, 봇이 모른다고 답한 것" 출력: {"save": true/false, "topic": "general|company|technical|personal"} ↓ Should Memorize? ├─ true → bge-m3 Embed → chat_memory 저장 └─ false → 스킵 (chat_logs에는 기록됨) ``` - local tier (인사 등)는 메모리 체크 자체를 스킵 (GPU 절약) ## DB 스키마 상세 ### 기존 테이블 (v1) ```sql -- ai_configs: feature별 모델/프롬프트 독립 관리 -- feature: 'classifier', 'chat', 'chat_local', 'calendar', 'mail_summary' -- routing_rules: complexity 기반 라우팅 (레거시 호환) -- prompts: 프롬프트 버전 관리 -- feature: 'classifier' v2 (활성), 'chat_local', 'memorize_check' -- chat_logs: 대화 기록 (v2: +username, +response_tier) -- mail_accounts: 메일 소스 관리 (Phase 6) ``` ### 신규 테이블 (v2) ```sql -- document_ingestion_log: 문서 등록 이력 + 버전 관리 -- file_hash 중복 체크, doc_group_key로 버전 연결 -- status: processing/completed/failed/deprecated -- field_reports: 현장 리포트 (안전/시설설비/품질 통합) -- domain, category, severity → SLA 자동 계산 -- due_at 기반 미처리 조회 -- classification_logs: 분류기 성능 모니터링 -- input_text (200자 제한), output_json (JSONB) -- fallback_used, latency_ms -- mail_logs: 메일 수신 로그 + 분류 -- calendar_events: 캘린더 이벤트 -- report_cache: 보고서 캐시 (domain + year_month UNIQUE) -- 동일 파라미터 재요청 → 캐시 반환, --force로 재생성 -- api_usage_monthly: API 사용량 + 예산 상한 -- year + month + tier UNIQUE -- estimated_cost vs budget_limit 비교 → 다운그레이드 ``` ### SLA 기준표 | domain | severity | 처리 기한 | |--------|----------|----------| | 안전 | 상 | 24시간 | | 안전 | 중 | 72시간 | | 안전 | 하 | 7일 | | 시설설비 | 상 | 48시간 | | 시설설비 | 중 | 5일 | | 시설설비 | 하 | 14일 | | 품질 | 상 | 48시간 | | 품질 | 중 | 5일 | | 품질 | 하 | 14일 | ## 안전장치 ### 보안 - **웹훅 토큰 검증**: SYNOLOGY_CHAT_TOKEN 비교, 불일치 → reject - **명령어 권한 체크**: ADMIN_USERNAMES allowlist - **서비스 포트 격리**: DB/Qdrant는 127.0.0.1 바인딩 - **classification_logs**: input 200자 제한 ### 비용 폭주 방지 - **규칙 기반 프리필터**: GPU 서버 다운 시에도 잡담은 API 미호출 - **Rate Limit**: username별 10초/5건 (in-memory, workflow static data) - **예산 상한**: api_usage_monthly → 초과 시 api_heavy→api_light 다운그레이드 ### 파이프라인 복원력 - **분류기 fallback**: Qwen 10초 타임아웃 → {response_tier: "api_light"} - **리랭커 fallback**: bge-reranker 실패 → Qdrant score 정렬 - **비전 모델 fallback**: 사진 분석 실패 → 사용자 설명만으로 구조화 ## 메인 채팅 파이프라인 v2 (37노드) ``` Webhook POST /chat │ ▼ [Parse Input] — 토큰 검증 + Rate Limit │ ├─ rejected → [Reject Response] → Send + Respond │ ▼ [Regex Pre-filter] — 인사/감사 정규식 │ ├─ match → [Pre-filter Response] → Send + Respond │ ▼ [Is Command?] │ ├─ true → [Parse Command] + 권한 체크 │ ├─ DB 필요 → [Command DB Query] → [Format] → Send + Respond │ └─ 직접 → [Direct Response] → Send + Respond │ └─ false → [Qwen Classify v2] (10초 타임아웃) │ ├─ [Log Classification] (비동기, PostgreSQL) │ ├─ needs_rag=true │ → [Get Embedding] → [Multi-Collection Search] │ → [Build RAG Context] (출처 표시) │ └─ needs_rag=false → [No RAG Context] │ ▼ [Route by Tier] ├─ local → [Call Qwen Response] ├─ api_light → [Call Haiku] └─ api_heavy → [Call Opus] (예산 체크 포함) │ ▼ [Send to Chat] + [Respond Webhook] + [Log to DB] + [API Usage Log] (API tier만) │ ▼ [비동기] [Memorization Check] → [Should Memorize?] ├─ true → [Embed & Save Memory] └─ false → (끝) ``` ## 페르소나: 이드 ### 전체 프롬프트 (api_light/api_heavy) ``` 당신의 이름은 "이드"입니다. [성격] - 배려심이 깊고 대화 상대의 기분을 우선시합니다 - 서포트하는 데 초점을 맞추며, 독선적이지 않습니다 - 의견을 제시할 때는 부드럽게, 강요하지 않습니다 - 틀린 것을 바로잡을 때도 상대방이 기분 나쁘지 않게 합니다 [말투] - 부드러운 존댓말을 사용합니다 - 자신을 지칭할 때 겸양어를 씁니다 - 자기 이름을 직접 말하지 않습니다 - 자연스럽고 편안한 톤 - 이모지는 가끔 핵심 포인트에만 사용합니다 [응답 원칙] - 간결하고 핵심적으로 답합니다 - 질문의 의도를 파악해서 필요한 만큼만 답합니다 - 모르는 것은 솔직하게, 추측은 추측이라고 밝힙니다 ``` ### 경량 프롬프트 (local tier) ``` 당신은 "이드"입니다. 배려심 깊고 부드러운 존댓말을 사용하는 개인 어시스턴트입니다. 간결하게 답하고, 모르면 솔직히 말하세요. 이모지는 핵심에만. ``` ## 향후 기능 (Phase 4-6) ### Phase 4: 회사 문서 등록 - `/문서등록 [부서] [유형] [제목]` → 청킹 → tk_company 저장 - hash 중복 체크, 문서 버전 관리 ### Phase 5: 현장 리포팅 - 사진 + 텍스트 → 비전 모델 → 구조화 → field_reports + tk_company - `/보고서 [영역] [년월]` → 월간 보고서 생성 - SLA 트래킹 + 긴급 에스컬레이션 ### Phase 6: 메일 + 캘린더 - IMAP 폴링 → Qwen 분석 → mail_logs + Qdrant - CalDAV 연동 → calendar_events