Projects

LanGraphNovel: 에이전트 협업 기반 장편 소설 집필 자동화 대시보드

GitHub ↗

소설 기획, 세계관 구성, 캐릭터 설계, 집필 및 편집(교정/윤문)에 이르는 창작의 모든 과정을 에이전트 간의 유기적인 피드백 루프로 자동화하고, 사용자가 Streamlit 대시보드를 통해 이를 실시간으로 모니터링하고 개입할 수 있습니다.

README.md

📚 LanGraphNovel: 에이전트 협업 기반 장편 소설 집필 자동화 대시보드

LanGraphNovelLangGraphDeepSeek API를 기반으로 작동하는 멀티 에이전트 협업 기반의 소설 집필 시스템입니다. 소설 기획, 세계관 구성, 캐릭터 설계, 집필 및 편집(교정/윤문)에 이르는 창작의 모든 과정을 에이전트 간의 유기적인 피드백 루프로 자동화하고, 사용자가 Streamlit 대시보드를 통해 이를 실시간으로 모니터링하고 개입할 수 있습니다.


🛠️ 기술 스택 (Tech Stack)

  • Core Framework: LangGraph, LangChain
  • LLM Provider: DeepSeek API (deepseek-chat, deepseek-reasoner)
  • Dashboard / UI: Streamlit
  • Environment Management: Python (dotenv, virtualenv)

🔄 에이전트 협업 워크플로우 (Agent Workflow Flowchart)

본 프로젝트는 기획(Pre-Production) 단계와 집필(Production) 단계로 구분되며, 각 에이전트들이 생성한 결과물에 대해 상호 검토와 피드백 루프를 반복하여 고품질의 텍스트를 완성합니다.

Agent Workflow Flowchart


🖥️ 대시보드 주요 기능 및 화면 (Dashboard Features & Screens)

Streamlit 기반 대시보드는 총 4개의 핵심 탭으로 구성되어 있으며, 소설 기획 단계부터 완성 단계까지의 흐름을 한눈에 관리할 수 있습니다.

1. 프로젝트 기획 (Setup)

소설의 기본적인 아이디어(제목, 장르, 테마, 시드/로그라인)와 챕터 수, 목표 글자 수 등 분량을 구성하고 기획 생성을 시작합니다. 1. 프로젝트 기획 (Setup)


2. 시놉시스 & 설정 (Synopsis & Lore)

기획 에이전트군(Planner, World Builder, Character Designer)이 작성한 소설의 시놉시스, 상세한 배경 설정(Lore), 그리고 주요 등장인물의 상세 프로필을 확인하고 검토할 수 있습니다. 2. 시놉시스 & 설정 (Synopsis & Lore)


3. 에이전트 모니터 (Console)

소설 집필(Writer)과 편집/검토(Editor)의 루프를 실시간으로 모니터링합니다. 에디터 검토 요약과 초안 텍스트를 비교하고 반려 및 AI 재작성, 수동 검토 의견 반영 및 최종 승인 절차를 인터랙티브하게 진행할 수 있습니다. 3. 에이전트 모니터 (Console)


4. 완료된 소설 (Book)

최종 승인된 각 챕터들을 책 형태로 모아서 읽을 수 있습니다. 챕터 선택 이동 기능을 통해 완성된 소설의 전체 흐름을 편리하게 탐색할 수 있습니다. 4. 완료된 소설 (Book)


🚀 시작하기 (Getting Started)

1. 요구사항 및 설치 (Installation)

리포지토리를 클론한 후 필요한 패키지를 설치합니다.

git clone https://github.com/your-username/LanGraphNovel.git
cd LanGraphNovel
pip install -r requirements.txt

2. 환경 변수 설정 (Configuration)

루트 디렉토리에 .env 파일을 생성하고 DeepSeek API Key를 입력합니다.

DEEPSEEK_API_KEY=your_actual_api_key_here

3. 실행 (Run)

Streamlit 대시보드 서버를 기동합니다.

streamlit run src/app.py

브라우저에서 http://localhost:8501/ 로 자동 접속되어 대시보드가 열립니다. (대시보드 내의 설정 패널에서도 API Key 및 LLM 모델 옵션을 실시간으로 갱신할 수 있습니다.)


📂 프로젝트 구조 (Project Structure)

  • src/: 소스 코드 디렉토리
    • agents/: 각 노드별 에이전트 로직 (Planner, Writer, Editor 등)
    • app.py: Streamlit 대시보드 진입점 및 레이아웃 정의
    • graph.py: LangGraph 워크플로우 정의
    • state.py: 소설 상태 구조 정의
  • docs/: 프로젝트 설계 문서 및 스크린샷 자산
    • assets/: README 연동 이미지 자산
  • saves/: 저장된 프로젝트 세이브 파일 (.gitignore에 등록되어 제외됨)

