Stencil · 프로젝트 보고서

An agentic generative system
for design-grammar slide synthesis

agentic generative system AI for semantics · code for geometry vision-in-the-loop evaluator–optimizer gate no frame copy · 원본 미복사

프레젠테이션 템플릿을 고정된 편집 틀이 아니라 학습 가능한 디자인 사례로 본다. 입력 슬라이드를 객체 단위로 분석해 간격·정렬·계층·컴포넌트 구조·객체 간 관계를 명시적 규칙으로 바꾸고, 사용자 프롬프트의 의도를 해석한 뒤 그 규칙을 제약으로 원본에 없던 배치를 생성한다. LLM은 의미 해석과 계획까지만 맡고, 좌표·텍스트 측정·충돌 회피·여백 보정 같은 픽셀 작업은 결정론 코드에 위임한다. 이 분리가 겹침·캔버스 이탈·결과 비일관성을 줄이는 핵심 설계다.

How it works · 파이프라인

BAKE · 테마당 1회 1 · Parse 흡수slides → 측정 슬롯 2 · Grammar 문법팔레트·타입·그리드·리듬 3 · Skeletons 골격아키타입 zone (집계) GrammarSpecblocks·cardSpec·skeletons STAMP · 요청마다 4 · Plan 계획prompt → 아키타입+콘텐츠Claude 5 · Synthesize 합성골격+문법 → 새 좌표grammar only 6 · Solve 배치fit·push-down·safe areadeterministic 7 · Evaluate 평가7 scores · revise/rejectgate revise (<7) / reject (novelty<6) → re-synthesize · 재합성

사용자 프롬프트(와 선택한 이미지)는 STAMP 단계에서만 들어간다. BAKE에서 미리 만들어 둔 GrammarSpec만 읽어서 합성하고, 평가를 통과 못 하면 다시 만든다. 원본 SVG는 합성할 때 한 번도 다시 읽지 않는다.

AI engineering · 역할을 어디서 가를 것인가

역할을 가른 기준은 분명하다. LLM은 '무엇이 무엇인지', '무엇이 보기 좋은지'는 잘 맞히지만 좌표 같은 숫자는 자주 틀린다. 그래서 의미 판단은 모델이, 픽셀은 코드가 맡는다. 좌표까지 모델에 맡긴 구성에서는 겹침과 화면 이탈, 재현 불가가 그대로 드러났다.

Claude — 의미 (probabilistic)
  • 슬롯 분류 — 번호 오버레이를 비전으로 읽어 role·mediaKind 판정
  • 아키타입 선택 — 프롬프트를 슬라이드 목적으로 분해
  • 콘텐츠 작성 — 레이아웃이 요구하는 정확한 블록만 생성
  • 품질 비평 — 렌더된 픽셀을 다시 비전으로 평가
맥락에 따라 달라지고 "보기 좋다"가 정답인 판단이라, 규칙으로 박아두면 금방 깨진다. 그래서 모델에 맡겼다.
Deterministic code — 기하 (exact)
  • 좌표 생성 — 그리드 스냅·간격 리듬·계층에서 산출
  • 텍스트 측정 — opentype.js 정확 메트릭, measure=render parity
  • 충돌 회피 — push-down·safe area·비겹침 보장
  • 대비 보정 — 가독성 자동 교정, 디자인 floor 강제
같은 입력이면 같은 결과가 나와야 하고 픽셀 단위로 맞아야 하는 일이라, 모델이 손대면 안 된다. 전부 코드가 계산한다.

Agentic patterns · 에이전틱 패턴

A

Structured tool-use as a contract

AI 호출은 자유 텍스트를 파싱하지 않는다. tool 스키마로 타입이 강제된 객체(ContentPlan·PlacementPlan·CritiquePatch)만 받는다. 그런데도 모델이 가끔 배열을 빼먹거나 형태가 어긋나길래, 경계에서 한 번 더 방어하도록 막아뒀다.

B

Vision-in-the-loop

슬롯에 번호를 그려서 비전으로 분류하고, 합성한 결과도 다시 비전으로 본다. 텍스트 설명이 아니라 실제로 렌더된 픽셀을 보기 때문에 "말로는 멀쩡한데 눈으로 보면 깨진" 경우를 잡는다.

C

Evaluator–optimizer loop

만들고 → 7개 지표로 채점하고 → 기준에 못 미치면 스스로 고친다. 만드는 쪽과 평가하는 쪽을 분리해 뒀다.score < 7 → revise novelty < 6 → reject & re-synthesize N ≤ 2 (bounded)

D

Bake-once, stamp-many

흡수·문법 추출·비전 분류처럼 비싼 작업은 테마당 한 번만 돌려 GrammarSpec에 캐시한다. 생성할 때는 원본 SVG를 다시 읽지 않고 캐시만 조합해서, 싸고 빠르고 결과가 항상 같다.

