Files
syn-chat-bot/docs/architecture.md
Hyungi Ahn 782caf5130 feat: DEVONthink 제거 + 모닝 브리핑 추가
- DEVONthink 의존성 제거 → kb_writer 전환 (news_digest, inbox_processor, mail pipeline)
- devonthink_bridge.py, plist 삭제
- morning_briefing.py 신규 (매일 07:30, 일정·메일·보고·뉴스 → Synology Chat)
- intent_service.py 분류기 프롬프트 개선 + 키워드 fallback
- migrate-v5.sql (news_digest_log kb_path 컬럼)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-19 14:12:38 +09:00

18 KiB

Architecture

전체 아키텍처

┌─────────────────────┐
│   Synology Chat     │  사용자 인터페이스
│   (NAS 192.168.1.227)│
└─────────┬───────────┘
          │ Outgoing Webhook
          ▼
┌────────────────────────────────────────────────────────────┐
│  bot-n8n (맥미니 Docker :5678) — 51노드 파이프라인          │
│                                                            │
│  ⓪ 토큰 검증 + Rate Limit (username별 10초/5건)            │
│                                                            │
│  ① 규칙 기반 프리필터                                       │
│     └─ 인사/감사 정규식 매칭 → 하드코딩 local 응답           │
│                                                            │
│  ② 명령어 체크 (/설정, /모델, /성격, /문서등록, /보고서)      │
│     └─ 권한 체크 (ADMIN_USERNAMES allowlist)                │
│                                                            │
│  ③ GPU Qwen 9B 분류 v3 (10초 타임아웃)                     │
│     → {intent, response_tier, needs_rag, rag_target, ...}  │
│     └─ 실패 시 fallback → api_light                        │
│                                                            │
│  ③-1 Route by Intent                                       │
│     ├─ log_event  → Qwen 추출 → bge-m3 → tk_company 저장   │
│     ├─ report     → 현장 리포트 → field_reports DB 저장      │
│     ├─ calendar   → CalDAV Bridge → Synology Calendar       │
│     ├─ reminder   → calendar로 통합 처리                     │
│     ├─ mail       → 메일 요약 조회 (mail_logs)               │
│     ├─ note       → KB Writer → 문서 저장                    │
│     └─ fallback   → 일반 대화 (RAG + 3단계 라우팅)           │
│                                                            │
│  ④ [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 메모리 판단 → 가치 있으면 벡터화 + KB 저장   │
│     └─ 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│        │ │              │
└──────┘ └────────┘ └─────────┘ └──────────────┘

┌────────────────────────────────────────────────────────────┐
│  별도 워크플로우                                            │
│  Mail Processing Pipeline (7노드)                          │
│    └─ MailPlus IMAP 폴링 → Qwen 분류 → mail_logs 저장      │
└────────────────────────────────────────────────────────────┘

┌────────────────────────────────────────────────────────────┐
│  네이티브 서비스 (맥미니)                                    │
│                                                            │
│  heic_converter.py (:8090)                                 │
│    └─ HEIC→JPEG 변환 (macOS sips)                          │
│                                                            │
│  chat_bridge.py (:8091)                                    │
│    ├─ DSM Chat API 폴링 (5초) → 사진 감지 + ack             │
│    ├─ POST /chat/recent-photo → 사진 다운+변환               │
│    └─ HEIC 자동 변환 (→ heic_converter :8090)               │
│                                                            │
│  caldav_bridge.py (:8092)                                  │
│    └─ CalDAV REST 래퍼 (Synology Calendar CRUD)             │
│                                                            │
│  inbox_processor.py (LaunchAgent, 5분)                     │
│    └─ OmniFocus Inbox 폴링 → Qwen 분류 → 자동 정리          │
│                                                            │
│  news_digest.py (LaunchAgent, 매일 07:00)                  │
│    └─ RSS 뉴스 수집 → Qwen 번역·요약 → Qdrant + Synology Chat│
│                                                            │
│  morning_briefing.py (LaunchAgent, 매일 07:30)             │
│    └─ 일정·메일·보고·뉴스 → 요약 → Synology Chat 전송       │
└────────────────────────────────────────────────────────────┘

┌────────────────────────────────────────────────┐
│  NAS 서비스 (192.168.1.227)                     │
│  Synology Chat / Calendar (CalDAV) / MailPlus   │
└────────────────────────────────────────────────┘

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 (복잡한 질문만)

분류기 v3 출력 스키마

{
  "intent": "greeting|question|log_event|calendar|reminder|mail|note|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": "검색용 쿼리"
}

Intent별 처리:

  • calendar — CalDAV Bridge로 일정 생성/조회 (Synology Calendar)
  • reminder — calendar로 통합 (알림 시간 포함 일정 생성)
  • mail — mail_logs에서 최근 메일 요약 조회
  • note — KB Writer로 문서 저장

### 프리필터 → 분류기 → 모델 라우팅 흐름

메시지 수신 │ ├─ 프리필터 매칭 (인사/감사 정규식) │ └─ 매칭 → 하드코딩 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)