LangGraph AI 에세이 평가 루프

GitHub ↗

LangChain과 LangGraph를 활용하여 사용자가 입력한 주제에 대한 AI 에세이 작성, 피드백, 그리고 순환 보완 교정을 수행하는 완전한 동작형 워크플로우 학습 예제입니다.

Neon Beats: Cyberpunk Interactive Drum Pad

GitHub ↗

인터랙티브 드럼 패드 학습용 프로젝트입니다. 사이버펑크 감성의 화려한 다크 네온 UI 테마 속에서 드럼을 연주할 수 있습니다.

Cozy Campfire Sprite Animation

GitHub ↗

마음을 차분하게 해주는 타닥타닥 모닥불 소리와 부드러운 픽셀 애니메이션을 감상할 수 있는 감성 웹 애플리케이션입니다.

README.md

🪵 Cozy Campfire (아늑한 모닥불)

마음을 차분하게 해주는 타닥타닥 모닥불 소리와 부드러운 픽셀 애니메이션을 감상할 수 있는 감성 웹 애플리케이션입니다.

Cozy Campfire Demo


✨ 주요 기능 (Key Features)

  1. 스프라이트 애니메이션:
    • 2행 5열의 모닥불 스프라이트 시트(10 프레임)에서 각 프레임을 렌더링합니다.
    • 프레임 전환 시 위아래로 흔들리거나(Jumping), 경계선이 뚫리거나(Boundary crossing), 화면이 깜빡이는 현상을 완전 교정했습니다.
  2. 제어 패널:
    • 불길의 타오르는 속도(1 FPS ~ 30 FPS) 조절 슬라이더.
    • 실시간 오디오 볼륨(0% ~ 100%) 제어 및 원터치 음소거 버튼.

🚀 시작하기 (How to Run)

본 프로젝트는 외부 라이브러리나 종속성이 없는 순수 정적 웹 애플리케이션입니다.

  1. 본 저장소를 복사(Clone)합니다.
    git clone https://github.com/사용자이름/AnimationSprite.git
    
  2. 폴더 내의 index.html 파일을 더블 클릭하여 웹 브라우저에서 실행합니다.
  3. 화면 우측 하단의 "소리 켜기" 버튼을 클릭하여 ASMR 사운드와 함께 모닥불을 감상하세요

pageindex-demo

GitHub ↗

PageIndex — vector DB 없이 문서의 챕터/섹션 구조를 트리 인덱스로 만들고 LLM 추론으로 트리를 탐색하는 retrieval 시스템

README.md

PageIndex × Project Gutenberg — 고전 문학 Q&A 데모

PageIndex(VectifyAI) — vector DB 없이 문서의 챕터/섹션 구조를 트리 인덱스로 만들고 LLM 추론으로 트리를 탐색하는 retrieval 시스템 — 의 강점과 한계를, 하나의 고전 소설 The Great Gatsby 위에서 vector RAG baseline과 나란히 비교해 검증하는 데모입니다.

전체 파이프라인은 로컬 Ollama 모델 (qwen2.5:7b 기본) 위에서 동작하며, 외부 API 키나 유료 서비스는 일절 필요하지 않습니다.


왜 PageIndex인가 — 책에 RAG를 쓰면 안 되는 이유

vector RAG는 짧고 독립적인 사실 조각비정형 청크의 바다에서 찾아내는 데 최적화돼 있습니다. 긴 문학 작품은 RAG의 모든 가정을 뒤집습니다.

요구 Vector RAG PageIndex
캐릭터의 시점 변화 추적 어휘 유사 청크만 회수, 시간 순서 상실 챕터 트리를 reasoning으로 탐색해 시간 순으로 선택
"마지막 챕터에선 무슨 일이?" "ending"을 포함한 청크 회수 — 마지막이라는 개념 없음 트리의 마지막 노드로 직접 이동
답변의 근거 인용 chunk #87 (사람에게 의미 없음) (Chapter 7) (책을 펴서 검증 가능)
특정 두 챕터 비교 다른 곳의 청크가 섞여 들어옴 정확히 그 두 노드만 선택

곧바로 실행해 볼 데모 쿼리 7개는 demo_queries.md에 정리돼 있습니다.


저장소 구조