E

Structure-first context

에이전트가 더 잘 고르게 하려고 이미 뽑아둔 구조를 그대로 보여준다. 역할 의미(kpi는 헤드라인 지표)나 아키타입이 가진 미디어(이 골격엔 제품 목업이 있음) 같은 것들. 의미 설명을 따로 비전으로 굽는 대신 결정론 구조를 먼저 쓰니 돈도 안 들고 환각도 없다.

Core logic · 핵심 로직

01

GrammarSpec

팔레트·타입스케일·정렬 그리드·간격 리듬·계층·blocks·측정 cardSpec을 하나로 구조화한 명시적 디자인 문법.

02

Archetype skeletons

아키타입별 예시 슬라이드의 region을 캔버스 분수로 정규화·median 집계한 패턴. 특정 프레임 복사가 아님.

03

Grammar-only synthesis

골격을 GrammarSpec만으로 인스턴스화. 좌표는 그리드·리듬·계층에서 생성, 카드 내부는 측정값 재사용.

04

Quality evaluator

7지표(문법 일관성·신규성·위계·간격·적합도·유사도 페널티·종합) 채점 + 게이트.any < 7 → revise layout novelty < 6 → reject

05

Decoration = theme habit

장식 양을 테마의 측정 coverage에서 결정(강제 아님). 빈 코너·여유 반경·팔레트색으로 배치 근거를 기록.

06

Design floor

테마 무관 기본 규약: 안전 여백(≥5%)·최소 폰트(~18px)·초점 크기(~54px)·비겹침(push-down/safe area).

Design systems · 템플릿 → 정립된 시스템

테마마다 슬라이드들을 하나의 재사용 디자인 시스템으로 정리한다. 복사한 슬라이드가 아니라 거기서 뽑아낸 규칙이다. 인스펙터는 팔레트, 타입 스케일, 그리드와 리듬, 계층, blocks, 아키타입 골격, 장식, 목업을 한 페이지에 모아 보여준다.

PALETTE TYPE SCALE title Bricolage · 120 · 200 headline 80 · 200 body Inter · 28 · 400 caption Inter · 28 · 600 ARCHETYPE SKELETONS · 집계 패턴 (복사 아님) eyebrow title cover header title cards ×3 content title mockup split + device mockup 11 device mockups · 26 decoration assets · 30 type roles · 3 themes — all extracted, not authored

인스펙터(scripts/design-system.mjs)는 GrammarSpec를 디자이너 문서처럼 그리고, 각 골격을 실제 예시 슬라이드와 나란히 놓는다. 추상 패턴과 실물을 같이 봐야 이해가 된다.

Colorful — distilled design system · 실제 추출 결과

위 개념을 colorful 테마에 실제로 적용한 결과다. 타입 스케일, 그리드·margin, spacing 리듬, 아키타입 골격(실제 예시 슬라이드와 mined 패턴을 나란히), 디바이스 목업까지 한 장으로 정리했다. 모든 값은 템플릿에서 추출했다.

colorful design system board
colorful GrammarSpec 보드 — scripts/ds-board.mjs colorful로 생성. 장식 언어·디바이스 목업까지 본 전체 인스펙터 → design-system.html

Results · 합성 결과 (no frame copied · grammar-synthesized)

같은 제품(Pulse, AI 고객지원 코파일럿)으로 세 테마에서 6장을 만들었다. 텍스트 슬라이드부터 데이터 시각화, 디바이스 목업, 타이틀까지 포함된다. 모두 원본 프레임을 복사하지 않고 GrammarSpec에서 좌표를 새로 생성한 것이며, 장식은 테마가 원래 쓰던 정도만 따른다. colorful은 과감하게, black은 거의 쓰지 않고, green은 차분하게 쓰는 것을 확인할 수 있다.

colorful timeline
colorful · timeline 날짜 + 연결선 + 테마 장식 어휘(유기적 블롭)
colorful metric
colorful · metric 메트릭 콜아웃 + 2×2 버블 사분면
green bar chart
green · chart 막대 차트(데이터 비례 높이) + 세리프
green title
green · title 다크그린 위 라임 대형 타이틀
black stat
black · stat 미니멀 KPI 3열(62% / 4x / +18)
black laptop mockup
black · mockup MacBook 프레임 + 빈 화면(사용자가 채움)

Device mockups · 목업 자산 (place the frame, user fills the screen)

목업(아이폰·맥북)은 틀과 빈 화면을 한 세트로 묶어 재사용 자산으로 떼어낸다. 합성할 땐 프레임을 그대로 찍고, 화면은 둥근 모서리와 노치까지 맞춘 빈 자리로 남긴다. 이미지는 우리가 만들지 않는다. 사용자가 직접 넣는다. 한 슬라이드에 여러 대가 들어갈 땐 그 슬라이드가 원래 쓰던 배열(예: 폰 3대 나란히)을 그대로 가져온다. black·colorful 두 테마에서 목업 11개를 뽑았다.

