아무거나 던지면 LLM이 알아서 분류한다 — InboxPilot MVP 하루 만에 만들기

TL;DR

  • URL이든 텍스트든 입력창 하나에 던지면 Claude Sonnet이 7개 카테고리로 자동 분류·요약·태깅
  • 3개 AI 에이전트(Opus, Codex, Sonnet) 교차 리뷰로 race condition, dead code 등 실제 버그 발견
  • 프롬프트에 카테고리별 판단 기준을 명시하자 분류 정확도가 70% → 100%로 상승
  • 원본 HTML을 gzip 압축 보존 — 추출 로직을 바꿔도 재크롤링 없이 재처리 가능
  • videokeeper와 Celery send_task()로 크로스앱 메시징 — API 호출 없이, 큐 이름으로 분리

들어가며

흩어진 URL과 메모를 나중에 다시 찾을 수 없다. 북마크는 쌓이기만 하고, 메모앱은 분류가 안 되고, “그거 어디서 봤더라”를 반복한다.

InboxPilot은 이 문제를 해결한다: 아무거나 던져넣으면 LLM이 알아서 분류하고, 요약하고, 태그를 달아준다.

하루(정확히는 긴 하루) 동안 Claude Code와 함께 SP-08부터 SP-14까지 7개 스프린트를 완료했다. 이 글은 그 과정에서 부딪힌 문제들과 설계 판단을 다룬다.


핵심 기능

  • 입력 자동 감지: URL인지 텍스트인지 자동 판단. 입력창 하나에 아무거나 붙여넣으면 된다
  • LLM 분류: Claude Sonnet이 7개 카테고리(기술/영상/뉴스/레퍼런스/읽을거리/도구/기타) 중 하나를 선택
  • 자동 enrichment: 분류와 별도로 요약(200자)과 태그(최대 5개)를 추출. 실패해도 분류는 유지되는 부분 성공 모델
  • 원본 보존: URL의 원본 HTML을 gzip 압축 저장. 추출 로직을 바꿔도 재크롤링 없이 재처리 가능
  • SPA 렌더링: React/Vue 같은 SPA 사이트는 Playwright headless 브라우저로 렌더링 후 HTML 캡처
  • YouTube 위임: 영상 URL은 videokeeper 프로젝트에 Celery 메시지로 위임. 트랜스크립트가 돌아오면 다시 수신

기술 스택

Django 5.1 + DRF + Celery + Redis + PostgreSQL
HTMX + Tailwind CSS (CDN)
Anthropic Claude API (분류 + enrichment)
Playwright (SPA 렌더링)
Docker + GitHub Actions → K3s 배포

3에이전트 교차 검증

모든 스프린트는 PRD → TDD → 구현 순서로 진행하되, 각 단계마다 3개의 AI 에이전트(Opus, Codex, Sonnet)가 독립적으로 교차 리뷰했다. 같은 라운드에서 3개 모두 ALL CLEAN을 2회 연속 달성해야 다음 단계로 진행.

이 과정에서 발견된 실제 버그들:

버그발견 에이전트해결
fetched_at 필드가 태스크 인자에서 누락3개 모두인자에 추가
RawContent 중복 생성 → IntegrityErrorrace condition 지적IntegrityError catch 패턴
LLM이 JSON을 markdown 코드 블록으로 감쌈응답 파싱 검증strip 처리
render_spa_content의 retry가 dead coderender_page가 내부에서 모든 예외를 삼킴예외 전파 수정

한 에이전트가 놓친 것을 다른 에이전트가 잡는 경우가 많았다. 1인 개발에서 “리뷰어 부재” 문제를 보완하는 효과적인 방법이었다.


프롬프트 튜닝 — 카테고리 이름만으로는 부족하다

Golden test set 10건으로 분류 정확도를 측정했다.

  • 튜닝 전: 70% (7/10) — “기술”과 “레퍼런스” 혼동, “읽을거리”와 “영상” 혼동
  • 튜닝 후: 100% (10/10)

핵심은 카테고리 이름만 나열하는 게 아니라, 각 카테고리의 판단 기준을 명시적으로 설명하는 것이었다.

- 기술: 기술 학습, 튜토리얼, 기술 블로그 글 (새로운 지식을 전달하는 콘텐츠)
- 레퍼런스: 공식 문서, API 문서, 라이브러리 문서 (반복 참조용 참고 자료)