ProjectGutenberg/
├── src/                      # 데모 코드
│   ├── download_book.py      # gutenberg.org에서 평문 다운로드
│   ├── preprocess.py         # 보일러플레이트 제거 + `# Chapter N` 마크다운화
│   ├── build_index.py        # PageIndex 트리 빌드 (Ollama 경유)
│   ├── pageindex_query.py    # Agentic 트리 walk + 답변 합성
│   ├── rag_baseline.py       # 같은 책에 대한 vector RAG (FAISS-style)
│   └── cli.py                # 통합 진입점
├── third_party/PageIndex/    # VectifyAI/PageIndex 외부 의존성(셋업 시 별도 clone, 미커밋)
├── data/                     # 셋업 후 자동 생성, 모두 .gitignore 처리
│   ├── raw/                  # Gutenberg 원본 텍스트
│   ├── markdown/             # 정제된 `# Chapter N` 마크다운
│   └── index/                # PageIndex 트리 JSON + RAG 임베딩 캐시
├── demo_queries.md           # PageIndex 강점이 드러나는 큐레이션 쿼리 7개
├── LICENSE                   # MIT
└── requirements.txt

셋업 (~10분)

  1. Ollama 설치 (Windows에서는 winget도 가능: winget install Ollama.Ollama)
  2. 모델 받기 — 기본은 qwen2.5:7b (8GB+ VRAM 환경에서 품질/속도 균형):
    ollama pull qwen2.5:7b
    
    가벼운 대안: ollama pull llama3.2:3b (더 빠르지만 요약 품질 떨어짐).
  3. PageIndex 라이브러리 clone — 본 저장소는 PageIndex 본체를 재배포하지 않습니다. 별도 clone이 필요합니다 (한 번만):
    git clone https://github.com/VectifyAI/PageIndex third_party/PageIndex
    
  4. Python 환경 (3.10+ 권장):
    python -m venv .venv
    .venv\Scripts\activate            # PowerShell / cmd
    # 또는: source .venv/bin/activate # bash
    pip install -r requirements.txt
    pip install -r third_party/PageIndex/requirements.txt
    
  5. Ollama 동작 확인:
    ollama run qwen2.5:7b "say hi"
    

End-to-end 실행

# (1) Gatsby 다운로드 + 정제 + PageIndex 트리 빌드 — 한 번만
python -m src.cli setup 64317

# PageIndex 단독 질문
python -m src.cli query "How does Nick's perception of Gatsby evolve?"

# RAG baseline 단독 질문
python -m src.cli rag   "How does Nick's perception of Gatsby evolve?"

# 두 시스템 나란히 비교 (가장 유용한 명령)
python -m src.cli compare "How does Nick's perception of Gatsby evolve?"

setup의 첫 실행은 7B 모델 기준 약 5–15분 소요됩니다 (대부분 PageIndex가 챕터별 요약을 Ollama로 생성하는 시간). 이후 쿼리는 30–90초 정도입니다.

모델을 바꾸려면:

python -m src.cli --model ollama_chat/llama3.2:3b compare "..."
# OpenAI 키가 있다면:
# python -m src.cli --model gpt-4o compare "..."

트리 직접 들여다보기

PageIndex의 핵심 가치는 산출물이 사람이 읽을 수 있는 형태라는 점입니다. setup 후 트리 자체를 살펴보세요:

python -c "import json; t=json.load(open('data/index/64317.json'));
print(json.dumps([{'title': n['title'], 'summary': n.get('summary','')[:80]}
                  for n in t['structure']], indent=2, ensure_ascii=False))"

9개 챕터 노드(+ Front Matter) 각각에 로컬 모델이 작성한 한 단락 summary가 붙어 있어야 정상이며, 이것이 LLM이 답변할 때 walk하는 구조입니다 — 불투명한 vector도, 청크도 없습니다.


단계별 수행 내용 — 무엇을 만들었는가

