# 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
```json
{
  "content": "텍스트/MDX 콘텐츠 (붙여넣기 또는 파일 업로드)",
  "base_path": "/path/to/images" (선택, 이미지 폴더 경로)
}
```

### 1단계 출력: step1_analysis.json
```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
```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
```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`: 블록 ID
- `template`: 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)
```javascript
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 연동

### 무한 재시도 메커니즘
```python
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 (렌더링 버그)

