폴더에 파일을 넣기만 하면 자동으로 감지 → 추출 → LLM 분류 → 임베딩 → Teradata Vector Store 저장 → 웹 채팅에서 검색되는 "자동 수집형 개인 지식 플랫폼".
- 사용자가
/second-brain/inbox에 파일을 복사한다. - 시스템이 자동 감지 → 처리 → 검색 가능 상태로 전환한다.
- 사용자는 웹 UI에서 질문하면 관련 지식을 근거와 함께 받는다.
- 디렉토리 감시 + 파이프라인 (
inbox → processing → archive/failed) - 파일 유형: 1순위
.txt/.md, 2순위.pdf, 3순위.docx/.pptx(.xlsx/이미지/음성은 확장 단계) - LLM 자동 분류/요약/메타데이터 생성 (Ollama)
- 청킹 + bge-m3 임베딩 + Teradata Vector Store 저장
- 파일 해시 기반 중복 제거
- 웹 UI: Chat / Documents / Inbox Monitor / Categories / Failed Files (실시간 상태)
- RAG 채팅 (출처 인용 포함)
- 이미지 OCR / 음성 STT,
.xlsx표 의미화 - 멀티 사용자/권한, 클라우드 동기화
- 모바일 앱
| 영역 | 선택 | 근거 |
|---|---|---|
| Backend API | FastAPI (Python 3.11+) | 비동기, 자동 OpenAPI 문서, 워커와 코드 공유 |
| Watcher/Worker | watchdog + 내부 큐(asyncio.Queue → 추후 Redis/RQ) | 단순·안정, 단일 머신 MVP에 충분 |
| LLM | Ollama (예: qwen2.5, llama3.1) |
로컬, 분류/요약/RAG 답변 |
| Embedding | bge-m3 (Ollama 또는 sentence-transformers) |
다국어(한국어) 강함, 1024-dim |
| Vector Store / DB | Teradata Vector Store | 요구사항. 메타데이터·청크·벡터 통합 저장 |
| 문서 파싱 | pypdf/pdfplumber, python-docx, python-pptx, markdown-it, trafilatura(html/url) |
유형별 추출 |
| Frontend | Next.js 14 (App Router) + TypeScript | SSR/스트리밍, 라우팅, DX |
| UI 시스템 | Tailwind CSS + shadcn/ui + Radix + Framer Motion + lucide-react | 세련된 현대적 UX, 접근성, 애니메이션 |
| 실시간 | SSE (Server-Sent Events) | Ingestion 상태/채팅 토큰 스트리밍 |
| 상태관리 | TanStack Query + Zustand | 서버 캐시 + 경량 클라 상태 |
| 패키지/런타임 | uv(py) / pnpm(js), Docker Compose |
재현 가능한 개발 환경 |
[Local Directory] [Web UI - Next.js]
/second-brain/inbox Chat / Documents / Inbox Monitor
│ ▲ │
▼ │SSE│ REST
[Directory Watcher (watchdog)] │ ▼
│ enqueue ┌──────────────┐
▼ │ FastAPI App │
[Ingestion Queue] ───────────────▶ REST + SSE │
│ └──────┬───────┘
▼ │
[Ingestion Worker] │ RAG 검색
1 hash/중복체크 │
2 parse(추출) ┌──────────────────┴──────────────┐
3 LLM 분류/메타 │ Teradata │
4 chunk │ SB_Document / SB_DocumentChunk │
5 embed(bge-m3) │ SB_IngestionFile / Log │
6 save ────────────▶│ SB_DocumentCategory / Vector │
└─────────────────────────────────┘
│
▼ 성공: archive / 실패: failed
상태 머신:
DETECTED → PROCESSING → COMPLETED
└→ FAILED (사유 기록)
DETECTED → DUPLICATED (해시 중복)
DETECTED → SKIPPED (미지원 확장자)
second-brain-agents/
├─ docs/
│ └─ IMPLEMENTATION_PLAN.md
├─ backend/
│ ├─ app/
│ │ ├─ main.py # FastAPI 엔트리, 라우터 등록, 워커 기동
│ │ ├─ config.py # 환경설정 (pydantic-settings)
│ │ ├─ db/
│ │ │ ├─ teradata.py # 커넥션 풀, 세션
│ │ │ └─ ddl/ # 테이블 생성 SQL
│ │ ├─ ingestion/
│ │ │ ├─ watcher.py # watchdog 핸들러
│ │ │ ├─ queue.py # 인제스천 큐
│ │ │ ├─ worker.py # 파이프라인 오케스트레이션
│ │ │ ├─ hashing.py # SHA256
│ │ │ ├─ parsers/ # txt/md/pdf/docx/pptx/html
│ │ │ ├─ chunking.py
│ │ │ └─ pipeline.py # process_file()
│ │ ├─ llm/
│ │ │ ├─ ollama_client.py
│ │ │ ├─ classifier.py # 분류/메타데이터 프롬프트
│ │ │ └─ rag.py # 검색+답변 생성
│ │ ├─ embeddings/
│ │ │ └─ bge_m3.py
│ │ ├─ repositories/ # SB_* CRUD
│ │ ├─ api/
│ │ │ ├─ documents.py
│ │ │ ├─ ingestion.py # 모니터/재처리
│ │ │ ├─ categories.py
│ │ │ ├─ chat.py # SSE 스트리밍
│ │ │ └─ events.py # SSE 상태 피드
│ │ └─ schemas/ # pydantic 모델
│ ├─ tests/
│ ├─ pyproject.toml
│ └─ .env.example
├─ frontend/
│ ├─ app/
│ │ ├─ (dashboard)/
│ │ │ ├─ chat/page.tsx
│ │ │ ├─ documents/page.tsx
│ │ │ ├─ inbox/page.tsx
│ │ │ ├─ categories/page.tsx
│ │ │ └─ failed/page.tsx
│ │ ├─ layout.tsx
│ │ └─ globals.css
│ ├─ components/
│ │ ├─ ui/ # shadcn 컴포넌트
│ │ ├─ chat/
│ │ ├─ ingestion/ # 상태 타임라인, 라이브 피드
│ │ └─ layout/ # Sidebar, CommandPalette, ThemeToggle
│ ├─ lib/ # api client, sse hooks, query
│ └─ package.json
├─ second-brain/ # 감시 디렉토리 (gitignore)
│ ├─ inbox/ processing/ archive/ failed/ logs/
├─ docker-compose.yml # teradata(or 외부), ollama, backend, frontend
└─ README.md
기존 SB_Document, SB_DocumentChunk에 더해 자동 수집용 테이블 추가.
CREATE MULTISET TABLE SB_IngestionFile (
FileId BIGINT GENERATED ALWAYS AS IDENTITY,
OriginalFileName VARCHAR(500) CHARACTER SET UNICODE,
FilePath VARCHAR(1000) CHARACTER SET UNICODE,
FileHash VARCHAR(128) CHARACTER SET UNICODE,
FileExtension VARCHAR(20) CHARACTER SET UNICODE,
FileSizeBytes BIGINT,
WatchDirectory VARCHAR(1000) CHARACTER SET UNICODE,
IngestionStatus VARCHAR(30) CHARACTER SET UNICODE, -- DETECTED/PROCESSING/COMPLETED/FAILED/DUPLICATED/SKIPPED
DetectedAt TIMESTAMP(6),
StartedAt TIMESTAMP(6),
CompletedAt TIMESTAMP(6),
ErrorMessage VARCHAR(4000) CHARACTER SET UNICODE
) PRIMARY INDEX (FileId);
-- 중복 조회 최적화: FileHash 보조 인덱스CREATE MULTISET TABLE SB_DocumentCategory (
CategoryId INTEGER GENERATED ALWAYS AS IDENTITY,
CategoryName VARCHAR(200) CHARACTER SET UNICODE,
ParentCategoryId INTEGER,
Description VARCHAR(1000) CHARACTER SET UNICODE,
IsActive BYTEINT DEFAULT 1
) PRIMARY INDEX (CategoryId);시드: AI Architecture / Data Platform / Teradata / MCP / Second Brain / Customer Project / Technical Troubleshooting / Personal Notes / Meeting Notes / Research
CREATE MULTISET TABLE SB_IngestionLog (
LogId BIGINT GENERATED ALWAYS AS IDENTITY,
FileId BIGINT,
StepName VARCHAR(100) CHARACTER SET UNICODE, -- hash/parse/classify/chunk/embed/save
StepStatus VARCHAR(30) CHARACTER SET UNICODE,
LogMessage VARCHAR(4000) CHARACTER SET UNICODE,
CreatedAt TIMESTAMP(6)
) PRIMARY INDEX (FileId);SB_Document: DocumentId, FileId(FK), Title, Category, SubCategory, Summary, Keywords(JSON), Entities(JSON), DocumentType, Importance, Language, Status, CreatedAtSB_DocumentChunk: ChunkId, DocumentId(FK), ChunkIndex, ChunkText, Embedding(VECTOR/Float[]), TokenCount
- watchdog
on_created/on_moved감지. 파일 쓰기 완료 안정성을 위해 폴링(크기 안정화) 또는 일정 시간 후 처리(예: 2s 후 mtime 재확인). - 감지 즉시
inbox → processing이동,SB_IngestionFile에DETECTED → PROCESSING기록, 큐에 적재. - 미지원 확장자는
SKIPPED로 기록 후 통과.
def process_file(file_path):
file_hash = calculate_file_hash(file_path) # 1 SHA256
if is_duplicate(file_hash): # 2 중복
mark_as_duplicate(file_path); return
text = extract_text(file_path) # 3 유형별 추출
metadata = classify_with_llm(text) # 4 분류/요약/메타
chunks = split_into_chunks(text) # 5 청킹(겹침 포함)
for i, chunk in enumerate(chunks): # 6 임베딩+저장
emb = create_embedding(chunk) # bge-m3
save_chunk_to_teradata(metadata, i, chunk, emb)
finalize_document(metadata, status="indexed")- 성공 시
processing → archive,COMPLETED기록. - 예외 시
processing → failed,FAILED+ErrorMessage기록 (단계별 로그로 원인 특정).
- 프롬프트: "JSON만 반환" 강제 +
format=json(Ollama) / 스키마 검증(pydantic) + 재시도. - 산출:
title, category, sub_category, summary, keywords[], entities[], document_type, importance(1-5), language. - 긴 문서는 요약 후 분류(맵-리듀스) 또는 앞부분+목차 기반.
- 토큰 기준 분할(예: 512
800 토큰, 1015% overlap), 코드/표 경계 보존. - bge-m3 1024-dim. 배치 임베딩으로 처리량 확보.
- 파일 내용 SHA256 →
SB_IngestionFile.FileHash조회 → 존재 시DUPLICATED.
| Method | Path | 설명 |
|---|---|---|
| GET | /api/ingestion/files |
수집 파일 목록(상태/카테고리/시간 필터·페이징) |
| GET | /api/ingestion/files/{id} |
파일 상세 + 단계 로그 |
| POST | /api/ingestion/files/{id}/retry |
실패 파일 재처리 |
| GET | /api/ingestion/stream |
SSE 실시간 상태 피드 |
| GET | /api/documents |
문서 목록(검색/필터) |
| GET | /api/documents/{id} |
문서 상세 + 청크/메타 |
| GET/POST/PATCH | /api/categories |
카테고리 관리 |
| POST | /api/chat |
SSE RAG 답변 토큰 스트리밍 |
| GET | /api/stats |
대시보드 지표 |
디자인 컨셉: "Calm, focused knowledge workspace" — Linear/Vercel/Raycast 류의 절제된 미니멀 + 다크 우선 + 정보 밀도 높은 데이터 화면 + 부드러운 마이크로 인터랙션.
- 테마: 다크 우선 + 라이트 토글. CSS 변수 토큰(색/간격/반경/그림자).
next-themes. - 컬러: 뉴트럴 그레이 베이스 + 단일 액센트(예: indigo/violet). 상태색 — COMPLETED(emerald), PROCESSING(amber, 펄스), FAILED(rose), DUPLICATED(slate), DETECTED(sky).
- 타이포: Inter / Geist (UI), 본문 한글 Pretendard. 명확한 타입 스케일.
- 표면: 은은한 글래스/보더(1px hairline) + 소프트 섀도. 과한 글래스모피즘 지양.
- 모션: Framer Motion — 페이지 전환 fade/slide, 리스트 stagger, 상태 변경 시 layout 애니메이션. 200~300ms, ease-out.
prefers-reduced-motion존중. - 접근성: Radix 기반 키보드 내비/포커스 링/ARIA, 명도 대비 AA.
- 좌측 사이드바(접이식): Chat / Documents / Inbox Monitor / Categories / Failed Files + 하단 상태 인디케이터("Watcher: ● live", 큐 N건).
- 상단 바: 글로벌 검색, 테마 토글, 처리 중 카운트 배지.
- ⌘K Command Palette: 문서 검색·페이지 이동·"실패 파일 재처리" 등 액션. Raycast 스타일.
- 반응형: 데스크톱 우선, 태블릿까지 대응.
① Chat (지식 질의응답)
- 중앙 정렬 대화 캔버스, 토큰 스트리밍(타이핑 효과) + 스켈레톤.
- 답변 하단 출처 카드(문서 제목/카테고리/관련 청크 발췌) → 클릭 시 문서 상세 슬라이드오버.
- 입력창: 멀티라인, ⌘↵ 전송, 카테고리 필터 칩으로 검색 범위 좁히기.
- 빈 상태: 추천 질문 칩, 최근 인덱싱 문서 미리보기.
② Documents (문서 목록)
- 그리드/리스트 토글. 카드: 제목, 카테고리·서브카테고리 칩, 중요도(별), 요약 2줄, 키워드 태그, 언어 배지.
- 좌측 패싯 필터(카테고리/유형/중요도/기간/언어) + 정렬. 디바운스 검색.
- 카드 클릭 → 슬라이드오버 상세: 요약, 엔티티/키워드, 청크 뷰어, 원본 메타.
③ Inbox Monitor (자동 수집 상태) — 핵심 화면
- 상단 KPI 카드: 오늘 수집 수, 처리 중, 완료, 실패율(스파크라인).
- 라이브 피드: SSE로 상태가 실시간 갱신되는 테이블/타임라인.
- 컬럼:
파일명 | 상태(컬러 배지+PROCESSING 펄스) | 카테고리 | 감지시간 | 처리시간 | 오류. - 새 항목 등장 시 하이라이트 후 페이드.
- 컬럼:
- 단계 진행 표시: 행 확장 시 hash→parse→classify→chunk→embed→save 6단계 progress stepper(현재 단계 강조, 실패 단계 적색).
- 예시:
mcp_hub_note.md ● COMPLETED AI Architecture 08:31 2.3s — sales_report.pdf ● FAILED — 08:35 — PDF text extraction failed meeting.docx ◐ PROCESSING Meeting Notes 08:36 … embedding 3/8
④ Categories (자동 분류 결과 관리)
- 좌: 계층 트리(드래그로 부모 변경, 활성/비활성 토글).
- 우: 선택 카테고리의 문서 수·중요도 분포·최근 문서. 카테고리 분포 도넛/바 차트.
⑤ Failed Files (실패 파일 재처리)
- 실패 목록 + 오류 메시지 + 마지막 실패 단계.
- 재처리 버튼(단건/일괄), 재처리 시 낙관적 업데이트 + 진행 표시.
- 오류 상세 모달: 단계별 로그 타임라인.
- 토스트 알림(수집 완료/실패), 스켈레톤 로딩, 낙관적 업데이트.
- 숫자 카운트업 애니메이션(KPI), 상태 배지 펄스, 호버 시 카드 살짝 부상.
- 빈 상태/에러 상태 일러스트와 명확한 다음 행동 제시.
M0 — 환경 셋업 (0.5주)
- 모노레포 스캐폴딩, Docker Compose(Ollama·backend·frontend), Teradata 연결,
.env.
M1 — 데이터 계층 (0.5주)
- DDL 적용(SB_* 테이블), 리포지토리 + 시드 카테고리, 연결 스모크 테스트.
M2 — 인제스천 파이프라인 코어 (1.5주)
- watchdog watcher + 폴더 이동 흐름, 큐/워커, 해시·중복.
- 파서(txt/md → pdf → docx/pptx), 청킹.
- 단계별 로그 기록. CLI로 end-to-end 검증(파일 넣으면 archive까지).
M3 — LLM & 임베딩 (1주)
- Ollama 분류기(JSON 강제+검증+재시도), bge-m3 임베딩, Teradata 청크/벡터 저장.
- 벡터 검색 쿼리 + RAG 답변 파이프라인.
M4 — API & SSE (0.5주)
- documents/ingestion/categories/chat 엔드포인트, SSE 상태/채팅 스트림.
M5 — 프론트엔드 (2주)
- 디자인 시스템·레이아웃·⌘K → Inbox Monitor(라이브) → Chat(스트리밍+출처) → Documents → Categories → Failed Files.
M6 — 마감 (0.5주)
- 에러 처리/리트라이/관측(로그·지표), 접근성·반응형 점검, README/운영 문서, 데모 시드.
대략 6.5~7주 (1인 기준, 순차). 병렬 시 단축.
| 리스크 | 대응 |
|---|---|
| 파일 쓰기 미완료 상태 감지 | 크기/mtime 안정화 폴링 후 처리 |
| LLM JSON 비정형 응답 | format=json + pydantic 검증 + 재시도/폴백 기본값 |
| 대용량 문서 분류 비용 | 요약 후 분류(맵-리듀스), 토큰 상한 |
| Teradata 벡터 검색 성능 | 인덱싱/배치 임베딩, 청크 크기 튜닝 |
| 워커 단일 장애 | 상태 기반 복구(PROCESSING 잔여 재큐), 추후 Redis/RQ 멀티 워커 |
| 중복/재처리 정합성 | 해시 유니크 + 재처리 시 기존 문서/청크 정리 후 재생성 |
- 이미지 OCR / 음성 STT,
.xlsx표 의미화,.url크롤링 강화 - 하이브리드 검색(키워드+벡터), 재랭킹
- 멀티 워커 큐(Redis/RQ/Celery), 알림(Slack/메일)
- 지식 그래프(엔티티 관계), 자동 태깅 피드백 루프