단계 내용 산출물
1. 요구 분석 & 계획 "PageIndex 장점, RAG 비교 포함, 3시간, API 키 0개" 요구를 분해. 핵심 결정 (Ollama / Gatsby / 비교 포함) 사용자 확인 계획 파일
2. 환경 셋업 winget install Ollama.Ollamaollama pull qwen2.5:7b (4.7GB) → Python 의존성 설치 → litellm↔Ollama 연결 검증 Ollama 0.21.2 + qwen2.5:7b
3. PageIndex clone git clone VectifyAI/PageIndex. 코드 분석 결과 pageindex.utils.llm_completionlitellm을 사용하므로 model="ollama_chat/qwen2.5:7b" 한 줄만 넘겨도 라우팅됨 third_party/PageIndex/
4. 데이터 다운로드 Project Gutenberg id=64317 (The Great Gatsby) — UTF-8 평문 296,900자 data/raw/64317.txt
5. 전처리 *** START / END *** 보일러플레이트 제거. TOC 항목과 챕터 헤딩이 같은 로마 숫자라 충돌 → 들여쓰기 유무로 구분(TOC는 flush-left, 챕터는 centered)하는 정규식 작성. # Chapter N (I) 형태의 9개 마크다운 챕터 + Front Matter 생성 data/markdown/64317.md (276,876자, 9챕터)
6. 트리 빌드 pageindex.page_index_md.md_to_tree를 직접 호출. Ollama가 9개 챕터 + Front Matter 각각에 한 단락 summary 생성 data/index/64317.json (10 노드)
7. PageIndex retrieval Agentic 트리 walk을 직접 구현 (PageIndex의 openai-agents SDK 데모는 OpenAI 강결합이라 Ollama와 호환 약함). 각 레벨에서 (title, summary, node_id) 목록을 LLM에 보여주고 "관련 노드 ID JSON 배열"을 응답받아 재귀. leaf의 text를 모아 synthesis. System prompt로 챕터 인용 강제 src/pageindex_query.py
8. RAG baseline 의도적으로 단순하게: 같은 마크다운에서 챕터 헤딩을 제거 → 500-word/50-overlap 청크 → sentence-transformers/all-MiniLM-L6-v2 임베딩 → 코사인 top-5 → 같은 Ollama 모델에 투입. 챕터 정보가 청크 단위에서 사라진다는 점이 핵심 src/rag_baseline.py
9. CLI 통합 setup / build / query / rag / compare 5개 서브커맨드 src/cli.py
10. 실행 검증 두 개의 비교 쿼리를 end-to-end로 실행. 인덱스/쿼리/RAG/compare 출력을 stdout으로 확인 (로그 파일은 python -m src.cli ... | tee data/index/x.log로 자유롭게 보존 가능) stdout

Windows에서 발견된 한 가지 이슈: cp949 콘솔 코덱이 PageIndex의 em-dash() 출력을 처리하지 못해 빌드 후 print_tree가 죽었습니다. 트리 JSON 자체는 정상 저장되며, src/cli.pysrc/build_index.py 상단에서 stdout/stderr를 UTF-8로 reconfigure하도록 패치했습니다.


평가 — qwen2.5:7b 기준 측정 결과

5축 비교

기준 PageIndex Vector RAG 비고
Retrieval 정확도 ★★★★☆ ★★★☆☆ PageIndex는 챕터 단위로 정확히 선택. Q1(Ch1↔Ch9 시점 변화)에서 Ch1, 5, 6, 9를 정확히 골랐고, Q2(첫↔마지막 챕터)에서는 Ch9만 골라 Ch1을 놓침 — 모델 reasoning 한계
Synthesis 정확도 ★★★☆☆ ★★★★☆ 7B 모델이 6KB 챕터 본문 4–6개에 휘둘려 답변이 산만해지거나 환각(없는 챕터 인용) 발생. RAG는 작은 청크에 집중. 모델을 키우면(8B+, GPT-4o) 즉시 역전
근거 (Traceability) ★★★★★ ★☆☆☆☆ PageIndex 답변은 항상 (Chapter 9)처럼 사람이 검증 가능한 인용. RAG는 chunk #29 (word offset 13050) 같은 불투명 번호. PageIndex의 핵심 우위가 가장 명확히 드러나는 축
쿼리 속도 ★★☆☆☆ (~30–60s) ★★★★☆ (~10–30s) PageIndex는 multi-step (트리 walk N회 + synthesis 1회). RAG는 1-shot
인덱싱 속도 ★★☆☆☆ (~수 분) ★★★★☆ (~30s) PageIndex는 노드별 LLM 요약 호출 10회. RAG는 임베딩만

토큰 사용량 (대략 추정)

단계 PageIndex Vector RAG
1회성 인덱스 빌드 (LLM) ~70K input + ~5K output (10챕터 × 7K 토큰 본문 → summary) 0 (LLM 호출 없음, 임베딩 모델만)
인덱스 빌드 (임베딩) 0 ~70K 토큰을 MiniLM에 통과
쿼리 1회 입력 ~30–50K 토큰 (트리 walk 프롬프트 + leaf 본문 최대 6×6KB) ~3–5K 토큰 (top-5 × 500 단어)
쿼리 1회 출력 ~500–1,000 토큰 ~200–500 토큰

