docs: architecture.md 대규모 갱신 — GPU 서버 재구성 반영

- ChromaDB → Qdrant 전체 치환 (28건)
- nomic-embed-text → bge-m3 (1024차원) 전체 치환 (12건)
- Qwen2.5-VL-7B → Surya OCR (:8400) 전체 치환 (5건)
- VRAM 다이어그램 갱신 (~11.3GB → ~7-8GB)
- 3-Tier 라우팅 전략, 모델 협업 파이프라인 갱신
- Komga 만화 서버 GPU 서버 이전 반영
- embed_to_chroma.py 삭제 (embed_to_qdrant.py로 대체)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hyungi
2026-03-30 13:45:16 +09:00
parent 5db2f4f6fa
commit f21f950c04
2 changed files with 68 additions and 172 deletions

View File

@@ -169,12 +169,12 @@ DEVONthink 4의 커스텀 메타데이터 필드를 활용합니다.
### AI 결과물 저장 전략 — 중복 저장 금지 ### AI 결과물 저장 전략 — 중복 저장 금지
GPU 서버에서 처리된 AI 결과물은 **각자 목적에 맞는 곳에만** 저장합니다. GPU 서버에서 처리된 AI 결과물은 **각자 목적에 맞는 곳에만** 저장합니다.
DEVONthink와 ChromaDB에 같은 정보를 이중으로 넣지 않습니다. DEVONthink와 Qdrant에 같은 정보를 이중으로 넣지 않습니다.
``` ```
처리 결과 저장 위치 이유 처리 결과 저장 위치 이유
─────────────────────────────────────────────────────── ───────────────────────────────────────────────────────
벡터 임베딩 ChromaDB만 시맨틱 검색 전용, DEVONthink에선 쓸모없음 벡터 임베딩 Qdrant만 시맨틱 검색 전용, DEVONthink에선 쓸모없음
비전 OCR 텍스트 DEVONthink 본문에 병합 검색 가능한 텍스트가 되어야 하므로 필수 비전 OCR 텍스트 DEVONthink 본문에 병합 검색 가능한 텍스트가 되어야 하므로 필수
리랭킹 점수 저장 안 함 (휘발) 쿼리 시점에만 의미 있는 일회성 데이터 리랭킹 점수 저장 안 함 (휘발) 쿼리 시점에만 의미 있는 일회성 데이터
태그/분류 DEVONthink 태그만 Smart Group, 브라우징에 활용 태그/분류 DEVONthink 태그만 Smart Group, 브라우징에 활용
@@ -183,10 +183,10 @@ OmniFocus 역링크 DEVONthink 메타데이터 양방향 참조에 필요
``` ```
**핵심 원칙:** **핵심 원칙:**
- ChromaDB = 벡터 검색 엔진. 여기엔 임베딩만 들어감 - Qdrant = 벡터 검색 엔진. 여기엔 임베딩만 들어감
- DEVONthink = 원본 문서 + 사람이 읽는 메타데이터(태그, 링크) - DEVONthink = 원본 문서 + 사람이 읽는 메타데이터(태그, 링크)
- 요약/분석은 RAG로 실시간 생성하면 되므로 별도 캐싱 불필요 - 요약/분석은 RAG로 실시간 생성하면 되므로 별도 캐싱 불필요
- 비전 모델의 OCR 결과만 DEVONthink 본문에 반드시 병합 (검색성 확보) - Surya OCR 결과만 DEVONthink 본문에 반드시 병합 (검색성 확보)
--- ---
@@ -211,7 +211,7 @@ OmniFocus 역링크 DEVONthink 메타데이터 양방향 참조에 필요
DEVONagent ────┤ ┌──────────────┐ DEVONagent ────┤ ┌──────────────┐
스캔 문서 ──────┼──► Inbox ──►│ Smart Rule │──► 자동 태깅 스캔 문서 ──────┼──► Inbox ──►│ Smart Rule │──► 자동 태깅
이메일 ────────┤ │ + Ollama API │ + 적절한 DB로 이동 이메일 ────────┤ │ + Ollama API │ + 적절한 DB로 이동
파일 드롭 ──────┘ │ + GPU 서버 │ + 벡터 인덱싱 (ChromaDB) 파일 드롭 ──────┘ │ + GPU 서버 │ + 벡터 인덱싱 (Qdrant)
└──────────────┘ + OCR 텍스트 병합 (스캔 시) └──────────────┘ + OCR 텍스트 병합 (스캔 시)
OmniFocus 작업 생성 OmniFocus 작업 생성
@@ -225,9 +225,9 @@ DEVONagent ────┤ ┌─────────────
트리거: Inbox DB에 새 문서 추가 트리거: Inbox DB에 새 문서 추가
조건: 태그가 비어있음 조건: 태그가 비어있음
동작: 동작:
1. 이미지/스캔 문서 → GPU 서버 VL-7B로 OCR → 본문에 병합 1. 이미지/스캔 문서 → GPU 서버 Surya OCR(:8400)로 OCR → 본문에 병합
2. Mac mini 35B → 태그 + 분류 대상 DB 생성 → DEVONthink 태그에만 저장 2. Mac mini 35B → 태그 + 분류 대상 DB 생성 → DEVONthink 태그에만 저장
3. GPU 서버 nomic-embed → 벡터화 → ChromaDB에만 저장 3. GPU 서버 bge-m3 → 벡터화 → Qdrant에만 저장
4. 태그 기반 도메인 DB 자동 이동: 4. 태그 기반 도메인 DB 자동 이동:
#주제/프로그래밍, #주제/AI-ML → 05_Programming #주제/프로그래밍, #주제/AI-ML → 05_Programming
#주제/공학, #주제/네트워크 → 03_Engineering #주제/공학, #주제/네트워크 → 03_Engineering
@@ -249,7 +249,7 @@ DEVONagent ────┤ ┌─────────────
동작: 동작:
1. 발신자 기준 그룹 자동 생성/분류 1. 발신자 기준 그룹 자동 생성/분류
2. 첨부파일 추출 → 태그 기반 도메인 DB로 복제 (기술문서→03, 도면→97 등) 2. 첨부파일 추출 → 태그 기반 도메인 DB로 복제 (기술문서→03, 도면→97 등)
3. GPU 서버에서 벡터 임베딩 → ChromaDB 인덱싱 3. GPU 서버에서 벡터 임베딩 → Qdrant 인덱싱
※ 이메일 요약은 저장하지 않음 (RAG로 검색 시 생성) ※ 이메일 요약은 저장하지 않음 (RAG로 검색 시 생성)
``` ```
@@ -336,8 +336,8 @@ on performSmartRule(theRecords)
end if end if
end try end try
-- Step 4: GPU 서버 → 벡터 임베딩 → ChromaDB 인덱싱 (비동기) -- Step 4: GPU 서버 → 벡터 임베딩 → Qdrant 인덱싱 (비동기)
do shell script "python3 ~/scripts/embed_to_chroma.py " & ¬ do shell script "python3 ~/scripts/embed_to_qdrant.py " & ¬
quoted form of docUUID & " &" quoted form of docUUID & " &"
-- Step 5: 처리 완료 표시 -- Step 5: 처리 완료 표시
@@ -567,59 +567,59 @@ if __name__ == "__main__":
│ RTX 4070 Ti Super 16GB VRAM │ │ RTX 4070 Ti Super 16GB VRAM │
│ │ │ │
│ ┌──────────────────────┐ ┌──────────────────────────────────┐ │ │ ┌──────────────────────┐ ┌──────────────────────────────────┐ │
│ │ 👁️ 비전 모델 │ │ 🔍 리랭커 (Reranker) │ │ │ │ 📄 Surya OCR │ │ 🔍 리랭커 (Reranker) │ │
│ │ Qwen2.5-VL-7B (8Q) │ │ bge-reranker-v2-m3 │ │ │ │ FastAPI :8400 │ │ bge-reranker-v2-m3 │ │
│ │ VRAM: ~8GB │ │ VRAM: ~1GB │ │ │ │ VRAM: ~2-3GB │ │ VRAM: ~1GB │ │
│ │ │ │ │ │ │ │ │ │ │ │
│ │ 용도: │ │ 용도: │ │ │ │ 용도: │ │ 용도: │ │
│ │ · 스캔 문서 분석 │ │ · RAG 검색 품질 극대화 │ │ │ │ · 스캔 문서 OCR │ │ · RAG 검색 품질 극대화 │ │
│ │ · 이미지 캡션/태깅 │ │ · 임베딩 검색 후 정밀 재정렬 │ │ │ │ · 이미지 텍스트 추출 │ │ · 임베딩 검색 후 정밀 재정렬 │ │
│ │ · 차트/그래프 해석 │ │ · Top-K → Top-N 정확도 향상 │ │ │ │ · 만화 말풍선 OCR │ │ · Top-K → Top-N 정확도 향상 │ │
│ │ · 사진 자동 분류 │ │ │ │ │ │ · 한/영/일 다국어 │ │ │ │
│ · OCR 보완 │ │ │ └───────────────────────┘ └──────────────────────────────────┘
│ └──────────────────────┘ └──────────────────────────────────┘ │
│ │ │ │
│ ┌──────────────────────┐ ┌──────────────────────────────────┐ │ │ ┌──────────────────────┐ ┌──────────────────────────────────┐ │
│ │ 🔗 임베딩 모델 │ │ 📊 VRAM 배분 │ │ │ │ 🔗 임베딩 모델 │ │ 📊 VRAM 배분 │ │
│ │ nomic-embed-text │ │ │ │ │ │ bge-m3 (1024차원) │ │ │ │
│ │ VRAM: ~0.3GB │ │ 비전 모델 (8Q): ~8GB │ │ │ │ VRAM: ~1.5GB │ │ Surya OCR: ~2-3GB │ │
│ │ │ │ 리랭커: ~1GB │ │ │ │ │ │ 리랭커: ~1GB │ │
│ │ 용도: │ │ 임베딩: ~0.3GB │ │ │ │ 용도: │ │ 임베딩: ~1.5GB │ │
│ │ · 문서 벡터 임베딩 │ │ 시스템: ~2GB │ │ │ │ · 문서 벡터 임베딩 │ │ Plex HW 트랜스: ~1-2GB │ │
│ │ · RAG 인덱싱 │ │ ───────────────────── │ │ │ │ · RAG 인덱싱 │ │ ───────────────────── │ │
│ │ · 쿼리 임베딩 │ │ 합계: ~11.3GB / 16GB │ │ │ │ · 쿼리 임베딩 │ │ 합계: ~7-8GB / 16GB │ │
│ │ │ │ 여유: ~4.7GB ✅ │ │ │ │ │ │ 여유: ~8-9GB ✅ │ │
│ │ ※ GPU 가속으로 │ │ │ │ │ │ ※ GPU 가속으로 │ │ │ │
│ │ 대량 임베딩 시 유리 │ │ │ │ │ │ 대량 임베딩 시 유리 │ │ │ │
│ └──────────────────────┘ └──────────────────────────────────┘ │ │ └──────────────────────┘ └──────────────────────────────────┘ │
│ │ │ │
│ ┌─────────────────────────────────────────────────────────────┐ │ │ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 🎬 미디어 서비스 │ │ │ │ 🎬 미디어 + 만화 서비스 │ │
│ │ Plex Media Server — GPU 하드웨어 트랜스코딩 활용 │ │ │ │ Plex Media Server — GPU 하드웨어 트랜스코딩 │ │
│ │ Komga — 만화 서버 (Docker, NFS → NAS /Comic) │ │
│ └─────────────────────────────────────────────────────────────┘ │ │ └─────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘ └─────────────────────────────────────────────────────────────────┘
``` ```
### 임베딩을 GPU 서버로 이전하는 이유 ### 임베딩을 GPU 서버로 이전하는 이유
임베딩 모델(nomic-embed-text)을 Mac mini에서 GPU 서버로 이전하는 것을 **권장**합니다: 임베딩 모델(bge-m3)을 Mac mini에서 GPU 서버로 이전하는 것을 **권장**합니다:
| 비교 항목 | Mac mini에서 실행 | GPU 서버에서 실행 | | 비교 항목 | Mac mini에서 실행 | GPU 서버에서 실행 |
|---|---|---| |---|---|---|
| **대량 인덱싱 속도** | CPU 기반, 느림 | CUDA 가속, 5-10배 빠름 | | **대량 인덱싱 속도** | CPU 기반, 느림 | CUDA 가속, 5-10배 빠름 |
| **Mac mini 부하** | 35B 모델 + 임베딩 동시 시 경합 | 35B 모델 전용, 쾌적 | | **Mac mini 부하** | 35B 모델 + 임베딩 동시 시 경합 | 35B 모델 전용, 쾌적 |
| **VRAM 영향** | 해당 없음 | +0.3GB (무시할 수준) | | **VRAM 영향** | 해당 없음 | +1.5GB (bge-m3, 1024차원) |
| **네트워크 레이턴시** | 없음 | 2.5G 네트워크, 1ms 미만 | | **네트워크 레이턴시** | 없음 | 2.5G 네트워크, 1ms 미만 |
| **배치 처리** | 문서 100개 인덱싱 시 수분 | 문서 100개 인덱싱 시 수십초 | | **배치 처리** | 문서 100개 인덱싱 시 수분 | 문서 100개 인덱싱 시 수십초 |
| **ChromaDB 위치** | Mac mini 유지 | Mac mini 유지 (변동 없음) | | **Qdrant 위치** | Mac mini 유지 | Mac mini 유지 (변동 없음) |
**결론:** 임베딩 모델은 단일 요청 레이턴시보다 **배치 처리량**이 중요합니다. **결론:** 임베딩 모델은 단일 요청 레이턴시보다 **배치 처리량**이 중요합니다.
GPU 서버의 CUDA 가속을 활용하면 대량 문서 인덱싱이 훨씬 빨라지고, GPU 서버의 CUDA 가속을 활용하면 대량 문서 인덱싱이 훨씬 빨라지고,
Mac mini의 통합메모리를 35B 모델에 온전히 할당할 수 있습니다. Mac mini의 통합메모리를 35B 모델에 온전히 할당할 수 있습니다.
nomic-embed-text는 0.3GB에 불과해 GPU 서버 VRAM에 거의 영향이 없고, bge-m3는 ~1.5GB로 GPU 서버 VRAM 16GB 대비 여유 충분하고,
2.5G 네트워크 환경이라 API 호출 레이턴시도 무시할 수준입니다. 2.5G 네트워크 환경이라 API 호출 레이턴시도 무시할 수준입니다.
다만 **ChromaDB는 Mac mini에 유지**합니다. RAG 질의 시 벡터 검색 → 다만 **Qdrant는 Mac mini에 유지**합니다. RAG 질의 시 벡터 검색 →
리랭킹 → 35B 응답 생성이 연속으로 일어나는데, 벡터 DB가 로컬에 있어야 리랭킹 → 35B 응답 생성이 연속으로 일어나는데, 벡터 DB가 로컬에 있어야
이 파이프라인이 가장 빠릅니다. 이 파이프라인이 가장 빠릅니다.
@@ -638,15 +638,15 @@ nomic-embed-text는 0.3GB에 불과해 GPU 서버 VRAM에 거의 영향이 없
│ Mac mini │ │ Claude │ │ GPU 서버 │ │ Mac mini │ │ Claude │ │ GPU 서버 │
│ (메인) │ │ (클라우드) │ │ (보조) │ │ (메인) │ │ (클라우드) │ │ (보조) │
├─────────────────┤ ├──────────────┤ ├────────────────────┤ ├─────────────────┤ ├──────────────┤ ├────────────────────┤
│ Qwen3.5-35B-A3B │ │ Sonnet 4.6 │ │ Qwen2.5-VL-7B (8Q) │ Qwen3.5-35B-A3B │ │ Sonnet 4.6 │ │ Surya OCR (:8400)
│ 4Q / ~80 tok/s │ │ │ │ bge-reranker-v2-m3 │ │ 4Q / ~80 tok/s │ │ │ │ bge-reranker-v2-m3 │
│ │ │ │ │ nomic-embed-text │ │ │ │ │ bge-m3 (1024차원)
├─────────────────┤ ├──────────────┤ ├────────────────────┤ ├─────────────────┤ ├──────────────┤ ├────────────────────┤
│ · 자동 태깅/분류 │ │ · 심층 분석 │ │ · 이미지/스캔 분석 │ · 자동 태깅/분류 │ │ · 심층 분석 │ │ · 스캔/이미지 OCR
│ · 문서 요약 │ │ · 리서치 합성 │ │ · RAG 리랭킹 │ │ · 문서 요약 │ │ · 리서치 합성 │ │ · RAG 리랭킹 │
│ · 메타데이터 │ │ · 보고서 생성 │ │ · 문서 임베딩/인덱싱│ │ · 메타데이터 │ │ · 보고서 생성 │ │ · 문서 임베딩/인덱싱│
│ · 액션아이템추출 │ │ · 복잡한 추론 │ │ · 사진 자동 분류 │ · 액션아이템추출 │ │ · 복잡한 추론 │ │ · 만화 텍스트 추출
│ · RAG 응답생성 │ │ · 다국어 번역 │ │ · OCR 후처리 │ · RAG 응답생성 │ │ · 다국어 번역 │ │ · 한/영/일 다국어
├─────────────────┤ ├──────────────┤ ├────────────────────┤ ├─────────────────┤ ├──────────────┤ ├────────────────────┤
│ 속도: ~80 tok/s │ │ 속도: ~3초 │ │ 속도: GPU 가속 │ │ 속도: ~80 tok/s │ │ 속도: ~3초 │ │ 속도: GPU 가속 │
│ 비용: 무료 │ │ 비용: 과금 │ │ 비용: 무료 │ │ 비용: 무료 │ │ 비용: 과금 │ │ 비용: 무료 │
@@ -659,13 +659,13 @@ nomic-embed-text는 0.3GB에 불과해 GPU 서버 VRAM에 거의 영향이 없
| 조건 | 라우팅 | 이유 | | 조건 | 라우팅 | 이유 |
|---|---|---| |---|---|---|
| 텍스트 문서 + 태깅/분류/요약 | Tier 1 (Mac mini 35B) | 메인 범용, 품질 충분 | | 텍스트 문서 + 태깅/분류/요약 | Tier 1 (Mac mini 35B) | 메인 범용, 품질 충분 |
| 이미지 포함 문서 / 스캔 PDF | Tier 3 → Tier 1 | 비전 모델로 텍스트 추출 후 35B로 분석 | | 이미지 포함 문서 / 스캔 PDF | Tier 3 → Tier 1 | Surya OCR로 텍스트 추출 후 35B로 분석 |
| 심층 분석 / 긴 보고서 생성 | Tier 2 (Claude API) | 최고 품질 필요 시 | | 심층 분석 / 긴 보고서 생성 | Tier 2 (Claude API) | 최고 품질 필요 시 |
| RAG 검색 결과 리랭킹 | Tier 3 (GPU reranker) | 검색 정확도 극대화 | | RAG 검색 결과 리랭킹 | Tier 3 (GPU reranker) | 검색 정확도 극대화 |
| RAG 최종 응답 생성 | Tier 1 (Mac mini 35B) | 컨텍스트 기반 응답 | | RAG 최종 응답 생성 | Tier 1 (Mac mini 35B) | 컨텍스트 기반 응답 |
| 새 문서 벡터 인덱싱 | Tier 3 (GPU embed) | CUDA 가속 배치 처리 | | 새 문서 벡터 인덱싱 | Tier 3 (GPU embed) | CUDA 가속 배치 처리 |
| 대량 배치 (100+ 문서) | Tier 1 + Tier 3 병렬 | 양쪽 분산 처리 | | 대량 배치 (100+ 문서) | Tier 1 + Tier 3 병렬 | 양쪽 분산 처리 |
| Synology Photos 자동 태깅 | Tier 3 (GPU vision) | 이미지 분석 특화 | | 만화 OCR (Komga 연동) | Tier 3 (GPU Surya OCR) | GPU 서버 로컬 처리 |
### 모델 간 협업 파이프라인 ### 모델 간 협업 파이프라인
@@ -674,26 +674,26 @@ nomic-embed-text는 0.3GB에 불과해 GPU 서버 VRAM에 거의 영향이 없
1. [Smart Rule 트리거] 새 PDF 감지, 이미지 기반 문서로 판단 1. [Smart Rule 트리거] 새 PDF 감지, 이미지 기반 문서로 판단
2. [GPU 서버 · Qwen2.5-VL-7B 8Q] 2. [GPU 서버 · Surya OCR :8400]
이미지 분석 → 텍스트 추출 (OCR) → DEVONthink 본문에 병합 이미지/스캔 PDF → OCR 텍스트 추출 → DEVONthink 본문에 병합
3. [Mac mini · Qwen3.5-35B-A3B] 3. [Mac mini · Qwen3.5-35B-A3B]
추출된 텍스트로 태그 생성 → DEVONthink 태그에만 저장 추출된 텍스트로 태그 생성 → DEVONthink 태그에만 저장
4. [GPU 서버 · nomic-embed-text] 4. [GPU 서버 · bge-m3]
문서 벡터 임베딩 → ChromaDB에만 저장 문서 벡터 임베딩 → Qdrant에만 저장
5. [결과] DEVONthink에는 본문(OCR)+태그+처리일시만 5. [결과] DEVONthink에는 본문(OCR)+태그+처리일시만
ChromaDB에는 벡터만. 요약은 저장하지 않음 (RAG로 실시간 생성) Qdrant에는 벡터만. 요약은 저장하지 않음 (RAG로 실시간 생성)
예시: RAG 질의 시 예시: RAG 질의 시
1. [사용자 질문] "서버 마이그레이션 관련 자료 정리해줘" 1. [사용자 질문] "서버 마이그레이션 관련 자료 정리해줘"
2. [GPU 서버 · nomic-embed-text] 쿼리 임베딩 2. [GPU 서버 · bge-m3] 쿼리 임베딩
3. [Mac mini · ChromaDB] 벡터 유사도 검색 → Top-20 후보 3. [Mac mini · Qdrant] 벡터 유사도 검색 → Top-20 후보
4. [GPU 서버 · bge-reranker-v2-m3] 4. [GPU 서버 · bge-reranker-v2-m3]
Top-20 → 정밀 리랭킹 → Top-5 선정 Top-20 → 정밀 리랭킹 → Top-5 선정
@@ -714,9 +714,9 @@ OLLAMA_MAX_LOADED_MODELS=3 # 동시 로드 모델 3개 (비전+리랭커+
OLLAMA_KEEP_ALIVE=10m # 미사용 시 10분 후 언로드 OLLAMA_KEEP_ALIVE=10m # 미사용 시 10분 후 언로드
# 모델 다운로드 # 모델 다운로드
ollama pull qwen2.5-vl:7b-instruct-q8_0 # 비전 모델 8Q (~8GB) # Surya OCR은 별도 systemd 서비스로 운영 (:8400)
ollama pull bge-reranker-v2-m3 # 리랭커 (~1GB) ollama pull bge-reranker-v2-m3 # 리랭커 (~1GB)
ollama pull nomic-embed-text # 임베딩 (~0.3GB) ollama pull bge-m3 # 임베딩 (~1.5GB, 1024차원)
# Mac mini에서 GPU 서버 호출 예시 # Mac mini에서 GPU 서버 호출 예시
# 비전 분석 # 비전 분석
@@ -725,11 +725,11 @@ curl http://gpu-server:11434/api/generate \
# 임베딩 (배치) # 임베딩 (배치)
curl http://gpu-server:11434/api/embed \ curl http://gpu-server:11434/api/embed \
-d '{"model":"nomic-embed-text", "input":["문서1 텍스트", "문서2 텍스트", ...]}' -d '{"model":"bge-m3", "input":["문서1 텍스트", "문서2 텍스트", ...]}'
``` ```
**`keep_alive` 활용 전략:** **`keep_alive` 활용 전략:**
- 비전 모델 (8Q): `keep_alive: "30m"` — 자주 사용, 항상 대기 - Surya OCR: systemd 서비스로 상시 구동 (포트 8400)
- 리랭커: `keep_alive: "10m"` — RAG 쿼리 시 활성 - 리랭커: `keep_alive: "10m"` — RAG 쿼리 시 활성
- 임베딩: `keep_alive: "30m"` — 새 문서 인덱싱 빈도에 맞춰 - 임베딩: `keep_alive: "30m"` — 새 문서 인덱싱 빈도에 맞춰
@@ -750,20 +750,20 @@ curl http://gpu-server:11434/api/embed \
│ [청킹] → 의미 단위로 텍스트 분할 (500토큰) │ │ [청킹] → 의미 단위로 텍스트 분할 (500토큰) │
│ │ │ │ │ │
│ ▼ │ │ ▼ │
│ [임베딩] → GPU 서버 Ollama (nomic-embed-text, CUDA) │ │ [임베딩] → GPU 서버 Ollama (bge-m3, CUDA) │
│ │ │ │ │ │
│ ▼ │ │ ▼ │
│ [벡터 저장] → ChromaDB (Mac mini 로컬) │ │ [벡터 저장] → Qdrant (Mac mini 로컬) │
│ │ │ │ │ │
│ ─ ─ ─ ─ ─ ─ 쿼리 시 ─ ─ ─ ─ ─ ─ │ │ ─ ─ ─ ─ ─ ─ 쿼리 시 ─ ─ ─ ─ ─ ─ │
│ │ │ │ │ │
│ [질문 입력] │ │ [질문 입력] │
│ │ │ │ │ │
│ ▼ │ │ ▼ │
│ [쿼리 임베딩] → GPU 서버 (nomic-embed-text) │ │ [쿼리 임베딩] → GPU 서버 (bge-m3) │
│ │ │ │ │ │
│ ▼ │ │ ▼ │
│ [유사도 검색] → ChromaDB (Mac mini, Top-20) │ │ [유사도 검색] → Qdrant (Mac mini, Top-20) │
│ │ │ │ │ │
│ ▼ │ │ ▼ │
│ [리랭킹] → GPU 서버 (bge-reranker, Top-5 선정) │ │ [리랭킹] → GPU 서버 (bge-reranker, Top-5 선정) │
@@ -841,7 +841,7 @@ Smart Rule 2차: 하위 그룹 라우팅
→ 80_Reference/Standards/ → 80_Reference/Standards/
ChromaDB 벡터 인덱싱 (비동기) Qdrant 벡터 인덱싱 (비동기)
→ RAG 검색에 즉시 반영 → RAG 검색에 즉시 반영
@@ -1023,7 +1023,7 @@ Mac mini에서는 **자동 스케줄 리서치**, 맥북에서는 **현장 수
│ 배치 + 자동화 중심 │ 인터랙티브 + 즉시성 중심 │ │ 배치 + 자동화 중심 │ 인터랙티브 + 즉시성 중심 │
├────────────────────────┴────────────────────────────────┤ ├────────────────────────┴────────────────────────────────┤
│ 공통: 결과는 모두 DEVONthink Inbox → CloudKit 동기화 │ │ 공통: 결과는 모두 DEVONthink Inbox → CloudKit 동기화 │
│ → Mac mini Smart Rule이 자동 태깅 + ChromaDB 인덱싱 │ │ → Mac mini Smart Rule이 자동 태깅 + Qdrant 인덱싱 │
└─────────────────────────────────────────────────────────┘ └─────────────────────────────────────────────────────────┘
``` ```
@@ -1095,7 +1095,7 @@ DEVONthink에서 자료 검색/열람 (동기화된 DB)
[RAG 질의 시] [RAG 질의 시]
Tailscale 연결 → RAG API에 자연어 질문 Tailscale 연결 → RAG API에 자연어 질문
→ Mac mini에서 GPU 임베딩 → ChromaDB 검색 → 리랭킹 → 35B 응답 → Mac mini에서 GPU 임베딩 → Qdrant 검색 → 리랭킹 → 35B 응답
→ 결과에 x-devonthink-item:// 링크 포함 → 결과에 x-devonthink-item:// 링크 포함
→ 맥북 DEVONthink에서 해당 문서 바로 열기 → 맥북 DEVONthink에서 해당 문서 바로 열기
@@ -1183,7 +1183,7 @@ RAG 시스템으로 내 지식베이스에 질문
│ 완료 5건 | 신규 3건 | 기한초과 1건 │ │ 완료 5건 | 신규 3건 | 기한초과 1건 │
│ │ │ │
│ ■ 시스템 상태 │ │ ■ 시스템 상태 │
ChromaDB 벡터: 12,847개 (+15) │ Qdrant 벡터: 12,847개 (+15) │
│ Inbox 잔여: 2건 │ │ Inbox 잔여: 2건 │
│ NAS 동기화: 정상 │ │ NAS 동기화: 정상 │
└─────────────────────────────────────────────┘ └─────────────────────────────────────────────┘
@@ -1194,7 +1194,7 @@ RAG 시스템으로 내 지식베이스에 질문
· Inbox 미처리 3건 이상 → "Inbox 정리 필요 (N건 미분류)" · Inbox 미처리 3건 이상 → "Inbox 정리 필요 (N건 미분류)"
· 시정조치 overdue → "시정조치 기한초과: [내용]" (긴급 플래그) · 시정조치 overdue → "시정조치 기한초과: [내용]" (긴급 플래그)
· 분류 실패 문서 존재 → "수동 분류 필요 (N건)" · 분류 실패 문서 존재 → "수동 분류 필요 (N건)"
· ChromaDB 인덱싱 실패 → "벡터 인덱싱 오류 점검" · Qdrant 인덱싱 실패 → "벡터 인덱싱 오류 점검"
출력 3 — Synology Chat 알림 (선택, 한 줄 요약): 출력 3 — Synology Chat 알림 (선택, 한 줄 요약):
"📋 오늘 다이제스트: 신규 12건, 법령변경 2건, overdue 1건 ⚠" "📋 오늘 다이제스트: 신규 12건, 법령변경 2건, overdue 1건 ⚠"
@@ -1222,7 +1222,7 @@ RAG 시스템으로 내 지식베이스에 질문
end tell end tell
5. 시스템 상태 — Python 5. 시스템 상태 — Python
ChromaDB collection.count(), NAS ping, sync 로그 확인 Qdrant collection.count(), NAS ping, sync 로그 확인
6. 상위 뉴스 요약 — Ollama 35B 6. 상위 뉴스 요약 — Ollama 35B
오늘 수집된 뉴스 중 상위 3건을 2-3문장으로 요약 오늘 수집된 뉴스 중 상위 3건을 2-3문장으로 요약
@@ -1259,8 +1259,8 @@ OmniFocus 리뷰 → 완료 작업의 DEVONthink 메타데이터 업데이트
□ DEVONsphere Express 설치 □ DEVONsphere Express 설치
□ OmniFocus, OmniOutliner, OmniGraffle, OmniPlan 설치 □ OmniFocus, OmniOutliner, OmniGraffle, OmniPlan 설치
□ Ollama 확인 (이미 설치됨) □ Ollama 확인 (이미 설치됨)
□ GPU 서버에 nomic-embed-text, Qwen2.5-VL-7B 8Q, bge-reranker 다운로드 □ GPU 서버에 bge-m3, bge-reranker 다운로드 + Surya OCR 서비스 설치
ChromaDB 설치 (pip install chromadb) — Mac mini Qdrant (Docker, Mac mini) — pkm_documents 컬렉션 (1024차원, Cosine)
□ Python 환경 설정 (venv 권장) □ Python 환경 설정 (venv 권장)
□ Plex Media Server를 GPU 서버로 이전 □ Plex Media Server를 GPU 서버로 이전
``` ```
@@ -1288,7 +1288,7 @@ OmniFocus 리뷰 → 완료 작업의 DEVONthink 메타데이터 업데이트
``` ```
□ Ollama 태깅/분류 프롬프트 최적화 □ Ollama 태깅/분류 프롬프트 최적화
□ Claude API 키 Keychain 등록 □ Claude API 키 Keychain 등록
□ RAG 파이프라인 구축 (GPU 서버 임베딩 + Mac mini ChromaDB) □ RAG 파이프라인 구축 (GPU bge-m3 임베딩 + Mac mini Qdrant + MLX 35B 응답)
□ DEVONthink Smart Rule과 AI 연동 테스트 □ DEVONthink Smart Rule과 AI 연동 테스트
□ DEVONagent 자동 검색 스케줄 설정 □ DEVONagent 자동 검색 스케줄 설정
``` ```
@@ -1325,7 +1325,7 @@ OmniPlan 0.5GB 낮음
OmniOutliner 0.3GB 낮음 OmniOutliner 0.3GB 낮음
OmniGraffle 0.5GB 낮음 OmniGraffle 0.5GB 낮음
MLX (Qwen3.5-35B-A3B 4bit) ~20GB 중간 MoE: 3B만 활성 MLX (Qwen3.5-35B-A3B 4bit) ~20GB 중간 MoE: 3B만 활성
ChromaDB 1-2GB 낮음 Qdrant (Docker) 1-2GB 낮음
Roon Core 2-4GB 낮음 Roon Core 2-4GB 낮음
Komga 0.5GB 낮음 Komga 0.5GB 낮음
기타 시스템 4-6GB - 기타 시스템 4-6GB -
@@ -1347,9 +1347,9 @@ Plex를 GPU 서버로 이전하고 임베딩도 GPU로 넘김으로써, Mac mini
``` ```
서비스 VRAM 상태 비고 서비스 VRAM 상태 비고
───────────────────────────────────────────────────────────── ─────────────────────────────────────────────────────────────
Qwen2.5-VL-7B (8Q) ~8GB 상주 비전/이미지 분석 Surya OCR (systemd) ~2-3GB 상주 문서/만화 OCR
bge-reranker-v2-m3 ~1GB 상주 RAG 리랭킹 bge-reranker-v2-m3 ~1GB 상주 RAG 리랭킹
nomic-embed-text ~0.3GB 상주 임베딩 (CUDA 가속) bge-m3 (1024차원) ~1.5GB 상주 임베딩 (CUDA 가속)
Plex HW Transcoding ~1-2GB 간헐적 NVENC/NVDEC 활용 Plex HW Transcoding ~1-2GB 간헐적 NVENC/NVDEC 활용
시스템 오버헤드 ~2GB - 시스템 오버헤드 ~2GB -