colorful phone mockup
colorful · phone 텍스트 좌측 + 아이폰 우측, 테마 장식(주황 블롭) 배경
phone empty screen
black · phone 빈 화면(노치 모양 그대로) — 사용자가 채움
laptop mockup
black · laptop MacBook 프레임 + 빈 화면(사용자가 채움)

Hard problems · 풀어낸 난제

"글자만 바꾸기" 함정
문제슬롯만 채우거나 배치를 통째로 LLM에 맡기면 결국 원본 슬라이드를 그대로 베끼게 된다.
난점원본의 분위기·간격·계층은 살리면서 레이아웃 구조는 새로 짜야 한다.
해법아키타입별 예시의 영역을 정규화해 중앙값으로 합친 골격을 만들고, 거기에 새 콘텐츠를 끼웠다.
결과원본을 복사하지 않고, 신규성 6점 미만이면 reject하는 게이트로 베끼기를 막았다.
관계 그래프 — 요소 사이의 의도를 잃지 않기
문제좌표만 뽑으면 "제목 위에 본문", "지표와 캡션은 한 쌍", "이미지를 피해 배치" 같은 요소 간 의도가 사라진다.
난점같은 좌표라도 관계가 다르면 다른 레이아웃이다. 관계를 잃으면 재조합할 때 어긋난다.
해법슬라이드에서 요소 간 관계 — 겹침(over), 정렬축(aligned), 한 쌍 묶음(coupled), 좌·우 위치(anchored), 회피(avoids) — 를 그래프로 추출해 문법에 저장한다. 합성은 좌표보다 이 관계를 먼저 만족시킨다. anchored 좌·우는 좌우 2단으로, coupled는 카드 내부 묶음으로, avoids는 장식을 콘텐츠 빈 영역에 두는 식으로 반영한다.
결과제목 좌·본문 우 2단, 지표+캡션 묶음, 콘텐츠를 피한 장식이 원본 의도대로 재현된다.
LLM은 픽셀 정밀도가 없다
문제좌표를 모델에 맡기면 글자가 겹치고 화면을 벗어나고, 같은 입력에도 결과가 달라진다.
난점구성은 창의적이길 바라지만 결과물이 깨지면 안 된다.
해법의미만 LLM이 정하고 좌표는 grammar와 solver가 전담했다. 골격이 창의성을 제약 안에 가둔다.
결과겹침도 화면 이탈도 없고, 같은 입력이면 같은 결과가 나온다.
텍스트 넘침 — 측정과 렌더가 어긋난다
문제글자 폭을 어림짐작하면 실제 렌더와 어긋나서 줄바꿈과 넘침을 예측할 수 없다.
난점생성된 텍스트는 길이를 미리 통제할 수 없다.
해법opentype.js로 폭을 정확히 재고 resvg와 폰트를 맞춰 측정과 렌더를 일치시켰다. 폭과 높이를 같이 줄인다.
결과줄바꿈과 자동 축소가 정확해져서 넘침이 사라졌다.
목업 안에 이미지를 어떻게 넣나
문제새로 합성한 슬라이드에서도 디바이스 목업을 재사용하고, 화면에만 이미지가 들어가야 한다.
난점화면이 사각형이 아니라 둥근 모서리에 노치까지 있는 path다. 프레임은 통째로 두고 화면만 바꿔야 한다.
해법목업을 틀과 화면 path가 들어간 독립 자산으로 떼어내, 원하는 위치·크기로 찍고 사용자 이미지를 화면 path 모양으로 clip한다. 여러 대를 놓을 땐 그 슬라이드의 배열을 그대로 쓴다.
결과노치까지 정확히 clip되고 폰 3대 배열도 재현된다. 이미지는 사용자가 채운다.
공간 관계를 버리고 있었다
문제처음엔 모든 슬라이드를 가운데 한 컬럼에 세로로만 쌓아서 단조로웠다.
난점골격에는 영역별 수평 위치(왼쪽/오른쪽)가 들어 있는데, 합성이 그 x값을 버리고 있었다.
해법골격의 x값을 그대로 살려서, 제목이 왼쪽·본문이 오른쪽처럼 갈라져 있으면 좌우 2단으로 합성한다.
결과테마가 실제로 쓰던 2단 구성이 살아나면서 단조로움이 사라졌다.
모델 출력이 가끔 어긋난다
문제모델이 이따금 스키마를 벗어난 형태(배열이 아닌 값 등)를 내보내 파이프라인이 깨졌다.
난점확률적인 출력 위에 결정론적 파이프라인을 올려야 한다.
해법tool-use로 형태를 강제하고, 경계에서 한 번 더 형태를 검사하고 정규화했다.
결과모델이 흔들려도 파이프라인은 안 깨진다.
"왜 이 에셋을 썼나"에 답할 수 없었다
문제장식과 배치를 인덱스 순서로 돌려쓰다 보니 왜 여기에 이걸 놨는지 설명할 수가 없었다.
난점결과를 설명할 수 있어야 믿고 쓸 수 있다.
해법결정마다 근거를 남겼다. 예를 들어 "가장 비어 있는 코너에, 안 겹치는 최대 반경으로, 팔레트 색으로". 장식 양도 테마가 원래 쓰던 정도에서 가져온다.
결과이제 모든 배치·장식 선택을 한 줄로 설명할 수 있다.

