# Kei Design Agent 콘텐츠를 시각적으로 구조화된 슬라이드 HTML(1280×720px, 16:9)로 변환하는 AI 파이프라인. ## 개요 텍스트/MDX 콘텐츠를 입력하면: 1. Kei 실장(Opus)이 정보 구조와 비중을 판단하고 2. 코드가 컨테이너 크기를 계산하고 3. 블록을 선택하고 4. 콘텐츠-컨테이너 적합성을 검증하고 5. AI(Sonnet)가 블록 디자인을 참고하여 HTML을 생성하고 6. 코드가 슬라이드 프레임에 조립하고 7. 측정+비전 모델로 검증합니다 --- ## 파이프라인 (10단계) ``` MDX 원본 ↓ [Stage 0] MDX 정규화 (코드) ↓ [Stage 1A] 꼭지 추출 + 영역 배정 (Kei API / Opus) ↓ [Stage 1B] 컨셉 구체화 (Kei API / Opus) ↓ [Stage 1.5a] 컨테이너 초기 계산 (코드) ↓ [Stage 1.7] 블록 선택 (코드) ↓ [Stage 1.8] 적합성 검증 + 재배분 + 보강 (코드 + Kei 에스컬레이션) ↓ [Stage 1.5b] 디자인 예산 재계산 (코드) ↓ [Stage 2] HTML 생성 (영역별 개별 호출) (Claude Sonnet) ↓ [Stage 3] 렌더링 조립 + 후처리 (코드) ↓ [Stage 4] 측정 + 품질 검증 (Selenium + Opus Vision) ↓ 검증 통과 시 → final.html 저장 + 팝업 분리 (파일 출력) ``` ※ Stage 4 이후의 파일 저장은 별도 Stage가 아닌 후처리입니다. --- ## 단계별 상세 ### Stage 0: MDX 정규화 | 항목 | 내용 | |------|------| | **목적** | 원본 MDX에서 JSX/frontmatter를 제거하고, 섹션/팝업/이미지/테이블로 분리 | | **적용기술** | 코드 (`normalize_mdx_content()`) | | **인풋** | 원본 MDX 문자열 | | **아웃풋** | `normalized` — clean_text, title, sections[], popups[], images[], tables[] | | **연계** | → Stage 1A가 clean_text를 Kei에게 전달 | ### Stage 1A: 꼭지 추출 + 영역 배정 | 항목 | 내용 | |------|------| | **목적** | 콘텐츠에서 핵심 파트(꼭지)를 식별하고, 슬라이드의 어떤 영역(배경/본심/첨부/결론)에 배치할지 결정 | | **적용기술** | Kei API (`classify_content()`) | | **인풋** | normalized.clean_text | | **아웃풋** | `topics[]` (id, title, purpose, layer, relation_type, expression_hint), `page_structure` (role별 topic_ids, weight) | | **연계** | → Stage 1B가 각 꼭지를 구체화 | ### Stage 1B: 컨셉 구체화 | 항목 | 내용 | |------|------| | **목적** | 각 꼭지에 실제 원본 텍스트(source_data)와 요약(summary)을 매핑 | | **적용기술** | Kei API (`refine_concepts()`) | | **인풋** | topics + clean_text | | **아웃풋** | `topics` 업데이트 — source_data, summary 추가 | | **연계** | → Stage 1.5a가 텍스트 양을 기반으로 컨테이너 비율 계산 | ### Stage 1.5a: 컨테이너 초기 계산 | 항목 | 내용 | |------|------| | **목적** | 폰트 위계 확정 + 슬라이드 내 영역별 컨테이너 크기(px) 계산 + 프리셋 선택 | | **적용기술** | 코드 (`calculate_font_hierarchy()`, `calculate_dynamic_ratio()`, `calculate_container_specs()`) | | **인풋** | topics, page_structure (weight), preset | | **아웃풋** | `font_hierarchy` (key_msg/core/bg/sidebar px), `container_ratio` (71:29 등), `containers` (role별 width_px, height_px), `preset` | | **연계** | → Stage 1.7이 컨테이너 크기를 보고 블록 선택 | ### Stage 1.7: 블록 선택 | 항목 | 내용 | |------|------| | **목적** | 각 꼭지의 relation_type + expression_hint + 컨테이너 크기로 적합한 블록 결정. 같은 영역 꼭지들의 layer가 다르면 주종관계 판단 (블록 1개로 합침) | | **적용기술** | 코드 (`select_and_generate_references()`) — catalog.yaml 기반 결정론적 매칭 | | **인풋** | topics, containers, page_structure | | **아웃풋** | `references` — role별 block_id, variant, design_reference_html, topic_id, is_hierarchical, supporting_topic_ids | | **연계** | → Stage 1.8이 선택된 블록+콘텐츠가 컨테이너에 맞는지 검증 | ### Stage 1.8: 적합성 검증 + 재배분 + 보강 + 서브 컨테이너 | 항목 | 내용 | |------|------| | **목적** | 콘텐츠가 컨테이너에 들어가는지 검증 → 안 맞으면 재배분 → 여전히 안 되면 Kei 에스컬레이션 → 여유 공간에 보충 콘텐츠 → 서브 컨테이너 배치 계산 | | **적용기술** | 코드 (`calculate_fit()`, `redistribute()`, `analyze_enhancements()`, `apply_enhancements()`, `calculate_sub_layout()`) + Kei API (에스컬레이션 시 `call_kei_fit_escalation()`) | | **인풋** | topics, containers, references, font_hierarchy, normalized, core_message | | **아웃풋** | `containers` (재배분된 height_px), `fit_result` (role별 fit_status, redistribution), `enhancement_result` (V-7 subordinate_treatments, V-8 supplement_blocks, V-9 emphasis_blocks, V-10 bold_keywords, V-4 kei_decisions), `sub_layouts` (role별 서브 컨테이너 name/width/height, table_rows) | | **내부 흐름** | Step 1: 필요 높이 계산 → Step 2: 재배분 → Step 3: Kei 에스컬레이션 → Step 4-5: 보강 분석+적용 → Step 6: fit 재검증 → Step 7: 서브 컨테이너 배치 → Step 8: 확정 | | **연계** | → Stage 1.5b가 재배분된 크기로 디자인 예산 재계산, → Stage 2가 sub_layouts + enhancements를 프롬프트에 반영 | ### Stage 1.5b: 디자인 예산 재계산 | 항목 | 내용 | |------|------| | **목적** | 재배분된 컨테이너 크기 + 선택된 블록 schema 기준으로 영역별 가용 공간 계산 | | **적용기술** | 코드 (`calculate_design_budget()`) | | **인풋** | containers (재배분 후), references (블록 schema) | | **아웃풋** | `containers` 업데이트 — design_budget (available_height_px, available_width_px, fits) | | **연계** | → Stage 2가 design_budgets를 프롬프트에 포함 | ### Stage 2: HTML 생성 (영역별 개별 호출) | 항목 | 내용 | |------|------| | **목적** | page_structure에 존재하는 각 역할(배경/본심/첨부/결론)의 HTML을 **영역별 개별 Sonnet 호출**로 생성. 블록 디자인을 참고하되 콘텐츠가 구조를 결정 (Phase R' 방식) | | **적용기술** | Claude Sonnet API — 영역당 1회 호출 (`build_area_prompt()` → `_call_claude()`) | | **인풋** | raw_content, topics, containers, font_hierarchy, references (design_reference_html), sub_layouts (서브 컨테이너 치수), enhancements (V-4~V-10 지시), design_budgets | | **호출 흐름** | Sonnet(배경) → bg_html, Sonnet(본심) → core_html, Sonnet(첨부) → sidebar_html, Sonnet(결론) → footer_html. 해당 역할에 꼭지가 없으면 스킵. body_html = bg_html + spacer + core_html | | **아웃풋** | `generated_html` — body_html, sidebar_html, footer_html | | **프롬프트에 포함되는 것** | 서브 컨테이너 레이아웃 제약, 디자인 레퍼런스 HTML (블록 CSS 참고), Kei 에스컬레이션 결정, 종속 꼭지 처리 지시, 보충 블록 지시, 강조 문장, bold 키워드, 폰트/컨테이너 크기 제약 | | **연계** | → Stage 3이 영역별 HTML을 슬라이드 프레임에 배치 | ### Stage 3: 렌더링 조립 + 후처리 | 항목 | 내용 | |------|------| | **목적** | 생성된 HTML 조각을 CSS Grid 슬라이드 프레임에 삽입 + 후처리 (폰트 캡핑, overflow 제거, sidebar width 조정, bold 변환) | | **적용기술** | 코드 (`render_slide_from_html()`) | | **인풋** | generated_html, preset (grid_areas, grid_columns), font_hierarchy, container_ratio | | **아웃풋** | `rendered_html` → `final.html` 파일 저장 | | **연계** | → Stage 4가 렌더링 결과를 측정+검증 | ### Stage 4: 품질 검증 | 항목 | 내용 | |------|------| | **목적** | Selenium으로 실제 브라우저 렌더링 후 overflow 측정 + Opus Vision으로 시각적 품질 평가 | | **적용기술** | Selenium (`measure_rendered_heights()`) + Claude Opus Vision (`vision_quality_gate()`) | | **인풋** | rendered_html | | **아웃풋** | `measurement` (zone별 clientHeight, scrollHeight, overflow, excess_px), `quality_score` | | **연계** | 파이프라인 완료. overflow 시 경고 포함하여 진행 | --- ## 중간 산출물 파이프라인 실행마다 `data/runs/{timestamp}/`에 단계별 결과가 저장된다. ### JSON Context (Stage별 누적 상태) | 파일 | Stage | 내용 | |------|-------|------| | `stage_0_context.json` | 0 | normalized (섹션, 팝업, 이미지) | | `stage_1a_context.json` | 1A | topics, page_structure | | `stage_1b_context.json` | 1B | topics (source_data 추가) | | `stage_1_5a_context.json` | 1.5a | font_hierarchy, containers, ratio | | `stage_1_7_context.json` | 1.7 | references (블록 선택 결과) | | `stage_1_8_context.json` | 1.8 | fit_result, enhancements, sub_layouts | | `stage_1_5b_context.json` | 1.5b | containers (design_budget 추가) | | `stage_2_context.json` | 2 | generated_html | | `stage_3_context.json` | 3 | (rendered_html은 final.html로 별도 저장) | | `stage_4_context.json` | 4 | measurement, quality_score | | `final_context.json` | 최종 | 전체 context | ### HTML 시각화 (`steps/` 폴더) | 파일 | Stage | 내용 | |------|-------|------| | `stage_0.html` | 0 | 섹션/팝업/이미지 목록 | | `stage_1a.html` | 1A | 꼭지 테이블 (purpose, layer, 영역) | | `stage_1b.html` | 1B | 꼭지 + source_data + summary | | `stage_1_5a.html` | 1.5a | 빈 컨테이너 (1280×720) | | `stage_1_5a_content.html` | 1.5a | 컨테이너에 콘텐츠 배치 | | `stage_1_5b.html` | 1.5b | 디자인 예산 (available height/width) | | `stage_1_7.html` | 1.7 | 블록 선택 표시 | | `stage_1_8_fit_before.html` | 1.8 | 적합성 (재배분 전) | | `stage_1_8_fit_after.html` | 1.8 | 재배분 후 + 보강 | | `stage_1_8_blocks.html` | 1.8 | SLOT 구조 + 블록 디자인 + 주종관계 (1280×720) | | `stage_2.html` | 2 | 영역별 Sonnet 출력을 실제 렌더링 (역할별 개별 확인) | | `stage_3.html` | 3 | 영역을 합쳐 슬라이드 프레임에 배치한 결과 (1280×720 실제 렌더링) | | `stage_4.html` | 4 | 측정 결과 + 품질 점수 | --- ## 핵심 원칙 1. **콘텐츠가 구조를 결정** — 블록 CSS는 참고만. AI가 콘텐츠 전달 의도를 보고 HTML 구조 결정 (Phase R') 2. **하드코딩 금지** — font-size 외 모든 수치는 동적 계산. 어떤 MDX가 들어와도 동일하게 동작 3. **스크롤 절대 금지** — overflow:auto/scroll 어떤 영역에서도 불허 4. **Kei API 필수** — fallback 없음. 성공할 때까지 무한 재시도 5. **AI가 옵션 생성, Kei가 결정** — 공간 부족 시 하드코딩 대응이 아니라 Kei 판단 요청 6. **계산 먼저, AI 판단 나중에, 렌더링은 검증만** 7. **overflow 상태에서 출력 금지** — Vision 모델 품질 게이트 통과 필수 --- ## 블록 라이브러리 (38개) 6개 카테고리, 38개 블록. 각 블록은 `catalog.yaml`에 용도(when), 금지(not_for), purpose_fit, schema(슬롯 정의)가 있음. | 카테고리 | 개수 | 용도 | |---------|------|------| | **headers** | 5 | 타이틀, 꼭지 헤더 | | **cards** | 9 | 항목 나열, 카드 그리드 | | **tables** | 3 | 비교표, 데이터 테이블 | | **visuals** | 6 | SVG 다이어그램, 관계도 | | **emphasis** | 10 | 강조, 인용, 결론, 불릿 | | **media** | 5 | 이미지/사진 | --- ## 기술 스택 | 역할 | 도구 | |------|------| | 서버 | FastAPI + uvicorn (포트 8001) | | AI (Kei 실장/편집자) | Kei API → Opus (localhost:8000) | | AI (HTML 생성) | Anthropic API → Claude Sonnet | | AI (품질 검증) | Anthropic API → Claude Opus Vision | | 블록 검색 | FAISS + bge-m3 | | 템플릿 | Jinja2 (블록 디자인 레퍼런스용) | | 렌더링 | CSS Grid + 디자인 토큰 (1280×720) | | 렌더링 측정 | Selenium headless Chrome | | SVG 시각화 | svg_calculator.py (N개 동적 배치) | | 이미지 | Pillow (크기 측정) + base64 인라인 | | 폰트 | Pretendard Variable | | 공간 계산 | space_allocator.py + fit_verifier.py (결정론적) | --- ## 설치 및 실행 ```bash # 설치 cd design_agent pip install -e . # FAISS 인덱스 빌드 (블록 추가/수정 시) python scripts/build_block_index.py # .env 설정 ANTHROPIC_API_KEY=sk-ant-... KEI_API_URL=http://localhost:8000 LOG_LEVEL=DEBUG ``` ```bash # 터미널 1: Kei API (필수) cd D:\ad-hoc\kei\persona_agent python -m uvicorn backend.main:app --host 127.0.0.1 --port 8000 # 터미널 2: Design Agent cd D:\ad-hoc\kei\design_agent python -m uvicorn src.main:app --host 127.0.0.1 --port 8001 --reload ``` 접속: http://localhost:8001 --- ## 개선 이력 | Phase | 내용 | 상태 | |-------|------|------| | A~D | 슬라이드 품질 핵심 | 완료 | | G~N | Kei API, 스토리라인, 정합성, 블록 선택, 비중, 측정 | 완료 | | O | 컨테이너 기반 레이아웃 | 완료 | | P | 다후보 렌더링 비교 | 완료 (20/100점 → 방향 전환) | | Q | 제약 기반 블록 선택 | 완료 | | R | 하이브리드 블록 (실패 — P=Q=R 동일 구조) | 실패 | | R' | 블록 CSS 참고 + AI 구조 결정 | 설계 확정 | | S | 검증 합격 프롬프트 + Claude HTML 생성 | 설계 확정 | | T | 11-Stage 파이프라인 + 디자인 레퍼런스 | 완료 (31/31 통과) | | V | 적합성 검증 + Kei 에스컬레이션 + 서브 컨테이너 | 완료 | | W | Stage 2 출력 품질 수정 (6건) | 진행 중 | --- ## Kei Persona와의 관계 ``` Kei Persona Agent (localhost:8000) ├── Opus + RAG + 세션 컨텍스트 ├── 도메인 지식 (건설/DX/BIM) └── 대화/생성/피드백/실행 모드 Design Agent (localhost:8001, 이 프로젝트) ├── 슬라이드 생성 전용 ├── Kei API로 꼭지 추출(1A) + 컨셉 구체화(1B) + 에스컬레이션(1.8) 호출 ├── Sonnet으로 HTML 생성(Stage 2) ├── Opus Vision으로 품질 검증(Stage 4) └── 두 프로젝트는 독립. 코드 공유 없음. API 연동만. ```