소개
나만의 산재되어 있는 정보들을 수집하는 Agent를 만들어본 사례글 입니다.
진행 방법
스타트업실험실 금번 AI OS 만들기 수업에 맞추어 나만의 Agent를 만들어보자는 생각을 하면서
지난 번 글처럼 PKM Agent를 만들고 있었고, 최종 완성이 되어서 생성기 글을 올려보고자 합니다.
사용한 도구는 Claude Code와 Codex입니다.
코덱스로 주로 하다가 인증 이슈로 마무리는 클코 Opus 4.8로 했습니다.
(모델도 중요하지만 요즘엔 성능이 다 좋아서 큰 차이 는 없는 것 같습니다)
시스템 개요는 다음과 같습니다.
Personal Knowledge Agent는 여러 소스에 흩어진 노트와 메모를 자동으로 수집하고, AI가 정제 제안을 생성하면 사용자가 검토·승인하는 **Vault-First Pipeline** 시스템입니다.
[수집 소스 → raw 저장(vault/00_inbox/raw) → 정제 제안 생성 → 사용자 승인 → 구조화 노트(vault/20_notes)]지원소스
1) 로컬 폴더 | .md, .txt, .pdf 파일이 있는 로컬 디렉토리
2) UpNote Export | UpNote 앱에서 내보낸 폴더
3) Notion 페이지 | Notion API를 통한 단일 페이지
4) Notion 데이터소스 | Notion 데이터베이스 전체 또는 증분 동기화
5) Webhook | 모바일 메모앱 등 외부 앱에서 직접 전송 주요기능
- 원본 보존 — raw 노트는 절대 자동 삭제·덮어쓰기 안 함
- 승인 게이트 — 정제 결과는 사용자 승인 후에만 vault에 반영
- 충돌 감지 — 기반 해시가 다르면 409로 거부, 원본 훼손 방지
- 증분 동기화 — UpNote/Notion 모두 변경된 파일·페이지만 재수집
- Qdrant 벡터 검색 — 수집된 모든 노트를 sparse vector로 인덱싱, 영속 검색
- 운영 대시보드 — job 현황, 처리 이력, 감사 로그 실시간 조회로컬에서 실행하는 절차
(주소 등은 개인정보 이슈로 삭제했습니다. 깃허브는 private입니다)
생성절차가 완료되었고, 하단의 문서의 경우 실행 절차를 상세히 정리해서 md로 저장해 달라고 한 내용을 가져왔습니다. 각 터미널에서 시작되고 백엔드와 프런트엔드가 구동이 됩니다. 환경 설치를 실행하고 나서
3-1. 저장소 클론 및 환경 파일 설정
```bash
git clone https://github.com/Educalvin7/personal-knowledge-agent
cd personal-knowledge-agent
# 환경 파일 복사
cp .env.example .env
```
`.env`를 열어 필요한 값을 채웁니다. 최소 설정은 기본값으로도 동작합니다.
3-2. 인프라 시작 (PostgreSQL + Qdrant)
```bash
make dev
```
3-3. 백엔드 시작
```bash
cd backend
python3 -m venv .venv
.venv/bin/pip install -e .
.venv/bin/uvicorn app.main:app --reload
```
> 백엔드 API: http://127.0.0.1
> Swagger 문서: http://127.0.0.1/docs
3-4. 프런트엔드 시작
새 터미널에서:
```bash
cd frontend
npm install
npm run dev
```
> 프런트엔드: http://127.0.0.1
3-5. 헬스 체크
```bash
curl http://127.0.0.1/health
# → {"status": "ok"}
```배포 및 빌드
환경변수를 설정해 준 후 실제 빌드를 시작합니다.
4-1. `.env` 준비
```bash
cp .env.example .env
```
`.env`에서 반드시 변경해야 하는 항목:
```dotenv
POSTGRES_PASSWORD=강력한_패스워드_설정
NOTION_API_TOKEN=secret_xxxx # Notion 연동 시
NOTION_DATABASE_ID=xxxx # Notion DB 연동 시
```
### 4-2. 프로덕션 컨테이너 빌드 및 시작
```bash
make prod-up
```
내부적으로 다음을 수행합니다:
- PostgreSQL, Qdrant, 백엔드, 프런트엔드 컨테이너 빌드 및 시작
- 볼륨으로 데이터 영속화 (`postgres_data`, `qdrant_data`, `vault_data`)
- healthcheck 통과 후 백엔드가 DB에 연결
**접속 주소:**
- 프런트엔드: `http://서버IP`
- 백엔드 API: `http://서버IP`
### 4-3. 운영 명령어
```bash
# 로그 실시간 확인
make prod-logs
# 서비스 중지 (데이터 보존)
make prod-down
# 서비스 재시작
make prod-down && make prod-up
```
### 4-4. 데이터 위치
| 데이터 | Docker 볼륨 | 설명 |
|--------|------------|------|
| PostgreSQL DB | `postgres_data` | 문서 메타데이터, 승인 이력, 감사 로그 |
| Qdrant 벡터 인덱스 | `qdrant_data` | 검색용 sparse vector |
| Vault 파일 | `vault_data` | raw 노트와 승인된 구조화 노트 |핵심 워크 플로우
자동 수집 절차의 핵심은 소스는 자동 수집한 후 메타 데이터를 기록하고, 기록된 내용을 자동 정제 후
최종 반영은 사용자의 승인을 받으 후 최종 옵시디언에 정제된 데이터를 반영하는 것입니다.
## 5. 핵심 워크플로우
```
[소스] ──수집──▶ [00_inbox/raw/] ──자동 생성──▶ [정제 제안]
│
사용자 승인 ▼
[20_notes/] (구조화 노트)
```
1. 수집 — 소스에서 raw 노트를 vault에 저장하고 DB에 메타데이터 기록
2. 제안 생성 — 수집된 raw 노트를 바탕으로 요약·분류·구조화 제안 자동 생성
3. 승인 — 프런트엔드 또는 API 에서 제안을 검토하고 승인
4. 반영 — 승인된 patch가 `20_notes/`에 적용되고 감사 로그 기록자료 수집 방법
구축한 Agent의 자료 수집 방법은 다음과 같습니다. 이렇게 자료 수집을 할 수 있습니다.
## 6. 소스 수집 방법
### 방법 A: 프런트엔드 UI (http://127.0.0.1)
수집 제어 : 섹션에서 각 소스 타입별로 입력 후 버튼 클릭.
| 소스 | 입력값 | 설명 |
|------|--------|------|
| 로컬 폴더 | 절대 경로 (예: `/Users/me/notes`) | .md/.txt/.pdf 재귀 수집 |
| UpNote Export | UpNote 내보내기 폴더 절대 경로 | upnote_export 버킷으로 분류 |
| Notion 페이지 | Notion 페이지 ID | 단일 페이지 수집 |
| Notion 데이터소스 | 데이터소스 ID + 개수 제한 | 새/변경 페이지만 체크 가능 |
방법 B: curl API 직접 호출
**로컬 폴더 수집:**
```bash
curl -X POST http://127.0.0.1/api/ingestions/local-folder \
-H 'Content-Type: application/json' \
-d '{"root_path": "/Users/me/obsidian-notes"}'
```
**UpNote export 수집:**
```bash
curl -X POST http://127.0.0.1/api/ingestions/upnote-export \
-H 'Content-Type: application/json' \
-d '{"root_path": "/Users/me/UpNote Export"}'
```
**Notion 단일 페이지:**
```bash
curl -X POST http://127.0.0.1/api/ingestions/notion/page \
-H 'Content-Type: application/json' \
-d '{"page_id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}'
```
**Notion 데이터소스 (증분):**
```bash
# 새 페이지 + 마지막 동기화 이후 변경된 페이지만
curl -X POST http://127.0.0.1/api/ingestions/notion/data-source \
-H 'Content-Type: application/json' \
-d '{"data_source_id": "ds-xxxx", "limit": 50, "only_new": true}'
```
**Webhook (모바일 메모앱 등):**
```bash
curl -X POST http://127.0.0.1/api/ingestions/webhook \
-H 'Content-Type: application/json' \
-d '{"source_id": "unique-id", "title": "메모 제목", "body": "메모 본문"}'자동 폴링 설정
수동 수집 없이 백그라운드에서 자동으로 변경 사항을 가져오게 할 수 있습니다.
### UpNote 자동 폴링
`.env`에 다음을 설정:
```dotenv
UPNOTE_EXPORT_ROOT=/Users/me/UpNote Export
WATCH_SCAN_ON_STARTUP=true # 시작 시 즉시 스캔
WATCH_POLL_INTERVAL_SECONDS=300 # 5분마다 재스캔
```
- 파일 수정 시간(mtime) 기반으로 변경된 파일만 재수집
- 이미 처리된 파일은 skip → 불필요한 중복 제안 생성 없음
### Notion 자동 폴링
```dotenv
NOTION_API_TOKEN=secret_xxxx
NOTION_WATCH_DATA_SOURCE_ID=ds-xxxx # 감시할 데이터소스 ID
NOTION_WATCH_LIMIT=50
WATCH_POLL_INTERVAL_SECONDS=300
```
- `last_edited_time` 기반으로 마지막 동기화 이후 변경된 페이지만 재수집
- cursor가 DB에 저장되어 재시작 후에도 증분 동기화 유지
### 폴링 상태 확인
```bash
# 최근 수집 job 확인
curl http://127.0.0.1/api/ops/jobs이렇게 자료 수집이 잘 되면 아래의 승인 절차에 따라 승인을 합니다.
승인 워크 플로우
8-1. 프런트엔드에서 승인
1. http://127.0.0.1 접속
2. **승인 대기열** 섹션에서 대기 중인 제안 목록 확인
3. 각 제안의 요약을 검토한 뒤 **승인** 버튼 클릭
4. 승인 즉시 `vault/20_notes/` 에 구조화 노트 생성
### 8-2. API로 승인
```bash
# 1. 대기 중인 제안 목록 조회
curl http://127.0.0.1/api/proposals/
# 2. 제안 승인 (proposal_id와 base_hash 필요)
curl -X POST http://127.0.0.1/api/proposals/approve \
-H 'Content-Type: application/json' \
-d '{
"proposal_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"expected_base_hash": "현재_노트의_sha256_해시"
}'
```
8-3. 충돌 처리
승인 시 두 가지 409 오류가 발생할 수 있습니다:
| 오류 | 의미 | 해결 |
|------|------|------|
| `conflict_detected` | 대상 노트가 승인 시도 전에 다른 경로로 변경됨 | 목표 파일 확인 후 재시도 |
| `proposal_superseded` | 같은 소스가 재수집되어 이 제안이 구버전이 됨 | 새 제안 목록에서 최신 제안 승인 |검색 및 사용법
최종적으로 이렇게 수집된 자료들을 검색할 수 있게 시스템으로 구현이 됩니다.
### 프런트엔드
1. 화면 하단 **검색** 섹션에서 검색어 입력
2. **검색** 버튼 클릭
3. 관련도 순으로 결과 표시 (vault 상대 경로 기준)
### API
```bash
curl -X POST http://127.0.0.1:8000/api/search/ \
-H 'Content-Type: application/json' \
-d '{"query": "지식 관리 에이전트", "limit": 5}'
```
응답:
```json
[
{"document_id": "00_inbox/raw/local_folder/notes.md", "score": 4.0},
{"document_id": "20_notes/my-note.md", "score": 2.0}
]
```
### 검색 동작 방식
| 상황 | 동작 |
|------|------|
| Qdrant 실행 중 + 데이터 있음 | Qdrant sparse vector 검색 (영속, 빠름) |
| Qdrant 미실행 또는 빈 컬렉션 | 파일 기반 in-memory BM25 재구성 후 검색 |
수집할 때마다 자동으로 Qdrant 인덱스에 upsert되므로 수동 인덱싱 불필요.이렇게 나만의 PKM Agent를 구축했습니다.
해당 에이전트 내용은 git과 연동하여 올려놓기도 했습니다. private로요 ㅎㅎ
결과와 배운 점
이번에 Agent를 만들면서 배운 점은 나만의 Agent를 만들기 위한 "나만의 방향 및 기획이 매우 중요"하다는
것을 배웠습니다.
이제 AI 모델들의 성능이 많이 올라와서 다소의 차이만 있을 뿐 지시한 작업은 잘 하기 때문에
내가 원하는바를 명확히 설계하여 지시하는 것이 매우 중요하다고 생각합니다.
더불어, 만들고 테스트-검증-디버깅하는 작업 역시 중요하다고 생각합니다.
다음 번에는 이 에이전트를 구동해본 후기 및 보안 및 이슈 등을 수정한 이야기를 올려보겠습니다.
도움 받은 글 (옵션)
여행가J님과 타이칸님의 지도편달