PageIndex 쿼리는 RAG의 약 5–10배 토큰 소모. 로컬 Ollama라 비용은 0이지만, 클라우드 API라면 같은 비율로 비용이 발생합니다. 대신 인덱스를 한 번 만들고 여러 번 쿼리하는 시나리오라면 빌드 비용은 amortize 됩니다.

정성 평가

잘 동작한 것

  • 트리 구조 인식: 9개 챕터 + Front Matter가 한 번에 정확히 노드화. PageIndex가 마크다운 # 헤딩만 보고 트리를 만들기 때문에, preprocess 정규식이 정확하면 인덱싱은 deterministic.
  • 챕터-aware 노드 선택: Q1에서 Ch1/5/6/9를 고른 동작은 vector RAG로는 흉내낼 수 없음. "Chapter 1부터 9까지의 변화"라는 구조적 의도를 retrieval 단계에서 LLM이 이해하고 반영.
  • Traceability: 모든 답변이 챕터 번호로 인용 → 사용자가 즉시 책을 펴 검증 가능. RAG의 "chunk #29"는 사용자에게 의미 있는 정보를 제공하지 못함.

잘 동작하지 않은 것

  • Cross-chapter 추론 실패: Q2의 "ending mirrors the opening"에서 Ch1과 Ch9를 둘 다 골라야 했지만 Ch9만 선택. 시스템의 한계가 아니라 qwen2.5:7b의 reasoning 한계. 답변 본문에서는 존재하지 않는 데이터로 Ch2를 인용하는 환각도 발생.
  • 긴 컨텍스트 부담: 챕터 본문 6KB × 4–6개 = 약 30KB를 7B 모델에 한 번에 넣으면 답변이 산만해지는 경향. RAG 쪽은 짧은 청크라 모델이 집중하기 쉬움.

핵심 발견

*PageIndex의 retrieval 우위는 모델과 무관하게 일관되며, synthesis 품질은 LLM 능력에 비례한다.* 7B 모델로도 PageIndex의 핵심 가치(챕터 단위 traceability, 구조 인식)는 명확히 시연되며, 모델만 강화하면 답변 품질도 같이 올라가는 구조.


검증 체크리스트

  • data/index/64317.json에 9 챕터 노드(+ Front Matter)가 있고 각 노드에 summary 필드가 채워져 있다.
  • python -m src.cli compare "..." 출력에서 PageIndex 답변에 (Chapter X) 형태의 인용이 포함되고, RAG 답변에는 chunk #N (word offset M) 형태의 청크 번호만 있다.
  • 쿼리 실행 중 ollama psqwen2.5:7b가 활성 상태이며, 외부 네트워크 호출이 발생하지 않는다.

트러블슈팅

  • Connection refused: Ollama가 실행되지 않았습니다. ollama serve 또는 Ollama 앱을 실행하세요.
  • 트리 빌드가 너무 오래 걸림: 7B 기준 Gatsby는 약 5–15분이면 끝납니다. 빌드를 건너뛰고 파이프라인의 나머지를 먼저 보고 싶다면 --no-summary로 빌드 (트리 walk가 제목만 보고 노드를 선택하게 됨, 품질 저하).
  • VRAM 부족: --model ollama_chat/llama3.2:3b 또는 qwen2.5:3b 같은 작은 모델로 교체.
  • Windows 콘솔에서 답변 출력 깨짐: cp949 코덱이 em-dash나 한글을 처리 못 하는 경우입니다. CLI는 stdout을 UTF-8로 자동 reconfigure 하지만, 직접 모듈 실행 시 문제가 보이면 set PYTHONIOENCODING=utf-8 (Windows) / export PYTHONIOENCODING=utf-8 (bash) 후 재실행.

안내

이 프로젝트는 클로드 코드의 도움을 받아 작성되었습니다

NotionToBlog

GitHub ↗

Notion 일기 → Hugo 블로그 자동 발행 파이프라인

README.md

NotionToBlog

Notion 일기 DB의 오늘자 페이지를 한 명령으로 Hugo(GitHub Pages) 포스트로 발행하는 개인용 파이프라인. Self-dogfood 전용 — PyPI 배포 의도 없음, Windows + Python 3.14 단일 지원. (내부 패키지 식별자는 dayblog / dayblog_mcp.)

Highlights

  • Notion 일기 → Hugo + GH Pages 자동 발행 — 두 차례 후속 릴리스(v0.2.0 / v0.2.1)로 실사용 피드백을 반영한 실가동 도구
  • Claude Code의 Hook + Skill + MCP 3축을 의도적으로 한정 적용 (Subagent / Plugin / Scheduled task는 범위 외) — 깊이를 위해 너비를 포기한 설계
  • 124 tests · GitHub Actions CI · Notion API 2025-09-03 (data_sources.query) 마이그레이션 대응
  • 핵심 설계 결정과 트레이드오프는 docs/domain-notes.md에 한 페이지로 정리 — commit history 자체가 곧 개발 일지