“기술”과 “레퍼런스”의 경계가 모호하다 — React 공식 문서는 레퍼런스지만, React 튜토리얼 블로그 글은 기술이다. 이 구분 기준을 프롬프트에 넣으니 LLM이 더 이상 혼동하지 않았다.


부분 성공 모델 — enrichment 실패가 분류를 막지 않는다

처음에는 분류와 enrichment(요약 + 태그)를 하나의 LLM 호출로 묶었다. 문제는 enrichment가 실패하면 분류까지 날아간다는 것이었다.

설계를 바꿨다. 분류는 먼저 확정하고, enrichment는 별도로 시도한다. enrichment가 실패해도 분류 결과는 유지된다. 아이템은 “분류됨, enrichment 대기” 상태로 남고, 나중에 재시도할 수 있다.

이 패턴은 시스템 안정성을 크게 높였다. LLM API가 일시적으로 느려지거나 응답 형식이 이상해도, 최소한 분류는 완료되어 사용자가 아이템을 찾을 수 있다.


인프라 공유 — dev-infra로 분리

PostgreSQL과 Redis를 프로젝트마다 각각 올리던 방식에서, dev-infra로 분리하여 모든 프로젝트가 공유하도록 변경했다. Redis는 DB 번호로 용도를 분리한다.

DB용도
0loopback
1공유 Celery broker (inboxpilot + videokeeper)
2inboxpilot cache
3videokeeper cache
4superset

Cross-app Celery 메시징

videokeeper와의 연동은 API 호출이 아닌 Celery send_task()를 사용했다. 같은 Redis broker를 공유하되, 큐 이름(inboxpilot/videokeeper)으로 분리하여 메시지가 섞이지 않게 했다.

# inboxpilot → videokeeper
current_app.send_task(
    "videokeeper.ingest_video",
    kwargs={"url": item.raw_input, "source": "inboxpilot", ...},
    queue="videokeeper",
)

# videokeeper → inboxpilot (처리 완료 후)
current_app.send_task(
    "inboxpilot.receive_handoff",
    kwargs={"source_item_id": str(item_id), "content_markdown": transcript, ...},
    queue="inboxpilot",
)

시간 결합이 없어서 videokeeper가 내려가 있어도 메시지는 큐에 남고, 올라오면 자동 처리된다. API 호출이었다면 상대 서비스가 살아 있는지 확인하고, 타임아웃을 처리하고, 재시도 로직을 짜야 했을 것이다. 같은 Redis broker를 공유하는 프로젝트 간 통신이라면 Celery 메시지가 훨씬 단순하고 안정적이다.


원본 보존 — Raw가 먼저다

모든 URL의 원본 HTML을 data 컬럼에 gzip 압축으로 저장한다. 추출 로직은 언제든 바꿀 수 있지만, 원본이 없으면 재크롤링해야 한다. link rot로 그마저 불가능할 수 있다.

실제로 프롬프트 튜닝 과정에서 이 설계가 빛났다. 분류 기준을 바꿀 때마다 원본 HTML에서 다시 추출하면 됐다. 사이트에 다시 요청을 보내지 않아도 되니 속도도 빠르고, rate limit 걱정도 없었다.


배운 것

  1. 부분 성공 모델: enrichment 실패가 분류를 막지 않는 설계가 시스템 안정성을 크게 높인다
  2. 교차 검증의 가치: 3개 에이전트가 서로 다른 각도에서 버그를 찾아냄. 한 에이전트가 놓친 것을 다른 에이전트가 잡는 경우가 많았다
  3. 프롬프트는 구분 기준이 핵심: 카테고리 목록만 주면 LLM이 자기 기준으로 판단. 명시적 기준을 주면 정확도가 급상승
  4. send_task > API 호출: 같은 Redis를 쓰는 프로젝트 간 통신은 Celery 메시지가 API보다 단순하고 안정적
  5. Raw 보존이 먼저: 추출 로직은 언제든 바꿀 수 있지만, 원본이 없으면 재크롤링해야 한다

다음 단계

  • RAG 검색: pgvector로 청킹/임베딩 → 하이브리드 검색 API → MCP 서버로 에이전트에게 노출
  • E2E 테스트: 전체 파이프라인 통합 검증
  • Hermes Agent 연동: Discord에서 URL/메모를 보내면 자동 수집

이 프로젝트의 코드는 GitHub에서 볼 수 있다.