- 루트의 IMPROVEMENT-PHASE-*.md, PHASE-*.md 등 45개 → docs/history/로 이동 - docs/block-tests/ 오래된 블록 테스트 HTML 삭제 (figma_to_html_agent로 대체) - docs/figma-analysis/, docs/figma-assets/, docs/figma-screenshots/ 정리 - docs/test-*.html 등 초기 테스트 파일 정리 - 참고 페이지/ 스크린샷 정리 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
24 KiB
Phase N: 4대 핵심 문제 진단 + 해결 방안
작성일: 2026-03-27 상태: ✅ 완료 — catalog 개선, fallback 전면 제거, topic_id 버그 수정, 무한 재시도 체계 구축
오답 노트 (절대 반복 금지)
아래는 이미 실패가 증명된 접근법이다. 어떤 상황에서도 다시 사용하지 않는다.
| # | 실패 패턴 | 왜 실패했나 | 교훈 |
|---|---|---|---|
| X-1 | Sonnet에게 블록 선택을 맡김 | Kei 추천을 무시하고 자기 맘대로 바꿈. 프롬프트로 제어 불가 | 블록 선택은 Kei 권한. 코드 레벨 강제. |
| X-2 | Sonnet fallback (Kei 실패 시 Sonnet 대체) | Sonnet이 대체해봤자 품질이 안 나옴. 결과물이 무의미 | Kei API는 필수 인프라. 실패 시 파이프라인 중단. fallback 자체가 없음. |
| X-3 | max-height + overflow:hidden으로 CSS 사후 자르기 | 텍스트가 잘리는데 측정기가 "정상"이라고 판단. 근본적 결함 | 콘텐츠는 렌더링 전에 맞춰야 함. CSS로 사후에 자르지 않음. |
| X-4 | HTML 텍스트를 읽고 시각 검수 | Kei가 HTML 소스를 읽어봤자 렌더링 결과를 알 수 없음. 10분 낭비 | 시각 검수는 스크린샷(이미지)으로. |
| X-5 | "안전망/fallback"이라는 명목으로 실패 패턴 재도입 | 실패한 방법을 "비상용"이라고 다시 넣으면 결국 그게 돌아감 | 실패한 것은 비상용으로도 안 됨. 오답 노트에 기록하고 근절. |
| X-6 | 프롬프트만으로 LLM 행동 강제 | "반드시 존중하라"고 써도 LLM은 안 지킴 | 강제는 코드로. 프롬프트는 가이드일 뿐. |
문제 전체 요약
| # | 문제 | 원인 위치 | 심각도 |
|---|---|---|---|
| N-1 | 블록 선택이 콘텐츠 전달 방식과 안 맞음 | design_director.py Step B |
치명 |
| N-2 | 사이드바에 섹션 제목이 없음 | kei_client.py + renderer.py |
중간 |
| N-3 | max-height CSS가 콘텐츠를 잘라먹음 | renderer.py 229-235행 |
치명 |
| N-4 | Stage 5가 HTML 텍스트를 읽어서 무용지물 | kei_client.py + pipeline.py |
치명 |
N-1. 블록 선택이 콘텐츠 전달 방식과 안 맞음
현상
- Kei 실장(Opus)이 1단계에서
expression_hint,relation_type을 판단함 - 2단계 Step A-2에서 Kei가 블록을 추천함 (
_opus_block_recommendation()) - 그런데 Step B에서 Sonnet이 Kei 추천을 무시하고 자기 맘대로 블록을 바꿈
- 프롬프트에 "Opus 추천 존중" 규칙을 넣어도 Sonnet이 안 지킴
원인 (코드 레벨)
design_director.py — Step B 흐름:
Step A: rule-based preset 선택 (sidebar-right 등)
Step A-2: Kei API로 블록 추천 받음 → opus_blocks[]
Step B: Sonnet이 zone 배치 + char_guide 결정
↑ 여기서 Sonnet이 블록 타입을 바꿔버림
STEP_B_PROMPT에 "Opus가 추천한 블록을 존중하라"고 적어놨지만, 프롬프트는 강제가 아니다.
Sonnet은 "더 적절하다"고 판단하면 얼마든지 다른 블록을 선택한다.
해결 방안: Kei가 블록을 결정, Sonnet은 zone + char_guide만
핵심 원칙: 블록 선택 = Kei 권한. 코드 레벨 강제. 프롬프트 의존 안 함.
변경 대상: design_director.py
현재 흐름:
Step A: preset 선택
Step A-2: Kei 블록 추천 (참고용)
Step B: Sonnet이 블록 + zone + char_guide 전부 결정
변경 후:
Step A: preset 선택
Step A-2: Kei가 블록 확정 (topic_id → block_type 매핑)
Step B: Sonnet은 zone 배치 + char_guide만 결정 (block_type 변경 금지)
구체적 변경:
-
Step A-2 (
_opus_block_recommendation): Kei API 응답에서 받은 블록을 "추천"이 아닌 "확정"으로 처리- 반환값:
{topic_id: block_type}딕셔너리 - 이 딕셔너리를 Step B에 읽기 전용으로 전달
- 반환값:
-
Step B 프롬프트 변경:
STEP_B_PROMPT에서 블록 선택 지시 제거- "각 꼭지에 맞는 블록을 선택하라" → 삭제
- "아래 확정된 블록의 zone 배치와 글자 수 가이드만 결정하라"로 변경
-
Step B 후처리 (코드 강제):
# Sonnet 응답 후, 블록 타입을 Kei 확정값으로 덮어쓰기 for block in sonnet_blocks: tid = block.get("topic_id") if tid in kei_confirmed_blocks: block["type"] = kei_confirmed_blocks[tid] # 코드 레벨 강제- Sonnet이 어떤 블록을 응답하든, topic_id에 매칭되는 Kei 확정 블록으로 강제 교체
- Sonnet의 zone, char_guide, reason만 살림
-
Kei API는 필수 의존성: 실패 시 fallback 없음. 파이프라인 중단 + 에러 반환.
- Kei API(localhost:8000)는 항상 떠 있어야 하는 로컬 인프라
- 안 되면 그건 버그. 대체 경로가 아니라 수정 대상.
사용 기술:
- 기존 Kei API (
_opus_block_recommendation) — 이미 존재 - Python dict 매핑으로 코드 레벨 강제 — 새 도구 불필요
STEP_B_PROMPT프롬프트 축소 — zone + char_guide만
N-2. 사이드바에 섹션 제목이 없음
현상
- 사이드바에 "용어 정의" 같은 콘텐츠가 배치되는데
- 그게 뭔지 알려주는 섹션 제목이 없음
- 독자가 사이드바가 무엇인지 맥락을 모름
원인 (코드 레벨)
- Kei 1단계 (
KEI_PROMPT):role: "reference"+purpose: "용어정의"는 출력하지만, section_title 필드가 없음 - design_director.py Step B: sidebar zone에 블록을 배치할 때 섹션 제목 블록을 안 넣음
- renderer.py: area div를 렌더할 때 영역 라벨 없이 바로 블록 HTML만 출력
해결 방안: Kei가 section_title 판단 + 렌더러가 표시
변경 대상: kei_client.py, design_director.py, renderer.py
-
Stage 1 Kei 프롬프트 (
KEI_PROMPT) 확장:- 기존 topic 필드에
section_title추가 role: "reference"인 꼭지에 Kei가 "용어 정의", "참고 자료" 등 섹션 제목을 부여- 출력 JSON 예시:
{"id": 4, "title": "용어 혼용 정리", "purpose": "용어정의", "role": "reference", "section_title": "용어 정의"}
- 기존 topic 필드에
-
Step B 블록 배치에 section label 블록 자동 삽입:
- sidebar zone에 reference 블록이 배치될 때
- 해당 topic의
section_title이 있으면 →topic-center또는divider-text블록을 자동 삽입 - 이것은 코드 레벨 (Sonnet 판단 아님)
-
renderer.py
_group_blocks_by_area()에서 sidebar 처리:- sidebar area 그룹에 section label이 있으면 최상단에 배치
- CSS: 작은 글씨 + 볼드 + 하단 구분선
사용 기술:
- KEI_PROMPT JSON 스키마 확장 (section_title 필드 1개)
- 기존 블록 (
divider-text또는topic-center) 재활용 - renderer.py 코드 로직으로 자동 삽입
N-3. max-height CSS가 콘텐츠를 잘라먹음
현상
- 렌더된 HTML에서 텍스트가 중간에 뚝 잘려 보임
- Selenium으로 측정하면 "overflow 없음"이라고 나옴 → 실제로는 잘리고 있는데 감지 못함
- 결과: Phase L 피드백 루프가 "정상"으로 판단하고 넘어감 → 잘린 채로 최종 출력
원인 (코드 레벨)
renderer.py 229-235행:
# Phase L: 블록별 max-height 제약
max_height = block.get("_max_height_px")
if max_height:
rendered_html = (
f'<div style="max-height:{max_height}px; overflow:hidden;">'
f'{rendered_html}</div>'
)
이게 하는 일:
- 블록에
max-height: Npx; overflow: hiddenCSS를 씌움 - → 콘텐츠가 N px을 넘으면 시각적으로 잘림
- →
overflow: hidden이므로scrollHeight === clientHeight→ 측정기가 "overflow 없음"으로 판단 - → 피드백 루프가 작동 안 함 → 잘린 채 확정
근본 원인: 텍스트가 공간에 맞는지를 CSS로 사후에 자르는 게 아니라, 편집 단계에서 글자 수를 맞춰야 한다.
해결 방안: max-height 제거 + 편집자에게 _max_chars 강제 전달
핵심 원칙:
- 콘텐츠가 렌더링 전에 공간에 맞아야 한다 (fit before render)
- CSS로 사후에 자르지 않는다
- overflow는 측정으로 감지하고, 감지되면 편집자를 다시 호출한다
변경 대상: renderer.py, content_editor.py, slide_measurer.py
변경 1: renderer.py에서 max-height 래퍼 제거
# 229-235행 삭제. 아래 코드 완전 제거:
max_height = block.get("_max_height_px")
if max_height:
rendered_html = (
f'<div style="max-height:{max_height}px; overflow:hidden;">'
f'{rendered_html}</div>'
)
max-height 없이 렌더링 → overflow가 생기면 scrollHeight > clientHeight로 정확히 감지됨.
변경 2: content_editor.py 프롬프트에 _max_chars 강제 명시
현재 EDITOR_PROMPT의 purpose별 분량 원칙이 "가이드라인" 수준.
_max_chars가 계산되어 있지만 편집자에게 전달이 안 되고 있음.
# fill_content()에서 각 블록의 _max_chars를 프롬프트에 명시
req_text += f"\n **최대 글자 수 (절대 제한): {block.get('_max_chars', '없음')}자**"
req_text += f"\n 이 글자 수를 넘기면 슬라이드에서 잘린다. 반드시 지켜라."
변경 3: slide_measurer.py의 overflow 감지 정상화
max-height + overflow:hidden이 없어지면, 기존 측정 스크립트가 정상 작동:
// scrollHeight > clientHeight → 정확한 overflow 감지
overflowed: zone.scrollHeight > zone.clientHeight + 2
현재 _MEASURE_SCRIPT는 이미 이 로직을 갖고 있음. max-height만 제거하면 됨.
추가: overflow:visible 확인
- CSS에서 zone/block 컨테이너에
overflow: hidden이 없는지 확인 base.css에 혹시 hidden이 있으면 제거- 기본값
overflow: visible이면 scrollHeight 측정이 정확
변경 4: Phase L 피드백 루프 강화
현재 pipeline.py 215-275행의 피드백 루프:
- 측정 → overflow 감지 → char_guide 축소 → 편집자 재호출 → 재렌더링
- 최대 3회 반복
수정사항:
- char_guide 축소 대신
_max_chars직접 축소 (더 정확) - 축소량:
calculate_trim_chars(excess_px)결과를_max_chars에서 차감 - 편집자 재호출 시 축소된
_max_chars를 프롬프트에 명시
사용 기술:
- 기존 Selenium +
scrollHeight > clientHeight— 이미 존재, max-height만 제거하면 작동 - 기존
calculate_max_chars(),calculate_trim_chars()— 이미 존재 content_editor.py프롬프트 확장 —_max_chars전달만 추가- 새 도구 불필요
N-4. Stage 5가 HTML 텍스트를 읽어서 무용지물
현상
- Kei 실장이 최종 검수 (Stage 5)에서 10분 걸리는데 아무것도 안 바뀜
- 이유: Kei가 HTML 소스 텍스트를 읽고 검수함
- HTML 태그 사이에서 실제 렌더링 결과를 상상해야 함 → 불가능
- "텍스트가 잘리는지", "비중이 맞는지", "가독성이 괜찮은지" → HTML 텍스트로는 판단 불가
원인 (코드 레벨)
kei_client.py call_kei_final_review() 306-313행:
prompt = (
KEI_REVIEW_PROMPT + "\n\n"
f"## 핵심 메시지\n{core_message}\n\n"
...
f"\n\n## 조립 HTML (요약)\n{html[:3000]}\n\n" # ← HTML 소스 텍스트 3000자
f"위 결과물을 검수하고 조정이 필요한지 판단해. JSON만."
)
Kei(Opus)는 멀티모달 모델이라 이미지를 볼 수 있는데, 현재는 텍스트만 전달.
해결 방안: Selenium 스크린샷 → Kei API에 이미지 전달
핵심 원칙:
- Stage 5에서 Kei가 실제 렌더링된 슬라이드 스크린샷을 보고 검수
- HTML 텍스트 읽기 → 이미지 보기로 전환
- overflow 없으면 Stage 5 건너뜀 (시간 절약)
- 최대 1회만 (현재 2회 → 1회)
기술 조사 결과
Selenium 스크린샷 → base64
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
options = Options()
options.add_argument("--headless=new")
options.add_argument("--window-size=1280,720")
options.add_argument("--force-device-scale-factor=1")
driver = webdriver.Chrome(options=options)
driver.get(f"data:text/html;charset=utf-8,{encoded_html}")
# 슬라이드 요소만 정확히 캡처
slide = driver.find_element(By.CSS_SELECTOR, ".slide")
screenshot_b64 = slide.screenshot_as_base64 # str, 순수 base64
driver.quit()
API 출처: Selenium 4.x WebElement.screenshot_as_base64 프로퍼티
- 반환:
str(순수 base64, data URI prefix 없음) - 형식: PNG
- 해당 요소의 bounding box만 캡처 (전체 페이지가 아님)
Anthropic Claude API 이미지 전달 형식
import anthropic
client = anthropic.AsyncAnthropic(api_key=settings.anthropic_api_key)
response = await client.messages.create(
model="claude-opus-4-0-20250514", # Opus = 멀티모달 지원
max_tokens=4096,
messages=[{
"role": "user",
"content": [
{
"type": "image",
"source": {
"type": "base64",
"media_type": "image/png",
"data": screenshot_b64, # 순수 base64 문자열
},
},
{
"type": "text",
"text": "이 슬라이드를 검수해줘. ...",
},
],
}],
)
API 출처: Anthropic 공식 Vision 문서
- 지원 모델: Claude Opus 4, Sonnet 4, Haiku 3.5 전부 멀티모달 지원
- 지원 포맷: PNG, JPEG, GIF, WebP
- 이미지 크기 제한: 최대 8000x8000px, 5MB/장
- 1280x720 슬라이드: ~1,229 토큰 (비용 미미)
문제: 현재 Kei API(/api/message)는 이미지 미지원
Kei persona_agent 조사 결과:
ChatRequest모델:message: str(텍스트만)- 이미지 필드 없음
- LLM 호출 시 messages를
{"role": "user", "content": str}로 전달
필요한 변경 (Kei persona_agent 측):
# ChatRequest 확장 (persona_agent/backend/main.py)
class ChatRequest(BaseModel):
session_id: str | None = None
message: str
image_data: str | None = None # base64 이미지 (선택)
image_media_type: str | None = None # "image/png" 등 (선택)
- 4개 파일, ~50줄 변경
- 기존 텍스트 요청은 깨지지 않음 (image 필드는 optional)
- Anthropic SDK는 이미 이미지 content block 지원 → 그대로 전달만 하면 됨
전체 Stage 5 변경 흐름
현재:
Phase L 측정 → Stage 5: Kei가 HTML 텍스트 3000자 읽기 → 조정
변경 후:
Phase L 측정 → overflow 없으면 Stage 5 건너뜀 (시간 절약)
→ overflow 있으면:
1. Selenium으로 슬라이드 스크린샷 (base64 PNG)
2. 스크린샷 + 측정 데이터 → Kei API (이미지 포함)
3. Kei가 실제 렌더링 보고 판단 → 조정 지시
4. 최대 1회 (현재 2회에서 축소)
변경 대상:
kei_client.py:call_kei_final_review()에 이미지 전달 추가pipeline.py: Stage 5에 스크린샷 촬영 + overflow 없으면 skip 로직slide_measurer.py: 스크린샷 캡처 함수 추가 (capture_slide_screenshot())- Kei persona_agent: ChatRequest에 image 필드 추가 (4파일 ~50줄)
주의: Kei persona_agent 코드를 수정해야 함 → 사용자 승인 필요
대안: Kei API 변경 없이 Anthropic 직접 호출
Kei API 수정이 부담스러우면, Stage 5만 Anthropic API 직접 호출 가능:
anthropic.AsyncAnthropic으로 Opus 직접 호출- Kei 페르소나 시스템 프롬프트를
personas/kei.md에서 로드하여 system으로 전달 - 단점: Kei의 RAG/세션 컨텍스트를 못 씀
- 장점: persona_agent 수정 없음
실행 순서 (의존 관계)
N-3 (max-height 제거) ← 가장 먼저. 다른 것과 독립.
│
├→ N-1 (블록 선택 강제) ← N-3과 독립. 병렬 가능.
│
├→ N-2 (사이드바 제목) ← N-1 완료 후 (블록 확정 후 제목 삽입)
│
└→ N-4 (스크린샷 검수) ← N-3 완료 필수 (overflow 감지 정상화 후)
추천 순서:
- N-3 — max-height 제거 + _max_chars 편집자 전달 (즉시, 가장 급함)
- N-1 — 블록 선택 코드 강제 (N-3과 병렬 가능)
- N-2 — 사이드바 섹션 제목 (N-1 후)
- N-4 — 스크린샷 기반 검수 (N-3 후, Kei API 수정 필요)
충돌 / 회귀 / 오류 검토
검토 방법
- 4개 변경의 모든 수정 대상 파일을 코드 레벨로 읽고 교차 검증
overflow: hidden전수 조사 (.py,.css,.html전체)_max_height_px,_max_chars참조 전수 조사- 각 변경 간 의존 관계 + 실행 순서에서의 충돌 가능성 점검
N-3 (max-height 제거) — 충돌 분석
overflow: hidden이 존재하는 3개 레이어:
| 위치 | 값 | 용도 | 건드리나 |
|---|---|---|---|
.slide (base.css:16) |
overflow: hidden |
1280x720 프레임 바깥 차단 | 유지 (건드리지 않음) |
.slide > div (base.css:76) |
overflow: visible |
area div (body, sidebar 등) | 이미 visible. 변경 불필요 |
renderer.py:229-235 |
max-height:Npx; overflow:hidden |
블록별 래퍼 | 이것만 제거 |
개별 블록 템플릿의 overflow: hidden (15개+):
card-image-3col.html,card-dark-overlay.html,venn-diagram.html등- 이것은 이미지/카드의
border-radius잘림용 - 텍스트 clipping과 무관 → 건드리지 않음
Phase L 측정기 영향:
- max-height 래퍼 제거 후,
scrollHeight가 실제 콘텐츠 높이를 정확 반영 _MEASURE_SCRIPT의block.scrollHeight > block.clientHeight→ 정상 작동- 이전에 false-negative(잘렸는데 감지 못함)이던 것이 정상 감지됨
- Phase L 루프가 더 자주 트리거될 수 있음 → 의도한 동작 (잘리는 걸 고치는 것)
- MAX_MEASURE_ROUNDS = 3이면 충분
회귀 위험: 없음. max-height 래퍼는 Phase L에서 추가된 것이고, 제거해도 기존 블록/CSS에 영향 없음.
N-1 (블록 선택 강제) — 충돌 분석
기존 Step B 후처리 체인 (design_director.py:819-850):
현재: Sonnet 응답 → 미등록 블록 거부 → area명 검증 → conclusion→footer 강제
추가: Sonnet 응답 → ★Kei 확정 블록 덮어쓰기 → 미등록 블록 거부 → area명 검증 → conclusion→footer 강제
| 시나리오 | 처리 |
|---|---|
| Kei가 추천한 블록이 catalog에 없음 | 바로 다음 단계에서 미등록 검증 → PURPOSE_FALLBACK 교체 |
| Kei가 추천한 블록이 sidebar 금지 | _validate_height_budget()의 SIDEBAR_FORBIDDEN_BLOCKS 체크 |
| Kei API 미응답 | 파이프라인 중단 + 에러 반환. fallback 없음. Kei API는 필수 인프라. |
N-3과의 관계: 독립. N-1은 2단계, N-3은 4단계. 서로 다른 파이프라인 단계.
회귀 위험: 없음. 기존 검증 체인 위에 한 단계 추가할 뿐.
N-2 (사이드바 제목) — 충돌 분석
| 시나리오 | 위험 | 대응 |
|---|---|---|
| label 블록 추가 → sidebar 높이 예산 초과 | 낮음 (label ~30px, 예산 490px) | label 블록은 고정 30px, allocate 제외 |
| N-1 미완료 상태에서 실행 | Sonnet이 블록을 바꿔서 label 위치 엉뚱 | 실행 순서: N-1 먼저, N-2 나중 |
_group_blocks_by_area() 호환 |
flex-column 최상단에 자연 배치 | 호환 문제 없음 |
회귀 위험: 없음. 기존 로직에 label 삽입만 추가.
N-4 (스크린샷 검수) — 충돌 분석
| 시나리오 | 위험 | 대응 |
|---|---|---|
| N-3 미완료 → overflow 감지 부정확 | "overflow 없으면 skip" 판단이 틀림 | 실행 순서: N-3 먼저, N-4 나중 |
| Selenium 인스턴스 충돌 | Phase L에서 quit 후 Stage 5에서 새 생성 | 동시 사용 아님, 충돌 없음 |
| Kei persona_agent 미수정 | 이미지 전달 불가 | 대안: Anthropic 직접 호출 (persona 프롬프트 파일에서 로드) |
| MAX_REVIEW_ROUNDS 2→1 축소 | 기존보다 조정 기회 줄어듦 | 스크린샷 기반이라 1회로 충분 (텍스트 기반이라 2회 필요했던 것) |
N-4 선행 조건 결정 필요:
- 옵션 A: Kei persona_agent 수정 (ChatRequest에 image 필드 추가, ~50줄)
- 장점: Kei의 RAG + 세션 컨텍스트 활용 가능
- 단점: persona_agent 코드 수정 필요
- 옵션 B: Anthropic API 직접 호출 (persona_agent 수정 없이)
- 장점: design_agent 내에서 완결
- 단점: Kei의 RAG/세션 없음, 페르소나 프롬프트만 로드
회귀 위험: 없음. Stage 5가 기존에 거의 무의미했으므로 (아무것도 안 바뀜), 변경해도 기존 품질이 나빠질 수 없음.
상호 작용 매트릭스
| N-1 | N-2 | N-3 | N-4 | |
|---|---|---|---|---|
| N-1 | — | N-2가 N-1에 의존 | 독립 | 독립 |
| N-2 | N-1 완료 후 실행 | — | 독립 | 독립 |
| N-3 | 독립 | 독립 | — | N-4가 N-3에 의존 |
| N-4 | 독립 | 독립 | N-3 완료 후 실행 | — |
충돌 가능 조합: 없음. 4개 변경이 모두 파이프라인의 서로 다른 단계를 수정하므로 교차 간섭 없음.
최종 실행 계획
실행 순서 (의존 관계 기반)
① N-3: max-height 래퍼 제거 + _max_chars 편집자 전달
(독립, 즉시 실행 가능)
② N-1: 블록 선택 코드 강제
(N-3과 독립, ①과 병렬 가능)
③ N-2: 사이드바 섹션 제목
(②N-1 완료 후)
④ N-4: 스크린샷 기반 검수
(①N-3 완료 후 + persona_agent 수정 또는 직접호출 결정 후)
각 항목별 변경 파일 + 예상 규모
| 항목 | 변경 파일 | 신규 코드 | 삭제 코드 | 프롬프트 변경 |
|---|---|---|---|---|
| N-3 | renderer.py, content_editor.py, pipeline.py | ~10줄 | ~7줄 | EDITOR_PROMPT에 _max_chars 절대제한 추가 |
| N-1 | design_director.py | ~15줄 (후처리 강제) | ~0줄 | STEP_B_PROMPT에서 블록선택 지시 제거 |
| N-2 | kei_client.py, design_director.py, renderer.py | ~20줄 | ~0줄 | KEI_PROMPT에 section_title 필드 추가 |
| N-4 | slide_measurer.py, kei_client.py, pipeline.py + (persona_agent 4파일) | ~80줄 | ~10줄 | KEI_REVIEW_PROMPT을 이미지 기반으로 변경 |
오류 처리 원칙
Kei API는 필수 인프라다. "실패하면 대체"가 아니라, 실패하면 파이프라인 중단이다.
| 시나리오 | 처리 | 이유 |
|---|---|---|
| Kei API 미응답 (N-1, N-2, N-3, N-4 공통) | 파이프라인 즉시 중단 + 에러 반환 | Kei는 선택이 아닌 필수. 없으면 돌리면 안 됨 |
| 편집자(Kei)가 _max_chars 안 지킴 (N-3) | Phase L 루프가 감지 → Kei 편집자 재호출 (최대 3회) | 측정 기반 재시도 |
| Selenium 스크린샷 실패 (N-4) | Stage 5를 텍스트 기반으로 수행 (현재 방식) | Selenium은 도구. 도구 실패 시 기존 방식 유지 |
| sidebar label이 높이 초과 유발 (N-2) | label을 고정 30px로 처리, allocate에서 제외 | 본문 블록 공간 유지 |
파일별 변경 범위 요약
| 파일 | N-1 | N-2 | N-3 | N-4 |
|---|---|---|---|---|
design_director.py |
Step B 프롬프트 축소 + 후처리 강제 | sidebar label 삽입 | - | - |
kei_client.py |
- | KEI_PROMPT section_title 추가 | - | 이미지 전달 추가 |
content_editor.py |
- | - | _max_chars 프롬프트 전달 | - |
renderer.py |
- | sidebar label 렌더 | max-height 래퍼 삭제 | - |
pipeline.py |
- | - | Phase L 루프 _max_chars 축소 | Stage 5 스크린샷 + skip 로직 |
slide_measurer.py |
- | - | - | capture_slide_screenshot() 추가 |
space_allocator.py |
- | - | - | - |
| Kei persona_agent | - | - | - | ChatRequest 이미지 확장 (~50줄) |