┌─────────────────────────┐         ┌──────────────────────────┐
│  NotionToBlog (this)    │         │  Hugo site (external)    │
│  - src/dayblog/         │ writes  │  D:\vscodeprojects\blog  │
│  - src/dayblog_mcp/     │ ──────► │  - content/posts/<slug>/ │
│  - .claude/ hooks       │         │  - themes/PaperMod/      │
│                         │         │  - .git/hooks/pre-push   │
└─────────────────────────┘         └──────────────────────────┘
         ↑                                     ↑
  Claude Code harness                   hugo server + git push
  (Hook + Skill + MCP)                   (GH Pages 배포)

요구 사항

  • Python 3.14
  • Windows (서브프로세스 · 경로 규약이 Windows 전제)
  • Hugo Extended (PaperMod SCSS)
  • Notion Integration 토큰 + 대상 DB ID + 통합이 DB에 초대됨
  • 외부 Hugo 사이트 레포 (예: D:\vscodeprojects\blog) — PaperMod 테마가 sub-module로 등록돼 있으면 git submodule update --init --recursive 필수

설치

pip install -e .[mcp,dev]

[mcp] extra = fastmcp, notion-client, httpx (Notion 연동). [dev] = pytest, ruff. 최소 실행(네트워크 없는 Hugo 툴링만)은 core deps(pyyaml, python-dotenv)로 충분.

설정

레포 루트에 .env 작성:

NOTION_TOKEN=ntn_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
NOTION_DATABASE_ID=<32-hex, dashes optional>
HUGO_SITE_ROOT=D:\vscodeprojects\blog

Notion DB 스키마 요구사항 (docs/domain-notes.md §4):

속성 타입 필수
Title (또는 Name) title
Date date
Status select (Draft / Ready / Published)
Tags multi_select
Category select
Summary rich_text

/publish-todayStatus == Ready 필터만 잡습니다.

실행

CLI

python -X utf8 -m dayblog publish-today [--page-id <ID>] [--date YYYY-MM-DD]
python -X utf8 -m dayblog new-post --title "<제목>" [--date YYYY-MM-DD]
python -X utf8 -m dayblog list-drafts
python -X utf8 -m dayblog validate <path>
python -X utf8 -m dayblog install-pre-push    # Hugo 레포에 훅 설치

Claude Code 슬래시 커맨드

  • /today — 오늘자 Ready 페이지 목록 (진단)
  • /publish-today [page_id?] — Notion → Hugo 번들 발행
  • /post-new [date?] <title> — 수동 드래프트 스케폴드 (Notion 우회)
  • /draft-list / /publish-queuedraft: true 포스트 나열

MCP 툴 (.mcp.json이 자동 등록)

  • notion_list_pages(date?) — Ready 페이지 요약 목록
  • notion_get_page(page_id) — 원본 metadata + top-level blocks (진단용)
  • notion_render_markdown(page_id) — Markdown + 이미지 매니페스트 + 경고

글 작성·수정 플로우

