← 목록으로

레시피 생성 운영서 (RUNBOOK)

매일 조금씩 굽고 모을 것이므로, 다음 날의 내가 같은 시행착오를 안 겪게 적는다.
2026-09-01 첫 배치(18장 발행) 실측 기준.


0. 복잡하지 않다 — 매일 하는 건 명령 세 줄이다

창업자 질문: "뭔가 과정이 복잡하고 이상하게 하는 건 아니야?"

아니다. 복잡했던 건 처음 한 번의 탐색이었지 매일 하는 일이 아니다.
앞으로 하루치는 이게 전부다:

# ① 오늘 구울 20개 고르기 (아직 안 쓴 것에서)
node scripts/recipe-pick.cjs --count 20 > /tmp/today.json

# ② 굽고 큐에 넣기          (시험 키. 실제로 팔 것이면 --real-key)
node scripts/recipe-generate-batch.cjs --picks /tmp/today.json

# ③ 카드 채우고(제목·훅·프롬프트·태그) 발행
node scripts/recipe-describe.cjs && node scripts/recipe-publish.cjs

나머지는 전부 이미 밟아 본 지뢰를 코드가 대신 피해 주는 것이다.


1. 이 워크플로우가 하는 일

핀터레스트 레퍼런스        우리 대표 모델 얼굴
        │                         │
        └──────────┬──────────────┘
                   ▼
        nano-banana-2-lite/edit         ← 베끼지 않는다. 구도·조명만 가져온다
                   ▼
        R2 recipe-out/                  ← 우리 것으로 고정
                   ▼
        samples (status=queued)         ← 기존 큐. 새 테이블 안 만든다
                   ▼
        Haiku 4.5 vision                ← 제목·훅·복사용 프롬프트·태그
                   ▼
        /recipes 에 발행

2. 왜 lite 인가 — 창업자 판정 (2026-09-01)

실측에서 lite 는 포즈를 그대로 안 따라오고 옷도 바꿨다. 나는 이걸 실패로 보고
nano-banana-2 를 추천했는데, 창업자 판정은 반대였다:

"그대로 베끼는 것도 마음에 안 들고 포즈도 완벽히 같을 필요 없다.
오히려 2차 저작물로 만드는 데 더 적합하지 않냐."

베끼기가 목적이 아니다. 그래서 lite 를 쓰고, 프롬프트도 "정확히 재현하라"가 아니라
"구도·조명·무드만 가져오고 의상은 비슷하되 분명히 다르게, 포즈는 자연스러운 변주로"라고 쓴다.

다만 알고 쓸 것 — 두 가지 한계

실측
원본 얼굴이 남는다 Y2K 블록 10장은 얼굴이 잘 바뀌었는데, 미니멀 블록 8장은 원본 인상이 꽤 남았다. 원인은 원본 해상도다 — 미니멀 원본은 고화질에 얼굴이 정면으로 크게 박혀 있어 lite 가 거기 강하게 붙는다. 저화질 원본이 오히려 결과가 좋다.
우리 모델도 안 나온다 차서윤·윤라온을 넣어도 결과는 "제3의 인물"이다. lite 는 정체성 전이가 약하다. "우리 대표 모델이 나오는 화보"가 목적이면 lite 로는 안 된다 — 그때는 nano-banana-2/edit($0.08).

3. ★ 지뢰 목록 — 여기서 시간을 다 썼다

3-1. /recipes 는 발행해도 안 보인다 (3중 관문)

status='published' 만 켜면 안 뜬다. lib/prompts/queries.tstoCard() 가 거른다:

if (!r.slug || !prompt || !image) return null      // prompt = metadata.prompt

필수 4종: is_public=true · slug(비면 탈락) · metadata.prompt(비면 탈락) · 이미지.
거기에 태그는 lib/prompts/tags.ts 의 8개 중에서만 필터에 잡힌다
(뷰티·패션·잡화·식품음료·리빙·디지털·모델인물·매장). 임의 태그는 카운트가 0이 된다.

→ 실제로 18장을 발행해 놓고 화면에 하나도 안 떠서 되짚었다.

3-2. samples.source 는 4개만 허용된다

CHECK (source = ANY (ARRAY['app','curated','featured','base']))

recipe-ref 를 넣다가 23514 로 막혔다. 우리 것은 curated.

3-3. AI Gateway — AI_GATEWAY_BASE_URL 을 그대로 쓰면 404

환경변수 값은 https://ai-gateway.vercel.sh/v1/ai 인데 이건 AI SDK 전용 경로다.
REST(OpenAI 호환)로 칠 때는 /v1/chat/completions.

❌ {BASE}/chat/completions   → 404 not_found
✅ https://ai-gateway.vercel.sh/v1/chat/completions

3-4. FAL 결과는 PNG 인데 우리는 .jpg 로 저장한다

확장자를 믿고 image/jpeg 로 vision 에 보내면 Anthropic 이 400 으로 거절한다:

"specified using the image/jpeg media type, but the image appears to be a image/png image"

매직바이트로 실제 타입을 읽는다 (89504e47 = PNG). 코드에 반영돼 있다.

3-5. R2 워커는 기본 python User-Agent 를 403 으로 막는다

urllib.request.urlopen(url) → 403. curl 이나 User-Agent 헤더를 넣어야 한다.

3-6. FAL 이 간헐적으로 빈 응답을 준다

같은 입력인데 한 번은 빈 배열, 다음엔 정상. 재시도 1회가 배치에 들어 있다.
그래도 안 되면 안전 필터일 가능성이 높다 — 노출이 큰 컷에서 나온다(087·095).
버린다. 매달리지 마라 (창업자 지시: "실패했어? 버려").

3-7. 핀터레스트 원본은 죽는다

받은 136개 중 8개가 이미 죽어 있었다(6%). 그래서 무엇을 하기 전에 R2 로 복사한다.
scripts/recipe-source-import.cjs 가 그 일을 하고 sha256 을 남긴다.


4. 자산 지도

무엇 어디
원본 128장 R2 assets/recipe-source/{a-y2k,b-minimal}/<nnn>.jpg
생성물 R2 assets/recipe-out/<coll>-<nnn>-<model>.jpg
대표 모델 얼굴 R2 assets/samples/images/presets-signature/{cha-seoyoon,yoon-raon,jin-taeyoon}-source.jpg
레코드 samples (app_id=recipes, source=curated)
공개 화면 /recipes · 상세 /recipes/[slug]
검수 화면 /admin/recipes (로그인 필요)
반입 scripts/recipe-source-import.cjs
굽기 scripts/recipe-generate-batch.cjs
카드 채우기 scripts/recipe-describe.cjs

5. 비용

장당 20장/일
nb2-lite 굽기 ~$0.05 ~$1.0
Haiku vision 카드 ~$0.002 ~$0.04
합계 ~$1/일

키는 기본 FAL_KEY_TEST(시험용). 실제로 팔 발행분을 구울 때만 --real-key.

6. 진행 현황

반입한 원본 128장 (a-y2k 63 · b-minimal 65)
구운 것 18장 (2026-09-01)
발행 18장/recipes 총 34개
남은 원본 107장 → 하루 20장이면 6일
실패 087 · 095 (안전 필터 추정) — 버림

7. 아직 안 정한 것


근거: 2026-09-01 실측. 커밋 a8256f6c 이후.