-- 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 비교 → 다운그레이드

-- news_digest_log: 뉴스 요약 이력
--   source_url UNIQUE, translated_title, summary
--   published_at, processed_at, qdrant_point_id

calendar_events 확장 (v3)

-- v3에서 추가된 컬럼:
--   caldav_uid: CalDAV 이벤트 UID (Synology Calendar 연동)
--   description: 이벤트 상세 설명
--   created_by: 생성 출처 (chat, caldav_sync, manual)

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: 사진 분석 실패 → 사용자 설명만으로 구조화
  • HEIC 자동 변환: macOS sips 기반 HEIC→JPEG 변환 (heic_converter.py, port 8090)
  • 사진 브릿지: chat_bridge.py가 DSM Chat API 폴링 → 사진 감지 → ack → n8n 요청 시 다운로드+변환+base64 반환
  • 이미지 base64 변환: Ollama API는 URL 미지원, chat_bridge가 자동 다운로드 후 base64 전달

메인 채팅 파이프라인 v3 (51노드)

Webhook POST /chat
    │
    ▼
[Parse Input] — 토큰 검증 + Rate Limit + channelId/userId 추출
    │
    ├─ rejected → [Reject Response] → Send + Respond
    │
    ▼
[Has Pending Doc?] — 문서 등록 대기
    │
    ├─ pending → [Process Document] → [Log Doc Ingestion] → 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 v3] (10초 타임아웃)
                │
                ├─ [Log Classification] (비동기, PostgreSQL)
                │
                ├─ [Route by Intent]
                │   ├─ log_event → [Handle Log Event] (Qwen 추출→임베딩→tk_company 저장→확인응답)
                │   ├─ report   → [Handle Field Report] → [Save Field Report DB]
                │   ├─ calendar → [Handle Calendar] → CalDAV Bridge → 확인응답
                │   ├─ reminder → [Handle Calendar] (calendar로 통합)
                │   ├─ mail     → [Handle Mail] → mail_logs 조회 → 요약응답
                │   ├─ note     → [Handle Note] → KB Writer → 확인응답
                │   └─ fallback → [Needs RAG?]
                │
                ├─ 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] + [KB 저장]
                └─ false → (끝)

페르소나: 이드

전체 프롬프트 (api_light/api_heavy)

당신의 이름은 "이드"입니다.

[성격]
- 배려심이 깊고 대화 상대의 기분을 우선시합니다
- 서포트하는 데 초점을 맞추며, 독선적이지 않습니다
- 의견을 제시할 때는 부드럽게, 강요하지 않습니다
- 틀린 것을 바로잡을 때도 상대방이 기분 나쁘지 않게 합니다

[말투]
- 부드러운 존댓말을 사용합니다
- 자신을 지칭할 때 겸양어를 씁니다
- 자기 이름을 직접 말하지 않습니다
- 자연스럽고 편안한 톤
- 이모지는 가끔 핵심 포인트에만 사용합니다

[응답 원칙]
- 간결하고 핵심적으로 답합니다
- 질문의 의도를 파악해서 필요한 만큼만 답합니다
- 모르는 것은 솔직하게, 추측은 추측이라고 밝힙니다

경량 프롬프트 (local tier)

당신은 "이드"입니다. 배려심 깊고 부드러운 존댓말을 사용하는 개인 어시스턴트입니다.
간결하게 답하고, 모르면 솔직히 말하세요. 이모지는 핵심에만.

구현 완료

Phase 4: 회사 문서 등록

  • /문서등록 [부서] [유형] [제목] → 청킹 → tk_company 저장
  • hash 중복 체크, 문서 버전 관리

Phase 5: 현장 리포팅 + API 사용량 추적

  • 사진 + 텍스트 → Claude Haiku Vision → 구조화 → field_reports + tk_company
  • /보고서 [영역] [년월] → 월간 보고서 생성 (report_cache)
  • API 사용량 추적 (api_usage_monthly UPSERT)
  • HEIC→JPEG 변환 (heic_converter.py) + chat_bridge.py (DSM Chat API 브릿지)

Phase 6: 캘린더·메일·OmniFocus·뉴스

  • 분류기 v3: calendar, reminder, mail, note intent 추가
  • caldav_bridge.py: CalDAV REST 래퍼 (Synology Calendar CRUD)
  • inbox_processor.py: OmniFocus Inbox 폴링 (LaunchAgent, 5분)
  • news_digest.py: 뉴스 번역·요약 (LaunchAgent, 매일 07:00)
  • Mail Processing Pipeline (7노드): IMAP 폴링 → 분류 → mail_logs
  • 51노드 파이프라인: calendar/mail/note 핸들러 추가

향후 기능 (Phase 7+)

  • SLA 트래킹 스케줄 워크플로우 + 긴급 에스컬레이션
  • CalDAV 양방향 동기화 (Synology Calendar → bot-postgres)
  • 메일 발송 (SMTP via MailPlus)
  • reminder 실구현 (알림 시간에 Synology Chat 푸시)
  • 모닝 브리핑 고도화 (주간 요약, 커스텀 섹션)