Kei Design Agent
콘텐츠를 시각적으로 구조화된 슬라이드 HTML(1280×720px, 16:9)로 변환하는 AI 파이프라인.
개요
텍스트/MDX 콘텐츠를 입력하면:
- Kei 실장(Opus)이 정보 구조와 비중을 판단하고
- 코드가 컨테이너 크기를 계산하고
- 블록을 선택하고
- 콘텐츠-컨테이너 적합성을 검증하고
- AI(Sonnet)가 블록 디자인을 참고하여 HTML을 생성하고
- 코드가 슬라이드 프레임에 조립하고
- 측정+비전 모델로 검증합니다
파이프라인 (10단계)
※ 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 |
측정 결과 + 품질 점수 |
핵심 원칙
- 콘텐츠가 구조를 결정 — 블록 CSS는 참고만. AI가 콘텐츠 전달 의도를 보고 HTML 구조 결정 (Phase R')
- 하드코딩 금지 — font-size 외 모든 수치는 동적 계산. 어떤 MDX가 들어와도 동일하게 동작
- 스크롤 절대 금지 — overflow:auto/scroll 어떤 영역에서도 불허
- Kei API 필수 — fallback 없음. 성공할 때까지 무한 재시도
- AI가 옵션 생성, Kei가 결정 — 공간 부족 시 하드코딩 대응이 아니라 Kei 판단 요청
- 계산 먼저, AI 판단 나중에, 렌더링은 검증만
- 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 (결정론적) |
설치 및 실행
접속: 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와의 관계