Progress · 진행 현황

Built · 구축

Explicit GrammarSpec
토큰·리듬·그리드·계층·blocks·cardSpec
Mined archetype skeletons
예시 집계, 복사 아님
Grammar-only synthesis
새 레이아웃, 원본 미복사
7-score evaluator + gate
revise / reject 루프
User image placement
배치, 생성 아님
Web app (synthesis/filler)
모드 토글 + 점수

Quality floor · 기본 규약

Decoration = theme habit
colorful 대담 / black 무장식
Safe padding + min font
가장자리 붙음·작은 글자 해결
Lead-size focal line
모든 슬라이드 초점 보장
Weak archetypes → cards
stat→KPI, comparison→티어
Explainable asset choice
배치 근거 기록
Cohesive centering
phantom 갭·tiny 폰트 제거

Open · 남은 작업

Web image upload UI
업로더 → 합성에 에셋 전달
Contrast on color panels
색 위 텍스트 흰색 전환
Decoration vs sparse content
콘텐츠 적을 때 크기 캡
ratio/desc image matching
이미지↔zone 적합 매칭
Relation graph direct use
anchored/aligned/avoids
Constraint solver v2 (kiwi)
자유 배치

Quality journey · 품질 개선 이력

Edge hugging → safe padding

colorful 그리드 margin 31px로 텍스트가 가장자리에 붙음 → 안전 여백 = max(grid, 5%) 강제.

Tiny text → font floor

측정 카드 폰트가 12px까지 축소 → 최소 폰트(~18px)·초점(~54px) 규약.

Empty comparison → tier cards

가격 티어가 한 문장으로 뭉침 → 카드 구조 보장(Starter/Growth/Scale).

Clustered band → cohesive center

마지막 zone 뒤 phantom 갭이 그룹을 미정렬 → 실제 잉크 범위로 중앙 정렬.

Forced blob → theme habit

모든 슬라이드 같은 블롭 → 테마 측정 coverage 기반(black은 무장식 유지).

Arbitrary asset → explainable

인덱스 회전 → "가장 빈 코너 + 여유 반경 + 팔레트색" 근거 기록.

id 없는 화면 슬롯 → 디바이스 그룹 검출

colorful 목업 화면은 id 없는 path(<g id="Image"> 안)라 추출 실패(0개) → 화면을 디바이스 그룹(iPhone/MacBook) 조상으로 검출하도록 변경 → 0→5개.

renderer undefined font → grammar fallback

family 없는 역할에서 렌더가 undefined로 크래시 → GrammarSpec가 테마 기본 fontFamily를 노출해 근본 해결.

What's next · 차기 로드맵

방향은 두 축이다. 남은 기술적 제약을 풀어 배치 표현력을 넓히는 것, 그리고 추출–합성 파이프라인을 실제로 쓸 수 있는 제품으로 만드는 것. 둘 다 "의미는 LLM, 픽셀은 코드"라는 같은 구조 위에서 진행한다.

결론

이 프로젝트는 LLM 생성에서 반복되는 겹침·캔버스 이탈·결과 비일관성을, 의미와 픽셀의 책임을 가르는 구조로 해결했다. 템플릿에서 추출한 디자인 문법을 제약으로 삼아, 단순 치환이 아니라 일관성과 신규성을 함께 갖춘 새 레이아웃을 결정론적으로 합성한다. 같은 구조 위에 업로드–에셋화–생성–export를 얹으면, 연구 프로토타입을 넘어 사용자가 자기 템플릿으로 발표 자료를 만드는 제품으로 이어진다.

원칙: 원본 프레임은 복사하지 않는다 · LLM은 콘텐츠를 쓰고 아키타입을 고를 뿐 좌표는 만들지 않는다 · 좌표는 전부 문법에서 나온다 · 랜덤 없이 결정론으로 돌아간다
Stencil · 디자인 문법 기반 슬라이드 합성 — 개인 프로젝트