새 글 발행

  1. Notion dayblog-journal DB에서 오늘자 페이지 작성 (Date / Title / 일기 본문)
  2. 페이지 맨 아래에 Heading 1 블로그 추가 → 그 아래 블로그용 내용 작성 (이 마커가 없으면 발행이 거부됨, domain-notes §9)
  3. StatusReady 로 변경
  4. python -X utf8 -m dayblog publish-today (또는 Claude /publish-today) — created 또는 updated 출력
  5. hugo server → 로컬 프리뷰 확인 (-D 불필요, baseURL이 서브패스면 http://localhost:1313/blog/)
  6. git add -A && git commit -m "post: <slug>" && git push — pre-push 훅이 손편집 draft 검사 후 GH Pages 자동 배포

Notion Status == Ready가 이미 발행 게이트 역할을 하므로 publish-today는 항상 draft: false로 직행 — 수동 플립 단계 없음.

발행된 글 수정

  1. Notion에서 본문 수정 → last_edited_time 자동 갱신
  2. python -X utf8 -m dayblog publish-today → idempotency가 updated로 감지, 번들 덮어씀
  3. git diff 확인 → commit + push

삭제

  • Notion StatusDraft: 이후 publish-today 대상에서 빠짐 (기존 번들은 손대지 않음)
  • 블로그에서도 지우려면 Hugo 레포의 해당 번들 디렉토리를 수동 삭제 후 commit + push

트러블슈팅: 빈 포스트가 나옴

  • 결과가 skipped-no-marker 면 Notion 페이지 본문에 top-level Heading 1 블로그 마커가 없음. toggle/callout 안의 H1은 인식 안 함 — 페이지 최상위 형제로 둬야 함.
  • Title/Date/Status property 이름이 정확히 매치되는지 (Status == Ready).

Idempotency

/publish-today를 같은 날짜로 여러 번 돌려도 안전합니다 (domain-notes §2 #1):

  • 기존 번들의 source_notion_id가 같고 lastmod ≥ Notion.last_edited_timeskipped
  • 더 오래됐으면 → updated (덮어쓰기)
  • 다른 페이지가 같은 날짜 slug를 점유 중이면 → -2/-3/… 로 자동 증분

Draft 보호 (Double guard)

NotionToBlog는 draft: true 포스트가 실수로 GH Pages에 올라가는 걸 막기 위해 두 훅을 동시에 설치합니다. (Notion publish-today는 이미 draft: false로 직행하므로 자동 발행 흐름에서는 차단되지 않습니다 — 훅은 /post-new로 만든 수동 드래프트 + 손편집으로 draft:true가 된 포스트 보호 용도.)

1. .git/hooks/pre-push (터미널 git push 커버)

Hugo 레포에서 한 번:

cd D:\vscodeprojects\blog
python -X utf8 -m dayblog install-pre-push

설치 후 git push 시 pushed 범위 안에 draft 포스트가 있으면 exit 1로 차단하고 파일 경로를 stderr에 나열합니다.

2. Claude Code PreToolUse (Claude가 내는 Bash 커버)

.claude/settings.json에 등록돼 있음. Claude가 git push Bash 툴을 호출할 때 JSON deny로 차단합니다. 중요: 이 훅은 HUGO_SITE_ROOT를 읽어서 그 레포를 스캔합니다 — .env에 제대로 세팅돼 있어야 작동.

왜 둘 다인가

Claude Code PreToolUse 훅은 구조적으로 "Claude가 Bash 툴로 실행하는" push만 가로챕니다. 사용자가 직접 터미널에서 git push하면 이 훅은 호출되지 않습니다. 그래서 git의 네이티브 pre-push도 필수. 두 훅이 동일한 Python 모듈(dayblog.hooks.pre_push_guard)을 호출하므로 로직은 DRY.

훅 우회 (의도적 push)

draft를 유지한 채로도 push해야 할 (매우 드문) 경우:

  • 터미널: git push --no-verify
  • Claude: 세션에서 훅 비활성화 (권장하지 않음)

로컬 렌더 확인

cd D:\vscodeprojects\blog
hugo server -D
# baseURL 서브패스가 있으면 http://localhost:1313/blog/

-D는 drafts 포함. 플립 전에 로컬에서 한 번 보고 draft: false로 바꿔 push.

범위 외

  • PyPI 배포 (self-dogfood 전제)
  • 과거 Notion 일기 일괄 마이그레이션
  • Notion DB 역방향 동기화 (블로그 → Notion)
  • Windows 외 OS, Python 3.14 외 버전
  • Subagent / Plugin / Scheduled task / Status line (일부러 깊이 제한)

테스트

pytest              # 111+ tests
pytest -k smoke
ruff check src tests

문서

안내

이 프로젝트는 클로드 코드의 도움을 받아 작성되었습니다.

라이선스

MIT

SCP World

GitHub ↗

SCP Foundation 위키 콘텐츠를 RAG로 검색하고, 재단 페르소나(연구원/요원/SCP-079)로 답변하는 챗봇 데모. 100% 서버리스 (Cloud Run + Firebase Hosting + Firestore) 로 운영됩니다.

README.md

SCP World

SCP Foundation 위키 콘텐츠를 RAG로 검색하고, 재단 페르소나(연구원/요원/SCP-079)로 답변하는 챗봇 데모. 100% 서버리스 (Cloud Run + Firebase Hosting + Firestore) 로 운영됩니다.


Overview: 프로젝트 개요

기획 배경 및 목표

세계관을 탐구할 때, 단순히 위키를 읽는 것이 아니라 하나의 페르소나를 가진 캐릭터와 대화하며 알아가면 재밌을 것 같다고 생각했습니다. 방대한 자료와 오픈된 라이선스(CC BY-SA 3.0)를 가진 SCP 위키를 기반으로, 3가지 캐릭터를 통해 SCP 세계관에 대해 탐구할 수 있는 챗봇을 만들었습니다.

핵심 기능

기능 설명
페르소나 선택 연구원(Dr. [REDACTED]), 요원(Agent [REDACTED]), SCP-079(Old AI) 중 선택
RAG 기반 대화 SCP 위키 문서를 벡터 검색하여 근거 있는 답변 생성
SSE 스트리밍 토큰 단위 실시간 스트리밍으로 자연스러운 대화 경험
페르소나별 격리 캐릭터마다 독립된 대화 세션 유지
Google 로그인 OAuth 2.0 기반 인증, ID Token으로 모든 API 보호
출처 표시 답변에 사용된 SCP 위키 원문 URL 제공

기술 스택

레이어 기술
Frontend Flutter Web, Riverpod, go_router, Google Sign-In v7
Backend API FastAPI, Uvicorn, Python 3.11
LLM Serving vLLM (Qwen2.5-7B-Instruct), NVIDIA L4 GPU
Embedding BAAI/bge-m3 (1024차원)
Vector DB Firestore Native Vector Search (find_nearest, COSINE)
Infra Cloud Run (Scale-to-Zero), Firebase Hosting, Firestore
Auth Google OAuth 2.0, ID Token 검증

시스템 상세 설명


System Architecture

시스템 구성도

시스템 구성도

데이터 흐름도

데이터 수집 파이프라인 (오프라인)

데이터 수집 파이프라인

실시간 질의 파이프라인 (온라인)

실시간 질의 파이프라인


라이선스

두 라이선스는 서로 다른 대상에 적용됩니다. 본 저장소의 코드를 재사용할 때는 MIT, RAG로 제공되는 SCP 위키 텍스트를 재배포할 때는 CC-BY-SA 3.0 조건을 따라야 합니다.

안내

클로드 코드를 활용하여 작성되었습니다

RAG Chat Project

GitHub ↗

Retrieval-Augmented Generation 기반의 AI 채팅 애플리케이션 — 사용자가 직접 AI 페르소나를 생성하고 문서를 업로드하여 질의응답(Q&A)을 할 수 있는 풀스택 프로젝트

README.md

RAG Chat

사용자가 AI 페르소나를 생성하고, 문서(PDF/텍스트/오디오)를 업로드한 뒤, 해당 문서를 기반으로 질의응답(Q&A)을 할 수 있는 RAG 채팅 애플리케이션입니다.


Overview: 프로젝트 개요

기획 배경 및 목표

사용자가 직접 업로드한 문서들을 바탕으로 특색있는 페르소나를 지닌 챗봇과 대화할 수 있는 RAG 파이프라인을 구축했습니다. 단일 봇이 아닌 페르소나 단위로 문서 컨텍스트를 격리하여, 용도별 전문 AI를 만들 수 있는 풀스택 프로젝트입니다.

핵심 기능

기능 설명
페르소나 생성 사용자가 직접 AI 페르소나를 생성하고 문서 컨텍스트를 격리
RAG 기반 대화 업로드된 문서를 벡터 검색하여 근거 있는 답변 생성
SSE 스트리밍 토큰 단위 실시간 스트리밍으로 자연스러운 대화 경험
다중 세션 관리 페르소나마다 독립된 대화 세션을 여러 개 유지
문서 업로드 PDF, TXT, MD, CSV, 오디오 파일 지원 (오디오는 STT 변환)
Google 로그인 OAuth 2.0 기반 인증, JWT로 모든 API 보호
품질 모니터링 RAG 검색 유사도를 자동 기록하고 리포트로 시각화

기술 스택

레이어 기술
Frontend Flutter Desktop (Windows), Provider, Dio, Google Sign-In
Backend API FastAPI, Uvicorn, SQLAlchemy, Pydantic, LangChain
LLM Ollama (qwen3:8b), NVIDIA GPU 가속
Embedding BAAI/bge-m3 (1024차원), sentence-transformers
Vector DB Qdrant (COSINE similarity)
Database PostgreSQL 15 (대화 기록), Redis 7 (캐시)
Auth Google OAuth 2.0, JWT (HS256)
Infra Docker Compose, NVIDIA Container Toolkit, Nginx (프로덕션)

시스템 상세 설명


System Architecture

시스템 구성도

시스템 구성도

데이터 흐름도

문서 업로드 파이프라인

문서 업로드 파이프라인

실시간 질의 파이프라인

실시간 질의 파이프라인


앱 화면

RAG Chat 대화 화면


안내

이 프로젝트는 학습 및 개인 프로젝트 목적으로 작성되었습니다. 클로드 코드를 활용하여 작성되었습니다.