View File

@@ -1,104 +0,0 @@
#!/usr/bin/env python3
"""
벡터 임베딩 스크립트
- DEVONthink 문서 UUID로 텍스트 추출
- GPU 서버(nomic-embed-text)로 임베딩 생성
- ChromaDB에 저장
"""
import os
import sys
import requests
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent))
from pkm_utils import setup_logger, load_credentials, run_applescript_inline
logger = setup_logger("embed")
# ChromaDB 저장 경로
CHROMA_DIR = Path.home() / ".local" / "share" / "pkm" / "chromadb"
CHROMA_DIR.mkdir(parents=True, exist_ok=True)
def get_document_text(uuid: str) -> tuple[str, str]:
"""DEVONthink에서 UUID로 문서 텍스트 + 제목 추출"""
script = f'''
tell application id "DNtp"
set theRecord to get record with uuid "{uuid}"
set docText to plain text of theRecord
set docTitle to name of theRecord
return docTitle & "|||" & docText
end tell
'''
result = run_applescript_inline(script)
parts = result.split("|||", 1)
title = parts[0] if len(parts) > 0 else ""
text = parts[1] if len(parts) > 1 else ""
return title, text
def get_embedding(text: str, gpu_server_ip: str) -> list[float] | None:
"""GPU 서버의 nomic-embed-text로 임베딩 생성"""
url = f"http://{gpu_server_ip}:11434/api/embeddings"
try:
resp = requests.post(url, json={
"model": "nomic-embed-text",
"prompt": text[:8000] # 토큰 제한
}, timeout=60)
resp.raise_for_status()
return resp.json().get("embedding")
except Exception as e:
logger.error(f"임베딩 생성 실패: {e}")
return None
def store_in_chromadb(doc_id: str, title: str, text: str, embedding: list[float]):
"""ChromaDB에 저장"""
import chromadb
client = chromadb.PersistentClient(path=str(CHROMA_DIR))
collection = client.get_or_create_collection(
name="pkm_documents",
metadata={"hnsw:space": "cosine"}
)
collection.upsert(
ids=[doc_id],
embeddings=[embedding],
documents=[text[:2000]],
metadatas=[{"title": title, "source": "devonthink"}]
)
logger.info(f"ChromaDB 저장: {doc_id} ({title[:30]})")
def run(uuid: str):
"""단일 문서 임베딩 처리"""
logger.info(f"임베딩 처리 시작: {uuid}")
creds = load_credentials()
gpu_ip = creds.get("GPU_SERVER_IP")
if not gpu_ip:
logger.warning("GPU_SERVER_IP 미설정 — 임베딩 건너뜀")
return
try:
title, text = get_document_text(uuid)
if not text or len(text) < 10:
logger.warning(f"텍스트 부족 [{uuid}]: {len(text)}")
return
embedding = get_embedding(text, gpu_ip)
if embedding:
store_in_chromadb(uuid, title, text, embedding)
logger.info(f"임베딩 완료: {uuid}")
else:
logger.error(f"임베딩 실패: {uuid}")
except Exception as e:
logger.error(f"임베딩 처리 에러 [{uuid}]: {e}", exc_info=True)
if __name__ == "__main__":
if len(sys.argv) < 2:
print("사용법: python3 embed_to_chroma.py <DEVONthink_UUID>")
sys.exit(1)
run(sys.argv[1])