Files
C.E.L_Slide_test2/ARCHITECTURE_OVERVIEW.md
kyeongmin b0bcffc0f6 Phase N+O: 컨테이너 기반 레이아웃 + Step B 제거 + 전면 정리
- 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>
2026-03-27 15:20:51 +09:00

606 lines
24 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 (렌더링 버그)