- Phase N: catalog 개선, fallback 전면 제거, Kei API 무한 재시도, topic_id 버그 수정 - Phase O: 컨테이너 스펙 계산(비중→px), 블록 스펙 확정, 렌더러 container div - Step B(Sonnet) 제거: Kei(A-2)+코드로 대체. STEP_B_PROMPT/fallback/DOWNGRADE_MAP 삭제 - Selenium: container div 감지 추가 - catalog.yaml: ref_chars 구조 변환 + FAISS 재빌드 - 문서 전면 갱신: README, PROGRESS, IMPROVEMENT, Phase I~O md Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
24 KiB
24 KiB
Design Agent 전체 구조 파악 리포트
작성일: 2026-03-27
목표: design_agent의 아키텍처, 파이프라인, 코드 구조를 체계적으로 이해
📌 Design Agent란?
목적: 텍스트/MDX 콘텐츠를 시각적으로 구조화된 슬라이드 HTML(1280×720px, 16:9)로 변환하는 독립 AI 에이전트.
핵심 특징:
- ✅ 콘텐츠 기반 동적 비중 — Kei가 매번 콘텐츠마다 본심/배경/첨부/결론의 비중을 판단 (고정값 없음)
- ✅ 텍스트 우선 설계 — 디자인이 텍스트에 맞춤 (텍스트를 자르거나 비틀지 않음)
- ✅ 전문가 판단 — Kei 실장이 꼭지 추출, 디자인 팀장이 레이아웃, 편집자가 텍스트 정리, 실무자가 디자인 조정
- ✅ Kei API 필수 — 하드코딩 없음. 모든 판단은 AI의 사고. fallback 없음. 성공할 때까지 무한 재시도.
- ✅ 블록 라이브러리 — 38개 블록, 6개 카테고리 (headers, cards, tables, visuals, emphasis, media)
- ✅ 중간 산출물 추적 — 각 단계별 결과가 JSON으로 저장 (
data/runs/{timestamp}/)
🏗️ 파이프라인: 5단계 + 중간 단계
┌─────────────────────────────────────────────────────────────┐
│ [입력] 텍스트 콘텐츠 (텍스트 붙여넣기 또는 파일 업로드) │
└────────────────────┬────────────────────────────────────────┘
↓
┌────────────────────────────┐
│ [1단계] Kei 실장 │ (Kei API / Opus)
│ 꼭지 추출 + 정보구조 분석 │
│ ───────────────────────── │
│ 1A: 핵심 메시지, 꼭지 5개 │
│ 1B: 컨셉 구체화 │
│ 출력: step1_analysis.json │
│ step1b_concepts.json │
└────────┬───────────────────┘
↓
┌────────────────────────────┐
│ [O-1] 컨테이너 계산 │ (코드 / 결정론적)
│ 비중 → px 확정 │
│ ───────────────────────── │
│ 출력: step1c_containers... │
└────────┬───────────────────┘
↓
┌────────────────────────────┐
│ [2단계] 디자인 팀장 │
│ 레이아웃 설계 + 블록 배치 │
│ ───────────────────────── │
│ Step A: 프리셋 선택 (규칙) │
│ reference 있음 │
│ → sidebar-right │
│ Step B: 블록 매핑 (Sonnet) │
│ 각 블록에 텍스트 │
│ 글자수 가이드 │
│ 출력: step2_layout.json │
└────────┬───────────────────┘
↓
┌────────────────────────────┐
│ [O-3] 블록 스펙 확정 │ (코드 / 결정론적)
│ 항목수, 글자수, 폰트 │
│ ───────────────────────── │
│ 출력: step2c_block_specs.. │
└────────┬───────────────────┘
↓
┌────────────────────────────┐
│ [3단계] 텍스트 편집자 │ (Kei API / Opus)
│ 각 슬롯에 텍스트 정리 │
│ ───────────────────────── │
│ • 팀장의 글자 수 가이드 │
│ • 의미 우선 │
│ • 원본 보존 + 편집 │
│ 출력: step3_filled_blocks. │
└────────┬───────────────────┘
↓
┌────────────────────────────┐
│ [4단계] 디자인 실무자 │ (Sonnet + Jinja2)
│ CSS 조정 + HTML 조립 │
│ ───────────────────────── │
│ • 텍스트에 맞게 디자인 │
│ • 이미지/표 처리 │
│ • HTML 렌더링 │
│ 출력: step4_rendered.html │
└────────┬───────────────────┘
↓
┌────────────────────────────┐
│ [Phase L] 렌더링 측정 │ (Selenium)
│ 실제 높이 측정 │
│ ───────────────────────── │
│ • overflow 감지 │
│ • 피드백 루프 (미완성) │
│ 출력: step4_measurement_.. │
└────────┬───────────────────┘
↓
┌────────────────────────────┐
│ [5단계] Kei 최종 검수 │ (Opus 멀티모달)
│ 스크린샷 보고 검증 │
│ ───────────────────────── │
│ 출력: step5_screenshot.txt │
└────────┬───────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ [출력] 완성 슬라이드 HTML (final.html) │
│ + 시각화 리포트 (report.html) │
└─────────────────────────────────────────────────────────────┘
👥 5가지 역할 (Role)
1️⃣ Kei 실장 (Opus)
역할: 전략과 콘텐츠 분석
소유 단계: 1A, 1B, 3, 5
| 단계 | 하는 일 | 출력 |
|---|---|---|
| 1A | 꼭지 5개 추출, 핵심 메시지, 본심/배경/첨부/결론 비중 판단 | step1_analysis.json |
| 1B | 각 꼭지의 relation_type, expression_hint, source_data | step1b_concepts.json |
| 3 | 팀장의 글자 수 가이드를 참고하며 원본 보존하며 텍스트 정리 | step3_filled_blocks.json |
| 5 | 최종 슬라이드의 스크린샷로 보고 최종 검수 | step5_screenshot.txt |
원칙:
- 비중이 모든 것을 결정 (본심 60%, 배경 20%, 첨부 10%, 결론 10% 등)
- 콘텐츠마다 비중은 달라짐 (고정값 없음)
- 하드코딩 없음 - 매번 사고하여 판단
2️⃣ 디자인 팀장 (Sonnet)
역할: 레이아웃과 공간 배분
소유 단계: 2 (Step B), 2 (Step A는 코드)
| 단계 | 하는 일 | 출력 |
|---|---|---|
| 2A | 규칙 기반 프리셋 선택 (코드가 수행) | 프리셋 CSS grid |
| 2B | 프리셋 내에서 블록 매핑, 글자 수 가이드 (Sonnet) | step2_layout.json |
원칙:
- 블록 타입 변경 불가 (Kei가 선택한 것 유지)
- zone 배치만 담당 (body/sidebar/footer)
- 텍스트 내용 건드리지 않음
3️⃣ 텍스트 편집자 (Opus)
역할: 콘텐츠 정리 및 편집
소유 단계: 3 (별도 호출)
원칙:
- 팀장의 글자 수 가이드 참고 (하지만 의미가 우선)
- 원본 텍스트 최대 보존
- 개조식(불릿, 번호) 사용
- 슬롯 빈칸 금지
4️⃣ 디자인 실무자 (Sonnet + 코드)
역할: 디자인 조정 및 HTML 조립
소유 단계: 4 (O-3 스펙 확정 후)
원칙:
- 텍스트를 자르지 않음. 디자인으로 텍스트 맞추기
- 이미지는 원본 그대로, 크기만 조절
- 표는 표로 유지 (다른 형태 전환 X)
5️⃣ Code (결정론적 알고리즘)
역할: 계산 및 측정
소유 단계: O-1 컨테이너 계산, O-3 블록 스펙, Phase L 측정
| 단계 | 하는 일 |
|---|---|
| O-1 | Kei 비중 → px 확정. 고정식 계산. |
| O-3 | 컨테이너 크기 → 블록별 항목수/글자수/폰트 계산. 결정론적. |
| Phase L | Selenium으로 실제 렌더링 높이 측정. 브라우저 엔진 기반. |
📂 소스 코드 구조
src/
├── main.py ← FastAPI 서버, /api/generate 엔드포인트
├── config.py ← 설정 (API key, Kei API URL, 슬라이드 크기)
│
├── ─ 파이프라인 핵심 ─
├── pipeline.py ← 메인 파이프라인 (5단계 + 중간 단계)
│ • generate_slide() — 비동기 제너레이터, SSE 이벤트 방출
│ • _retry_kei() — Kei API 무한 재시도
│
├── ─ 단계별 모듈 ─
├── kei_client.py ← 1단계: Kei API 호출
│ • classify_content() — 꼭지 추출 + 분석 (1A)
│ • refine_concepts() — 컨셉 구체화 (1B)
│ • call_kei_overflow_judgment() — Phase L 피드백
│ • call_kei_final_review() — 최종 검수 (5)
│
├── space_allocator.py ← O-1, O-3: 컨테이너 & 블록 스펙 계산
│ • calculate_container_specs() — 비중 → px
│ • finalize_block_specs() — 항목수/글자수/폰트
│ • calculate_trim_chars() — overflow px → 삭제 글자
│
├── design_director.py ← 2단계: 레이아웃 설계
│ • select_preset() — 프리셋 규칙 선택 (Step A)
│ • create_layout_concept() — 블록 매핑 (Step B, Sonnet)
│ • LAYOUT_PRESETS — 4개 프리셋 (sidebar-right, two-column, hero-detail)
│ • BLOCK_SLOTS — 모든 블록의 슬롯 정의
│
├── content_editor.py ← 3단계: 텍스트 편집
│ • fill_content() — 각 슬롯에 텍스트 정리 (Kei API)
│
├── renderer.py ← 4단계: HTML 렌더링
│ • render_slide() — Jinja2로 HTML 생성
│ • _load_catalog_map() — catalog.yaml 블록 매핑
│
├── ─ 보조 모듈 ─
├── block_search.py ← FAISS 기반 블록 검색
│ • search_blocks_for_topics() — 콘텐츠 적합 블록 후보
│
├── slide_measurer.py ← Phase L: Selenium 렌더링 측정
│ • measure_rendered_heights() — 실제 높이 측정
│ • format_measurement_for_kei() — 측정값 → 피드백 포맷
│ • capture_slide_screenshot() — 멀티모달용 스크린샷
│
├── image_utils.py ← 이미지 처리
│ • get_image_sizes() — 이미지 크기 측정
│ • embed_images() — 이미지 데이터 URI 변환
│
├── svg_calculator.py ← SVG 다이어그램 계산
│
└── sse_utils.py ← SSE 스트리밍 유틸
• stream_sse_tokens() — 토큰 스트리밍 처리
📊 데이터 흐름
입력: SlideRequest
{
"content": "텍스트/MDX 콘텐츠 (붙여넣기 또는 파일 업로드)",
"base_path": "/path/to/images" (선택, 이미지 폴더 경로)
}
1단계 출력: step1_analysis.json
{
"title": "슬라이드 제목",
"core_message": "핵심 메시지 한 줄",
"total_pages": 1,
"info_structure": "정보 구조 설명",
"page_structure": {
"본심": {"topic_ids": [3], "weight": 0.60},
"배경": {"topic_ids": [1, 2], "weight": 0.20},
"첨부": {"topic_ids": [4], "weight": 0.10},
"결론": {"topic_ids": [5], "weight": 0.10}
},
"topics": [
{
"id": 1,
"title": "개념 혼용의 현실",
"purpose": "문제제기",
"role": "flow",
"emphasis": false,
"layer": "intro"
}
]
}
2단계 출력: step2_layout.json
{
"preset": "'header header' 'body sidebar' 'footer footer'",
"blocks": [
{
"area": "body",
"type": "callout-warning",
"topic_id": 1,
"purpose": "문제제기",
"reason": "..."
}
],
"overflow": [
{
"area": "body",
"overflow_px": 100,
"budget_px": 490
}
]
}
3단계 출력: step3_filled_blocks.json
{
"blocks": [
{
"type": "callout-warning",
"topic_id": 1,
"data": {
"icon": "⚠️",
"title": "DX와 BIM의 개념적 혼용 문제",
"description": "건설산업에서 DX와 BIM이 명확히 정립되지 않은 채 혼용..."
},
"char_count": 124
}
]
}
최종 출력: final.html
완성된 슬라이드 HTML (1280×720px, 16:9 비율)
🎨 프리셋 시스템 (2단계 Step A)
규칙 기반 자동 선택:
| 프리셋 | 조건 | CSS Grid | 사용 |
|---|---|---|---|
| sidebar-right | reference 꼭지 1개 이상 | "title title" "body sidebar" "footer" (65fr 35fr) |
용어 정의, 참조 정보가 있을 때 |
| two-column | 모든 flow 꼭지가 대등한 비교 | "title title" "left right" "footer" (1fr 1fr) |
좌우 비교 구조 |
| hero-detail | 고강조 꼭지 1개 + 나머지 보조 | "title" "hero" "detail" "footer" |
하나의 큰 내용 + 상세 |
| single-column | 모든 꼭지가 flow, 순차적 | "title" "body" "footer" (1fr) |
일반적인 순차 흐름 |
🏷️ 블록 라이브러리 (38개)
| 카테고리 | 개수 | 용도 | 예시 |
|---|---|---|---|
| headers | 5 | 타이틀, 꼭지 헤더 | section-title-with-bg, topic-left-right |
| cards | 10 | 항목 나열, 카드 그리드 | card-image-3col, card-compare-3col |
| tables | 3 | 비교표, 데이터 테이블 | data-table, comparison-table |
| visuals | 6 | SVG 다이어그램 | venn-diagram, flow-process, relationship |
| emphasis | 10 | 강조, 인용, 결론 | callout-warning, quote-block, banner-gradient |
| media | 5 | 이미지/사진 | image-frame, full-width-image |
각 블록은 catalog.yaml에 정의됨:
id: 블록 IDtemplate: HTML 템플릿 경로height_cost: 크기 등급 (compact/medium/large/xlarge)slots: 필수/선택 슬롯when: 사용 조건not_for: 금지 조건
🔧 기술 스택
Backend (Python)
FastAPI ≥0.115 — 웹 서버
uvicorn ≥0.30 — ASGI 서버
Jinja2 ≥3.1 — HTML 템플릿
Pydantic ≥2.0 — 데이터 검증
Anthropic ≥0.40 — Claude API
httpx ≥0.27 — HTTP 클라이언트
sse-starlette ≥2.0 — SSE 스트리밍
Selenium — Headless Chrome 제어 (Phase L)
Pillow ≥10.0 — 이미지 처리
PyYAML ≥6.0 — YAML 파싱 (catalog.yaml)
Frontend (React + Vite)
React 18 — UI 프레임워크
Vite — 번들러
Tailwind CSS — 스타일링
Design Assets
static/
├── base.css — 슬라이드 기본 스타일
├── tokens.css — 디자인 토큰 (색상, 폰트, 간격)
├── index.html — 프론트엔드 UI
templates/
├── catalog.yaml — 블록 라이브러리 정의
├── slide-base.html — 슬라이드 기본 템플릿
└── blocks/ — 6개 카테고리 × 38개 블록 HTML
├── headers/ — 5개
├── cards/ — 10개
├── tables/ — 3개
├── visuals/ — 6개
├── emphasis/ — 10개
└── media/ — 5개
🔄 Kei API 연동
무한 재시도 메커니즘
async def _retry_kei(fn, *args, **kwargs):
"""성공할 때까지 무한 재시도"""
while True:
result = await fn(*args, **kwargs)
if result is not None:
return result
await asyncio.sleep(10) # 10초 대기 후 재시도
특징:
- fallback 없음 (모든 판단을 Kei가 함)
- 타임아웃 없음 (10분이든 1시간이든 기다림)
- 성공 또는 명시적 실패만 가능
호출 파이프라인
design_agent (Sonnet)
↓ HTTP POST
persona_agent (Opus)
↓ (구조화된 프롬프트)
Kei 페르소나 (고급 OS 모델)
↓ (판단)
JSON 응답 (역직렬화)
↓
design_agent 계속 진행
📁 폴더 구조
design_agent/
├── CLAUDE.md ← 프로젝트 규칙 & 아키텍처 (이 파일)
├── README.md ← 사용자 가이드
├── PLAN.md ← 실행 계획 (태스크 목록)
├── PROGRESS.md ← 진행 상황 추적
├── pyproject.toml ← Python 의존성 정의
├── package.json ← Node.js 의존성 (프론트 관련)
│
├── src/ ← Python 소스 코드 (백엔드)
│ ├── main.py — FastAPI 서버
│ ├── pipeline.py — 파이프라인 메인 로직
│ ├── kei_client.py — Kei API 호출
│ ├── design_director.py — 2단계 레이아웃
│ ├── space_allocator.py — O-1, O-3 계산
│ ├── content_editor.py — 3단계 텍스트 정리
│ ├── renderer.py — 4단계 HTML 렌더링
│ ├── block_search.py — FAISS 블록 검색
│ ├── slide_measurer.py — Phase L 측정
│ ├── image_utils.py — 이미지 처리
│ ├── sse_utils.py — SSE 스트리밍
│ └── config.py — 설정
│
├── templates/ ← HTML 템플릿 & 블록 정의
│ ├── catalog.yaml — 38개 블록 라이브러리
│ ├── slide-base.html — 슬라이드 기본 구조
│ └── blocks/ — 블록별 HTML
│ ├── headers/ — 5개 헤더 블록
│ ├── cards/ — 10개 카드 블록
│ ├── tables/ — 3개 테이블 블록
│ ├── visuals/ — 6개 다이어그램 블록
│ ├── emphasis/ — 10개 강조 블록
│ └── media/ — 5개 미디어 블록
│
├── static/ ← CSS & 프론트엔드
│ ├── base.css — 슬라이드 스타일
│ ├── tokens.css — 디자인 토큰
│ └── index.html — 프론트엔드 UI
│
├── data/ ← 중간 산출물 & 인덱스
│ ├── runs/{timestamp}/ — 각 실행의 단계별 결과
│ │ ├── step1_analysis.json
│ │ ├── step1b_concepts.json
│ │ ├── step1c_containers.json
│ │ ├── step2_layout.json
│ │ ├── step2c_block_specs.json
│ │ ├── step3_filled_blocks.json
│ │ ├── step4_rendered.html
│ │ ├── step4_measurement_round*.json
│ │ ├── step5_screenshot.txt
│ │ └── final.html
│ ├── block_index.faiss — FAISS 인덱스
│ └── block_metadata.json — 메타데이터
│
├── docs/ ← 문서
│ ├── BLOCKS.md — 블록 라이브러리 설명
│ └── OUTPUTS.md — 산출물 구조
│
├── scripts/ ← 유틸리티 스크립트
│ ├── build_block_index.py — FAISS 인덱스 빌드
│ └── generate_run_report.py — 실행 리포트 생성
│
└── .env — 환경 변수 (API key, Kei API URL)
🚀 실행 흐름 (전체)
1. 사용자 입력
POST /api/generate
{
"content": "콘텐츠...",
"base_path": "/path/to/images"
}
2. SSE 스트리밍 시작
event: progress
data: "1/5 Kei 실장이 꼭지를 추출 중..."
3. 파이프라인 실행 (pipeline.py)
├─ [1단계] kei_client.py: classify_content()
│ → Kei API 호출, 꼭지 5개 추출
│
├─ [1B] kei_client.py: refine_concepts()
│ → Kei API, 컨셉 구체화
│
├─ [O-1] space_allocator.py: calculate_container_specs()
│ → 결정론적 계산, 비중 → px
│
├─ [2] design_director.py:
│ ├─ select_preset() — 규칙 기반 프리셋 선택
│ └─ create_layout_concept() — Sonnet: 블록 매핑
│
├─ [O-3] space_allocator.py: finalize_block_specs()
│ → 블록별 스펙 확정
│
├─ [3] content_editor.py: fill_content()
│ → Kei API: 텍스트 정리
│
├─ [4] renderer.py: render_slide()
│ → Jinja2: HTML 생성
│
├─ [Phase L] slide_measurer.py: measure_rendered_heights()
│ → Selenium: 실제 높이 측정
│
└─ [5] kei_client.py: call_kei_final_review()
→ Opus 멀티모달: 최종 검수
4. 최종 출력
event: result
data: {
"html": "<html>...</html>",
"runs_id": "1774588279782"
}
5. 프론트엔드
├─ iframe에 HTML 미리보기
├─ 다운로드 버튼 (final.html)
└─ 리포트 버튼 (report.html)
🔍 핵심 원칙
| 원칙 | 설명 | 구현 |
|---|---|---|
| 비중 우선 | 비중이 모든 것을 결정 | Kei가 page_structure 판단 → space_allocator 계산 |
| 텍스트 기준 | 디자인이 텍스트에 맞춤 | renderer.py에서 폰트/여백 조정 |
| Kei API 필수 | fallback 없음, 무한 재시도 | pipeline.py _retry_kei() |
| 결정론적 계산 | 규칙 기반 자동화 | space_allocator.py, design_director.py Step A |
| 역할 분리 | 각 역할이 자신의 영역만 담당 | CLAUDE.md Table "역할 분리" |
| 원본 보존 | 콘텐츠 의미 손상 X | content_editor.py 원칙 |
| 중간 산출물 | 모든 단계를 추적 가능하게 | _save_step() 함수 |
📊 Phase별 상태
| Phase | 내용 | 상태 |
|---|---|---|
| A~D | 슬라이드 품질 핵심 | ✅ 완료 |
| G | Kei API 통신 정상화 | ✅ 완료 |
| H | 스토리라인 설계 기반 전환 | ✅ 완료 |
| I | 정합성 복구 + 넘침 처리 | ✅ 완료 |
| J | 블록 선택 권한 구조 재정의 | ✅ 완료 |
| K | communicative role 기반 위계 | ✅ 완료 |
| K-1 | 파이프라인 스텝별 중간 산출물 저장 | ✅ 완료 |
| L | Selenium 렌더링 측정 + 피드백 루프 | ⚠️ 진행 중 |
| M | Kei 비중 시스템 강화 | ✅ 완료 |
| N | 4대 핵심 문제 해결 | ✅ 완료 |
| O | 컨테이너 기반 레이아웃 시스템 | 🔄 진행 중 |
🎯 결론
Design Agent는:
- ✅ 전문가 사고 기반 (Kei 실장, 디자인 팀장, 편집자, 실무자의 역할 분리)
- ✅ 결정론적 계산과 AI 판단의 조합
- ✅ 모든 단계를 추적 가능하게 설계
- ✅ 텍스트 우선, 디자인은 그에 맞춤
- ✅ 비중 기반 동적 레이아웃
- ✅ Kei API 필수, fallback 없음
현재 진행 상태:
- Core 완성: 1~4단계, O-1, O-3 명확히 작동
- 진행 중: Phase L (피드백 루프), Phase O (세부 개선)
- 테스트 필요: BF-4, BF-5, BF-6, BF-7 (렌더링 버그)