Phase I: 전수 정합성 복구 + 넘침 처리 패러다임 전환 (14개 항목) - I-14: SSE 유틸 공통 추출 (src/sse_utils.py 신규, 3개 파일 중복 제거) - I-13: dead code 3건 삭제 (_call_anthropic_direct, _extract_sse_text x2) + import anthropic 제거 - I-1: STEP_B_PROMPT purpose 가이드 미존재 블록 3개 → 실존 블록 교체 - I-2: catalog.yaml not_for 13건 미존재 블록 참조 교체/제거 - I-12: BLOCK_SLOTS 주석 개수 수정 (cards 9, visuals 6, emphasis 10) - I-10: INDEX.md 38개 동기화 (삭제된 8개 블록 행 제거) - I-11: README.md 38개 동기화 (_legacy 제거, 트리/개수 정리) - I-3: PURPOSE_FALLBACK 상수 + purpose 기반 미등록 블록 교체 - I-7: compare-pill-pair 단독 사용 금지 검증 - I-4: 38개 블록 전체에 slot_desc 추가 - I-5: 편집자 프롬프트에 slot_desc 전달 로직 - I-6: 제목 유사도 70% 초과 시 자동 교정 - I-9: 넘침 판단 Kei API 호출 (KEI_OVERFLOW_PROMPT, call_kei_overflow_judgment) - I-8: 대형 콘텐츠 정보 Kei overflow 프롬프트에 포함 프로세스 재설계: - Stage 2.5 제거 → Stage 5에서 Sonnet 감지 + Kei 판단 통합 - _review_balance() 확장: zone 예산 + overflow_detected action 추가 - Stage 5 루프에 Kei 넘침 판단 호출 통합 - _apply_adjustments()에 kei_trim/kei_restructure action 추가 - _build_overflow_context(), _convert_kei_judgment() 헬퍼 함수 추가 - DOWNGRADE_MAP은 Kei API 실패 시 비상용으로만 잔존 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
18 KiB
파이프라인 프로세스 재검토 — 검증 시점 문제 진단
Phase I 실행 완료 후 실제 구동 중 발견된 프로세스 구조 문제. Phase I의 코드 변경(14개 항목)은 유효하나, 검증이 배치된 시점이 부적절.
현재 프로세스 흐름 (as-is)
[1단계] Kei 실장 — 콘텐츠 분석 + 스토리라인 설계
├ 1-A: 꼭지 추출 (Kei API)
├ 1-B: 컨셉 구체화 (Kei API)
├ 제목 중복 검증 (코드)
└ 이미지 크기 측정 (Pillow)
↓
[2단계] 디자인 팀장 — 레이아웃 + 블록 매핑
├ Step A: 프리셋 선택 (규칙 기반)
├ Step A-2: Opus 블록 추천 (Kei API)
├ Step B: Sonnet 블록 매핑
└ 블록 검증 (코드): 미등록 교체, zone 교정, pill-pair, 높이 예산 체크
↓
[2.5단계] ⚠️ Kei 넘침 판단 — 예상 높이 기반
↓
[3단계] Kei 편집자 — 텍스트 채움 (Kei API)
↓
[4단계] 디자인 실무자 — CSS 조정 + HTML 렌더링 (Sonnet + Jinja2)
↓
[5단계] 디자인 팀장 — 재검토 + 조정 루프 (Sonnet, 최대 2회)
↓
미리보기 + HTML 다운로드
각 시점에서 알 수 있는 정보
| 시점 | 원본 텍스트 | 꼭지 분석 | 블록 배치 | 실제 텍스트 | 렌더링 HTML | 실제 높이 |
|---|---|---|---|---|---|---|
| 1단계 후 | O | O | - | - | - | - |
| 2단계 후 | O | O | O | - | - | 예상만 |
| 2.5단계 | O | O | O | 없음 | 없음 | 예상만 |
| 3단계 후 | O | O | O | O | - | - |
| 4단계 후 | O | O | O | O | O | 측정 가능 |
| 5단계 | O | O | O | O | O | 측정 가능 |
문제 진단 (6건)
문제 1: 내용 없이 넘침 판단
위치: Stage 2.5 현상: Kei에게 "이 zone이 넘친다"고 전달하지만, 실제 텍스트가 없는 상태. 블록 타입의 예상 높이(medium=150px, large=250px)만으로 판단 요청. 문제: Kei가 "trim할까 restructure할까"를 결정하려면 실제 콘텐츠를 봐야 하는데 볼 수 없음. 판단 근거가 부족한 상태에서 판단을 요청.
문제 2: 예상 높이 초과 → 판단 주체 잘못됨
위치: Stage 2.5 현상: Sonnet에게 이미 "zone 예산 490px, height_cost 확인해서 초과하지 마라"고 프롬프트로 지시함. 그런데도 예상 높이가 초과하면 그건 Sonnet이 지시를 안 따른 것. 문제: Sonnet의 지시 불이행을 Kei에게 물어볼 문제가 아님. Sonnet을 다시 호출하거나 프롬프트를 개선할 문제. 판단 주체와 해결 주체가 불일치.
문제 3: 실제 HTML이 있는데 넘침을 안 봄
위치: Stage 5 현상: 렌더링된 HTML이 있고, 각 블록의 실제 텍스트 양도 알 수 있는 시점. 그러나 현재 Stage 5의 점검 항목은 "빈 블록, 채움 불균형, 정보량, HTML 구조"만. 문제: 정작 "컨테이너에 실제로 넘치는가"는 점검 항목에 없음. 넘침을 확인할 수 있는 최적의 시점에서 확인하지 않음.
문제 4: 넘침 판단에 Kei가 없음
위치: Stage 5 현상: Stage 5 재검토는 Sonnet이 단독으로 수행. 조정도 expand/shrink/rewrite를 Sonnet이 결정. 문제: 넘침 발생 시 "뭘 줄이고 뭘 팝업으로 분리할지"는 콘텐츠 중요도 판단 — Kei가 해야 할 일. 현재 Stage 5에 Kei 참여 경로가 없음.
문제 5: 실제 렌더링 높이 측정 수단 없음
위치: 전체 파이프라인 현상: 파이프라인 어디에서도 렌더링된 HTML의 실제 px 높이를 측정하지 않음.
- Stage 2: 블록 타입 기반 예상 높이 (HEIGHT_COST_PX: compact=70, medium=150, large=250, xlarge=400)
- Stage 5: Sonnet이 HTML 코드를 읽고 눈대중으로 판단 문제: 예상 높이와 실제 높이는 다를 수 있음. 텍스트 양, CSS 조정, 폰트 크기에 따라 실제 높이가 달라지는데 이를 측정하는 코드가 없음.
문제 6: 넘침이 재검토 루프에 포함 안 됨
위치: Stage 5 루프
현상: Stage 5는 재검토 → 조정 → fill_content(Stage 3) → render(Stage 4) → 재검토 루프가 있음 (최대 2회).
문제: 이 루프 안에 넘침 판단이 없음. 조정 후에도 여전히 넘칠 수 있는데, expand 조정으로 텍스트가 늘어나서 오히려 더 넘칠 수도 있음. 루프가 넘침을 감지하지 못함.
문제 요약 매트릭스
| # | 문제 | 위치 | 핵심 원인 | 영향 |
|---|---|---|---|---|
| 1 | 내용 없이 넘침 판단 | 2.5 | 텍스트 채움 전에 판단 | Kei 판단 근거 부족 → 부정확한 결정 |
| 2 | 예상 높이 초과 → Kei에게 물음 | 2.5 | 판단 주체 잘못됨 | Sonnet 지시 불이행을 Kei가 해결할 수 없음 |
| 3 | HTML 있는데 넘침 안 봄 | 5 | 점검 항목 누락 | 실제 넘침 감지 못함 |
| 4 | 넘침 판단에 Kei 없음 | 5 | Sonnet만 참여 | 콘텐츠 중요도 무시한 조정 |
| 5 | 실제 높이 측정 없음 | 전체 | 측정 수단 부재 | 예상과 실제의 차이 감지 불가 |
| 6 | 넘침이 루프에 없음 | 5 루프 | 넘침 체크 미포함 | 조정 후 넘침 악화 가능 |
원인 관계
근본 원인: Stage 2.5의 넘침 판단 위치가 기존 DOWNGRADE_MAP 위치를 그대로 따름
↓
메커니즘만 변경(DOWNGRADE → Kei), 시점은 재검토 안 함
↓
내용 없이 판단(문제 1) + 주체 잘못됨(문제 2)
↓
실제 넘침이 감지되는 시점(Stage 4 이후)에는 검증 없음(문제 3, 4, 6)
↓
애초에 실제 높이 측정 수단도 없음(문제 5)
해결 방안 조사 결과
방안 1: 실제 렌더링 높이 측정 (문제 5 해결)
현재 파이프라인에는 렌더링된 HTML의 실제 px 높이를 측정하는 수단이 없음.
| 도구 | 정확도 | 속도 | CSS Grid | CSS 변수 | 커스텀 폰트 | 설치 상태 |
|---|---|---|---|---|---|---|
| Playwright | 픽셀 정확 | 20~50ms/요소 | 완전 지원 | 완전 지원 | 완전 지원 | 미설치 |
| Selenium | 픽셀 정확 | 50~150ms/요소 | 완전 지원 | 완전 지원 | 완전 지원 | 설치됨 (4.34.0) |
| WeasyPrint | 제한적 | 200~500ms | 부분 지원 | 제한적 | 지원 | 설치됨 (65.1) |
| 텍스트 추정 | ±15~30% 오차 | <1ms | 불가 | 불가 | 불가 | — |
권장: Playwright — 가장 정확하고 빠름. 비동기 지원. headless Chromium 자동 설치. 차선: Selenium — 이미 설치됨. 동기식이라 약간 느리지만 충분히 사용 가능.
측정 방식:
# Playwright 예시
async with async_playwright() as p:
browser = await p.chromium.launch()
page = await browser.new_page(viewport={"width": 1280, "height": 720})
await page.set_content(html)
# 각 zone의 실제 높이 측정
body_box = await page.locator("[data-zone='body']").bounding_box()
actual_height = body_box["height"] # 실제 렌더링 px
# overflow 감지: scrollHeight > clientHeight
overflow = await page.evaluate("""
el => el.scrollHeight > el.clientHeight
""", await page.query_selector("[data-zone='body']"))
방안 2: Stage 2.5 → Stage 5로 이동 (문제 1, 2, 3, 4, 6 해결)
현재: Stage 2.5에서 텍스트 없이 Kei 판단 → 근거 부족 개선: Stage 4(렌더링) 이후, Stage 5(재검토) 안에서 넘침 판단
현재:
Stage 2 → [2.5 Kei 넘침 판단] → Stage 3 → Stage 4 → Stage 5(Sonnet만)
개선:
Stage 2 → Stage 3 → Stage 4 → Stage 5(Sonnet 감지 + Kei 판단)
Stage 5 역할 확장:
- Sonnet이 감지: 렌더링된 HTML + zone 예산 정보를 보고 넘침 여부 판단
- 넘침이면 Kei에게 전달: 실제 콘텐츠가 있는 상태에서 Kei가 판단
- Kei가 결정: trim(텍스트 축약) 또는 restructure(팝업 분리)
- Sonnet이 실행: CSS 조정 + 재렌더링
Sonnet + Kei 협업 모델:
Sonnet: "body zone이 520px인데 예산 490px. 30px 초과."
↓
Kei: "꼭지 3의 부연 설명을 축약하면 됨. 핵심은 유지." (trim)
또는
Kei: "12행 비교표는 팝업으로 분리. 슬라이드엔 요약만." (restructure)
↓
Sonnet: CSS 조정 + 재렌더링
방안 3: Stage 2 구조적 검증은 유지하되 역할 한정 (문제 2 해결)
Stage 2의 _validate_height_budget()는 구조적 검증만 담당:
- 금지 블록 교체 (BODY_FORBIDDEN_MAP) — 유지
- pill-pair 단독 금지 (I-7) — 유지
- 예상 높이 초과 — 경고만 (Kei 호출 안 함, Stage 5에서 처리)
# Stage 2: 경고만 출력, overflow 정보는 Stage 5에서 활용
if total > budget:
logger.warning(f"[예상 높이 초과] {area}: {total}px > {budget}px (Stage 5에서 검증)")
# Kei 호출 안 함. 실제 렌더링 후 Stage 5에서 정확히 감지.
Sonnet 프롬프트(STEP_B_PROMPT) 개선:
- 현재: height_cost 매핑을 설명하지만 구체적 예시 없음
- 개선: 계산 예시 추가 + "초과 시 reason 필드에 설명" 명시
방안 4: 넘침을 Stage 5 재검토 루프에 통합 (문제 6 해결)
현재 Stage 5 루프:
재검토(Sonnet) → 조정(expand/shrink/rewrite) → 재편집(Kei 편집자) → 재렌더링 → 재검토
개선 Stage 5 루프:
재검토(Sonnet, 넘침 포함)
→ 넘침 있으면: Kei 판단(trim/restructure)
→ 조정 적용(expand/shrink/rewrite/trim/restructure)
→ 재편집(Kei 편집자) → 재렌더링 → 재검토
Stage 5 프롬프트에 추가할 점검 항목:
6. 높이 제약: 각 zone이 예산을 초과하는가?
- 자동 조정(shrink)으로 해결 가능 → shrink
- 불가능 → overflow_detected (Kei 판단 필요)
_apply_adjustments()에 추가할 action:
overflow_detected→ Kei API 호출 → trim/restructure 적용
해결 방안 매트릭스
| 방안 | 해결하는 문제 | 필요 기술 | 구현 난이도 |
|---|---|---|---|
| 1. 실제 높이 측정 | 문제 5 | Playwright 또는 Selenium | 중 (의존성 추가) |
| 2. 넘침 판단 Stage 5로 이동 | 문제 1, 2, 3, 4 | 코드 리팩토링 | 중 (Stage 2.5 제거, Stage 5 확장) |
| 3. Stage 2 경고만 | 문제 2 | 코드 수정 | 소 (Kei 호출 제거, 경고만) |
| 4. 넘침을 루프에 통합 | 문제 6 | Stage 5 프롬프트 + 코드 | 중 (새 action + Kei 연동) |
방안 1은 선택적 — Playwright/Selenium 없이도 Sonnet이 HTML을 읽고 넘침을 추정할 수 있음. 정확도는 떨어지지만 현실적. 방안 2+3+4는 필수 — 프로세스 구조 자체의 문제이므로 반드시 수정.
실행 계획: 프로세스 재설계 (방안 2+3+4)
충돌/회귀/오류 검토 완료. Phase I 산출물 전부 재사용. 변경 파일
pipeline.py만. Sonnet 신규 투입 0건. Kei API 호출 위치만 이동. 하드코딩/단발성 없음.
변경 전후 프로세스 비교
[변경 전]
Stage 1 → Stage 2 → [2.5 Kei 넘침 판단 ⚠️] → Stage 3 → Stage 4 → Stage 5(Sonnet만)
[변경 후]
Stage 1 → Stage 2(경고만) → Stage 3 → Stage 4 → Stage 5(Sonnet 감지 + Kei 판단)
변경 상세 (5건, pipeline.py만)
P-1: Stage 2.5 제거
위치: pipeline.py 91~136행 (46행)
작업: 전체 삭제
영향: 없음. overflow 키는 layout_concept에 남아 Stage 5에서 참고.
Phase I 회귀 검토:
call_kei_overflow_judgment()— 함수 삭제 안 함. 호출 위치만 Stage 5로 이동._downgrade_fallback()— 삭제 안 함. Stage 5에서 비상용.KEI_OVERFLOW_PROMPT— 삭제 안 함. Stage 5에서 사용.
P-2: _review_balance() 시그니처 + 프롬프트 확장
위치: pipeline.py 297~363행
작업:
- 시그니처:
(html, layout_concept, content)→(html, layout_concept, content, analysis)추가 - 프롬프트에 zone 예산 정보 + overflow 힌트 추가
- 점검 항목 6번 추가: "높이 초과 — overflow_detected"
- 출력 format에
overflow_detectedaction 추가
변경 내용:
# 시그니처 변경
async def _review_balance(
html: str,
layout_concept: dict[str, Any],
content: str,
analysis: dict[str, Any], # 추가
) -> dict[str, Any] | None:
# 프롬프트 추가
# 1. zone 예산 정보 (select_preset + LAYOUT_PRESETS에서)
preset_name = select_preset(analysis)
preset = LAYOUT_PRESETS.get(preset_name, {})
zone_budget_lines = [
f"- {name}: ~{z['budget_px']}px (너비 {z['width_pct']}%)"
for name, z in preset.get("zones", {}).items()
]
# 2. Stage 2 예상 overflow 힌트 (있으면)
overflow_hint = layout_concept.get("overflow", [])
# 3. 점검 항목 6번
"6. 높이 초과: 각 zone의 블록+텍스트가 예산을 초과하는가?\n"
" - shrink로 해결 가능 → shrink\n"
" - 불가능 (콘텐츠가 본질적으로 큼) → overflow_detected\n"
# 4. action 추가
"- overflow_detected: 높이 초과로 Kei 판단 필요. 해당 zone과 초과 블록 명시.\n"
충돌: 기존 5개 점검 + 3개 action 변경 없음. 추가만. Sonnet 역할: 넘침 감지만. 판단은 Kei.
P-3: Stage 5 루프에 Kei 넘침 판단 통합
위치: pipeline.py 155~180행
작업: 루프 내에서 overflow_detected 시 Kei 호출 추가
for review_round in range(MAX_REVIEW_ROUNDS):
review_result = await _review_balance(html, layout_concept, content, analysis)
if not review_result or not review_result.get("needs_adjustment"):
break
# overflow_detected가 있으면 Kei에게 판단 요청
overflow_adjs = [
adj for adj in review_result.get("adjustments", [])
if adj.get("action") == "overflow_detected"
]
if overflow_adjs:
# 실제 콘텐츠가 있는 상태에서 Kei 판단
overflow_context = _build_overflow_context(layout_concept, overflow_adjs)
kei_judgment = await call_kei_overflow_judgment(
overflow_context, content, analysis
)
if kei_judgment is None:
logger.warning("[DOWNGRADE 비상] Kei API 실패")
for page in layout_concept.get("pages", []):
_downgrade_fallback(page.get("blocks", []), overflow_context)
else:
# Kei 판단을 adjustments에 반영 (overflow_detected → kei_trim/restructure)
_convert_kei_judgment(review_result, kei_judgment, analysis)
# 모든 조정 적용 (기존 expand/shrink/rewrite + 신규 kei_trim)
layout_concept = await _apply_adjustments(layout_concept, review_result, content)
html = render_slide(layout_concept)
호출되는 함수: 모두 Phase I에서 만든 것 재사용
call_kei_overflow_judgment()— kei_client.py (변경 없음, Kei API만 사용)_downgrade_fallback()— design_director.py (변경 없음)
신규 헬퍼 함수 2개:
_build_overflow_context()— overflow_adjs + layout_concept에서 실제 블록 데이터 추출_convert_kei_judgment()— Kei의 trim/restructure 결정을 review_result.adjustments에 반영
P-4: _apply_adjustments() — kei_trim action 추가
위치: pipeline.py 366~410행
작업: 기존 elif 체인에 kei_trim 분기 추가
# 기존 expand/shrink/rewrite 로직 변경 없음
# 아래 elif만 추가:
elif action == "kei_trim":
max_chars = adj.get("max_chars", 200)
if "char_guide" not in block:
block["char_guide"] = {}
for key in block.get("char_guide", {}):
block["char_guide"][key] = min(block["char_guide"][key], max_chars)
if not block["char_guide"]:
block["char_guide"] = {"text": max_chars}
logger.info(f"조정: {area} → kei_trim max_chars={max_chars}")
elif action == "kei_restructure":
block["detail_target"] = True
if "data" in block:
del block["data"]
block["reason"] = f"재구성: {adj.get('detail', 'Kei 판단 팝업 분리')}"
logger.info(f"조정: {area} → kei_restructure (detail_target)")
충돌: 없음. 기존 3개 action 변경 0행. 새 elif 추가만.
P-5: 호출부 수정
위치: pipeline.py 156행
# 현재:
review_result = await _review_balance(html, layout_concept, content)
# 변경:
review_result = await _review_balance(html, layout_concept, content, analysis)
영향: 이 함수의 호출부는 pipeline.py 156행 1곳만. 다른 파일에서 호출하지 않음.
변경 파일 총괄
| 파일 | 변경 | Phase I 코드 영향 |
|---|---|---|
pipeline.py |
Stage 2.5 제거 + Stage 5 확장 + 헬퍼 2개 + action 2개 | Phase I 함수 재사용, 삭제 0건 |
design_director.py |
변경 없음 | — |
kei_client.py |
변경 없음 | — |
content_editor.py |
변경 없음 | — |
sse_utils.py |
변경 없음 | — |
검증 매트릭스
| 항목 | 결과 |
|---|---|
| Phase I 회귀 | 없음 — I-1~I-14 전부 유지, 함수/상수 삭제 0건 |
| Kei API 사용 | 유지 — call_kei_overflow_judgment() 호출 위치만 Stage 5로 이동 |
| Sonnet이 Kei 역할 대체 | 없음 — Sonnet은 감지만, 판단은 Kei만 |
| 하드코딩 | 없음 — trim max_chars는 Kei가 결정 |
| 단발성 수정 | 없음 — 범용 구조 (어떤 overflow에도 동작) |
| 기존 코드 충돌 | 없음 — overflow 키가 중간 단계에서 무시되는 것 확인 |
| DOWNGRADE 비상용 | 유지 — Stage 5에서 Kei 실패 시 동일하게 작동 |
실행 순서
- P-1: Stage 2.5 제거 (pipeline.py 91~136행 삭제)
- P-2:
_review_balance()시그니처 + 프롬프트 확장 - P-3: Stage 5 루프에 Kei 연동 + 헬퍼 함수 2개
- P-4:
_apply_adjustments()kei_trim/kei_restructure action 추가 - P-5: 호출부
analysis파라미터 추가
이력
| 날짜 | 내용 |
|---|---|
| 2026-03-26 | Phase I 실행 완료 후 프로세스 검증 중 발견. 6개 문제 진단. |
| 2026-03-26 | 해결 방안 4개 조사. Playwright 높이 측정 + Stage 5 넘침 통합 방향 도출. |
| 2026-03-26 | 실행 계획 확정. 충돌/회귀/오류 검토 완료. P-1~P-5 5건, pipeline.py만 변경. Phase I 산출물 전부 재사용. |