---
title: 모범 사례
description: 사례 하나 — 라이브 컷 생성, 클립 receipt, 같은 스레드에서 장면 하나 다시 받기.
---

모비두 백엔드 연동용 **사례 하나**입니다. create → receipt → 클립 재시도가
어떻게 이어질 수 있는지 보여주는 예이며, 계약서가 아닙니다.

팀 워크스페이스 API 키:
[팀 Format에는 팀 키가 필요합니다](/formats/call#팀-format에는-팀-키가-필요합니다).

API 표면: [라이브 커머스 API](/enterprise/mobidoo/live-commerce).  
`input`과 typed receipt 깊이:
[Format 호출하기 — 요청 본문](/formats/call#요청-본문),
[구조화 출력](/formats/structured-output).

## 1. Create 예시

instruction + 편한 형태의 `input` + 파트너 `output_schema`(typed receipt —
완성본과 씬별 클립). 탭은 같은 body의 cURL / TypeScript / JavaScript /
Python입니다.

<!-- api-call-example:format-mobidoo-lc-product -->

### create body에서 반드시 맞춰야 하는 것

이 body에서 실제로 하중을 받는 것은 네 가지입니다. 위 탭이 보내는 body를 그대로
펼친 것입니다 — 이 호출은 34.72초 컷, 씬 7개, $7.02로 돌아왔습니다. 긴 문자열만
길이 때문에 줄였습니다:

```json
{
  "instruction": "라이브 커머스 세로 영상. input.script.segments의 INTRO/MID/FIN 대본을 그대로 사용하고 문장을 임의로 바꾸지 말 것. 대본을 늘리거나 문장을 추가하지 말 것 — 이 대본 분량이 곧 영상 길이입니다. BGM 없음, 자막 없음, 9:16 세로, 30fps. 화자는 input.host_image_url의 인물 1인.",
  "output_schema": {
    "name": "mobidoo/live-commerce/v1",
    "strict": true,
    "schema": {
      "type": "object",
      "additionalProperties": false,
      "required": ["live_title", "live_description", "full_video", "opening", "middle", "closing", "schedule_images"],
      "properties": {
        "live_title": { "type": ["string", "null"] },
        "live_description": { "type": ["string", "null"] },
        "full_video": { "$ref": "SumeMediaFile#" },
        "opening": { "type": "array", "items": { "$ref": "#/$defs/scene" } },
        "middle": { "type": "array", "items": { "$ref": "#/$defs/scene" } },
        "closing": { "type": "array", "items": { "$ref": "#/$defs/scene" } },
        "schedule_images": { "type": "array", "items": { "$ref": "SumeMediaFile#" } }
      },
      "$defs": {
        "scene": {
          "type": "object",
          "additionalProperties": false,
          "required": ["id", "role", "act", "status", "script", "tags", "duration_seconds", "video"],
          "properties": {
            "id": { "type": "string" },
            "role": { "type": "string", "enum": ["talk", "broll", "under_banner"] },
            "act": { "type": ["string", "null"] },
            "status": { "type": "string", "enum": ["succeeded", "stand-in", "failed"] },
            "script": { "type": ["string", "null"] },
            "tags": { "type": "array", "items": { "type": "string" } },
            "duration_seconds": { "type": ["number", "null"] },
            "video": { "$ref": "SumeMediaFile#" }
          }
        }
      }
    }
  },
  "primary_output_key": "full_video",
  "generation_spend_cap_usd": 20,
  "input": {
    "product_url": "https://item.gmarket.co.kr/Item?goodscode=4364915596",
    "product_name": "AHC 마스터즈 아쿠아리치 선크림 듀오세트",
    "on_card_name": "AHC 아쿠아리치 선크림 듀오",
    "host_image_url": "https://media.sume.com/assets/asset_707ef7a4149340d494a12278ef4e95fe/source.png",
    "vo_language": "ko",
    "price": { "list": "32,900원", "sale": "23,030원", "discount_label": "30%" },
    "highlights": ["SPF50+ PA++++ 4중 UV 에이징 케어", "…"],
    "script": {
      "segments": [
        { "tag": "INTRO", "text": "안녕하세요, AI 쇼호스트 유라입니다. …" },
        { "tag": "MID", "text": "구성은 삼십 밀리리터 본품 네 개, 듀오세트 두 개이고 …" },
        { "tag": "FIN", "text": "가격은 정가 삼만 이천구백원에서 이만 삼천삼십원, …" }
      ]
    }
  }
}
```

**`output_schema`는 객체 전체입니다** — `{ name, strict, schema }`이고 JSON
Schema를 그 안에 그대로 실어야 합니다. 등록된 이름만 보내는 것으로는 부족합니다.
`primary_output_key`와 함께 보내고, 적용됐는지 확인하세요. create 응답이
`output_schema.source`를 돌려주는데, 보낸 스키마가 적용됐다면
`request_override`입니다.

**`generation_spend_cap_usd`는 0이 아니어야 하고** 예상치보다 넉넉해야 합니다.
0이거나 빠뜨린 것은 "무제한"이 아닙니다. 위 실행의 cap은 $20이었고 실제 청구는
$7.02였습니다.

**`input`은 평평한 snake\_case입니다** — `product_url`, `product_name`,
`on_card_name`, `host_image_url`, `vo_language`, `price`, `highlights`,
`script.segments`. 래퍼 키 아래로 감싸지도, camelCase로 바꾸지도 마세요.

**파트너 쪽 대문자 태그가 그대로 동작합니다.** `INTRO` / `MID` / `FIN`을 보내기
전에 변환할 필요가 없습니다. receipt는 `act`와 `tags`에서 `Intro` / `Mid` /
`Fin`으로 정규화해 돌려줍니다. 비교는 보낸 표기가 아니라 **receipt의 표기**를
기준으로 하세요.

인식되는 `input` 키가 없는 요구사항은 전부 `instruction`으로 갑니다. 위 실행도
"segments를 그대로 사용", "대본을 늘리지 말 것", "무BGM·무자막·9:16"을 거기에
넣었습니다.

### 길이를 정하는 것은 대본 분량입니다

길이 파라미터는 없습니다. 완성본 길이는 대본을 읽는 데 걸리는 시간이고, 따라서
길이를 조절하는 유일한 손잡이는 `script.segments`에 넣는 글자 수입니다. 이
자료에서 한국어 기준으로 유지된 비율은 **초당 약 8.2자**였습니다 — 283자가
34.72초 컷으로 돌아왔습니다. 대본을 쓰기 전에 분량을 가늠하는 용도로 쓰세요.
SLA가 아니라 기획용 숫자입니다.

run이 돌아옵니다:

```json
{
  "id": "arun_42ed6a48b3d44092",
  "thread_id": "thr_96012fce-d2e8-445b-bee0-d939e355db02",
  "status": "processing"
}
```

terminal까지 기다리세요 — [`status_url` / `result_url`](/formats/runs) 폴링이나
[run 웹훅](/agents/run-webhooks)(`format.run.terminal`)입니다. 웹훅은 씬마다가
아니라 턴마다 한 번입니다.

받은 뒤에는 운영자에게 보여주기 전에 **receipt가 보낸 대본을 실제로 담고 있는지**
확인하세요. `middle: []`와 `closing: []`도 스키마상 유효하기 때문에, 대본 일부만
읽은 실행이 오류 없이 `completed`로 돌아올 수 있습니다. 돌아온 행의 `script`
텍스트를 보낸 segments와 대조하는 것이 값싼 방어선입니다.

## 2. Receipt 예시

위 스니펫은 값을 채워 쓰는 템플릿입니다. 여기서부터는 전부 **실제 프로덕션
실행**을 그대로 옮긴 것입니다 — Format v5의 `arun_42ed6a48b3d44092`, 한국어
선크림 컷, 씬 11개, 73.36초, $14.96. 그래서 아래 `media.sume.com` URL은 지금
열리는 실제 파일입니다. 길이 때문에 씬 11개 중 3개만 남겼습니다(`sc_1`–`sc_3`,
`sc_5`, `sc_6`, `sc_8`–`sc_10` 생략). 스키마 세부:
[구조화 출력](/formats/structured-output).

```json
{
  "live_title": "AHC 아쿠아리치 선크림 듀오 라이브",
  "live_description": "SPF50+ PA++++ 선케어와 듀오세트 구성, 증정 혜택을 한 번에 소개합니다.",
  "full_video": {
    "type": "video",
    "url": "https://media.sume.com/artifacts/artf_ANhCBRoC8EHphQqGSniAPaVDqZYnYE_E/timeline.mp4",
    "content_type": "video/mp4",
    "file_name": null,
    "size_bytes": 12049030,
    "width": 720,
    "height": 1280,
    "duration_ms": 73360,
    "expires_at": null
  },
  "opening": [
    {
      "id": "sc_0",
      "role": "talk",
      "act": "Intro",
      "status": "succeeded",
      "script": "안녕하세요, AI 쇼호스트 유라입니다.",
      "tags": ["Intro"],
      "duration_seconds": 2.67,
      "video": {
        "type": "video",
        "url": "https://media.sume.com/artifacts/artf_l67fvKiIvfr2VGKnGCJ9oBP8UoJOHyAi/video.mp4",
        "content_type": "video/mp4",
        "file_name": null,
        "size_bytes": null,
        "width": 720,
        "height": 1280,
        "duration_ms": 2670,
        "expires_at": null
      }
    }
  ],
  "middle": [
    {
      "id": "sc_4",
      "role": "broll",
      "act": "Mid",
      "status": "succeeded",
      "script": "이번에는 듀오세트 두 개, 즉 삼십 밀리리터 용량 네개로 구성되어 있어요. 여기에 증정품까지 함께 준비되어 있는데요.",
      "tags": ["Mid"],
      "duration_seconds": 7.2,
      "video": {
        "type": "video",
        "url": "https://media.sume.com/artifacts/artf_ZVzP_Y02FpCdBIf2g-0Z6vLdpcfYaAw2/timeline.mp4",
        "content_type": "video/mp4",
        "file_name": null,
        "size_bytes": null,
        "width": 720,
        "height": 1280,
        "duration_ms": 7200,
        "expires_at": null
      }
    }
  ],
  "closing": [
    {
      "id": "sc_7",
      "role": "under_banner",
      "act": "Fin",
      "status": "succeeded",
      "script": "특히 유브이비뿐 아니라 롱 유브이에이, 숏 유브이에이까지 모두 차단하는 사중 유브이 에이징 케어가 적용됐다는 점이 눈에 띄는데요. …",
      "tags": ["Fin"],
      "duration_seconds": 17.76,
      "video": {
        "type": "video",
        "url": "https://media.sume.com/artifacts/artf_nlQMSVkbv8OlbOWfycZQmNj7LH3VAy7/timeline.mp4",
        "content_type": "video/mp4",
        "file_name": null,
        "size_bytes": null,
        "width": 720,
        "height": 1280,
        "duration_ms": 17760,
        "expires_at": null
      }
    }
  ],
  "schedule_images": []
}
```

“이 클립 다시 만들기”를 붙이려면 이 턴에서 네 가지를 저장해 두세요:

| 저장 | 어디서 | 쓰임 |
| --- | --- | --- |
| `run.id` | create 응답 | 재시도의 `previous_run_id`가 됩니다 |
| `thread_id` | create 응답 | 한 제작물의 모든 턴을 묶습니다 — **읽기 전용**, 요청 body에는 절대 넣지 않습니다 |
| `scene.id` | receipt의 각 행 | 재시도할 클립을 지목합니다 — 제작물 동안 고정이므로 배열 인덱스로 잡지 마세요 |
| `scene.status` | receipt의 각 행 | `stand-in` / `failed`이 재시도 버튼을 켜는 신호입니다 |

`scene.id`는 **불투명한 문자열**로 저장하세요. 이 실행은 `sc_0` … `sc_10`을
돌려줬지만, 이전 Format 버전의 제작물은 다른 모양을 돌려줬습니다. receipt가
주는 값을 그대로 저장하고, 파싱하거나 직접 만들어 쓰지 마세요.

이 실행은 모든 씬이 `succeeded`로 돌아왔습니다. 그래서 아래 재시도는 실패
복구가 아니라 운영자가 다른 테이크를 요청한 경우입니다. 다른 트리거는 재시도
대상으로 돌아온 행입니다 — 대시보드는 아래 둘 중 하나에서 “이 클립 다시
만들기”를 켭니다:

```json
{ "id": "sc_7", "status": "stand-in" }
{ "id": "sc_9", "status": "failed" }
```

status는 설명용 예시입니다 — 이 제작물은 11/11 `succeeded`였습니다.

## 3. Retry 예시

대시보드 루프: 운영자가 클립을 고름 → 백엔드는 이미 `previous_run_id`(create
run id)와 `scene_id`(receipt 행)를 갖고 있음. 같은 Format 엔드포인트에
`input.scene_id` + **create와 같은 `output_schema`**를 실어 보냅니다. typed
receipt 모양이 그대로 유지됩니다.

`thread_id`는 body에 **넣지 마세요.** 응답 전용 필드이며, 보내면
`unknown_parameter`로 거절됩니다. 대화를 잇는 키는 `previous_run_id`
하나뿐입니다.

남는 것은 `instruction`이고, 무엇이 돌아올지를 결정하는 필드가 바로 이것입니다.

### 씬별 구체 피드백을 넣으세요

운영자가 테이크가 마음에 들지 않아 “이 클립 다시 만들기”를 누른 경우라면, 그
이유를 물어보고 그 답을 `instruction`에 실어 보내세요 — **어느 씬인지, 무엇이
잘못됐는지, 어떻게 바꿔야 하는지.** 실제 피드백을 담은 재시도가 권장 형태이고,
아래의 고정 템플릿은 기본값이 아니라 대체 수단입니다.

instruction에 넣을 네 부분입니다. 제작물이 돌아가는 언어 그대로 쓰면 됩니다:

1. 씬 — `scene_id`와 role을 산문으로도 한 번 더 적어 지목을 분명히
2. 관찰된 결함 — 운영자가 이 테이크에서 실제로 본 것
3. 구체적인 수정 — 형용사가 아니라 지시로, 대신 무엇을 할지
4. 가드레일 — VO 스파인·대본 텍스트·인물·의상·세트 유지, 세로 9:16, 무BGM,
   무자막

아래 두 instruction 예시는 같은 레시피의 **다른 제작물**에서 그대로 가져온
것입니다. 위 receipt들과는 다른 스레드라 `previous_run_id`가 §2와 다릅니다.
receipt가 아니라 요청 body로 보세요.

첫 번째는 빈 공간에 제품만 떠 있는 패키지샷으로 돌아온 `broll` 씬입니다. 배너
규칙을 눈여겨보세요 — 화면 안 카드/배너 타이포는 프레임 높이 **상단 40%**
안에서 끝나야 합니다. 하단은 라이브 UI가 덮기 때문입니다.

<!-- api-call-example:format-mobidoo-lc-retry-scene-instructed -->

두 번째 `talk` 씬도 같은 방식입니다 — 같은 네 부분, 다른 결함. 인사 구간에서
호스트의 시선이 떨어지고 눈이 감기는 프레임이 있었던 경우입니다:

```json
{
  "previous_run_id": "arun_a045f6a01f5d4b20",
  "instruction": "지정한 씬 하나만 다시 만들어 주세요. 다른 씬과 VO 스파인, 대본 텍스트는 한 글자도 바꾸지 마세요.\n\n고칠 씬: sc_0 (talk / Intro). 대사는 \"안녕하세요, AI 쇼호스트 유라입니다.\" 그대로입니다.\n지금 무엇이 잘못됐는지: 인사 구간인데 호스트의 시선이 아래로 떨어지고 눈이 감기는 프레임이 있습니다.\n\n어떻게 바꿀지:\n- 호스트가 카메라를 더 정면으로 바라보게 해 주세요. 인사 내내 시선이 카메라에 머물러야 합니다.\n- 눈을 뜬 상태로 또렷하게, 표정은 조금 더 밝고 따뜻한 톤으로.\n- 대사 텍스트와 길이는 절대 바꾸지 마세요. 같은 문장, 같은 목소리입니다.\n- 동일 인물 유지. 헤어, 의상, 세트는 지금 그대로 두세요.\n- 세로 9:16, 무BGM, 무자막 유지.",
  "input": { "scene_id": "sc_0" },
  "output_schema": { "...": "create와 동일" },
  "primary_output_key": "full_video",
  "generation_spend_cap_usd": 3
}
```

왜 이렇게까지 하냐면: 고정 템플릿으로 재시도한 `talk` 씬이 운영자가 거절한
바로 그 테이크와 **바이트 단위로 동일하게** 돌아온 적이 있고, 같은 씬을 위와
같은 피드백과 함께 재시도했을 때는 다시 렌더링되어 시선이 고쳐졌습니다. API
보장이 아니라 연동 가이드로 받아들이세요 — 고정 템플릿 재시도가 항상 동일하게
돌아온다는 보장도, 구체 피드백이 항상 바뀐다는 보장도 없습니다. 통제할 수
있는 것은 instruction이 얼마나 많이 알려주는가입니다. §5에서 이런 재시도를 끝까지
측정했습니다 — 가격 카드에 지시한 여덟 가지 수정이 전부 반영됐고, 나머지 여섯
클립은 그대로였습니다.

### 기본 템플릿은 기계적 재시도용입니다

`stand-in` / `failed`로 돌아온 행에는 설명할 것이 없습니다 — 운영자가 거절할
테이크 자체를 본 적이 없으니까요. 이 경우에는 고정 템플릿이 맞고, 단순히 “한
번 더 돌려”인 경우에도 정직한 기본값입니다:

<!-- api-call-example:format-mobidoo-lc-retry-scene -->

새 run, 같은 thread:

```json
{
  "id": "arun_808e4eb3e6c04cf7",
  "thread_id": "thr_96012fce-d2e8-445b-bee0-d939e355db02",
  "previous_run_id": "arun_42ed6a48b3d44092",
  "status": "processing"
}
```

여러 클립을 한 번에 재시도하는 것도 같은 호출입니다 — `scene_ids`와 복수형
instruction만 바뀝니다:

```json
{
  "input": { "scene_ids": ["sc_7", "sc_9"] },
  "instruction": "Retry the selected scenes only. Keep every other scene and the VO spine unchanged."
}
```

클립 재시도는 씬이 *어떻게 보이는지*를 다시 렌더링합니다. *무엇을 말하는지* —
문구, 가격 낭독, 길이 — 를 바꾸는 것은 새 씬 id가 붙는 새 제작입니다.
[클립 재시도로 바꿀 수 있는 것과 없는 것](/enterprise/mobidoo/live-commerce#클립-재시도로-바꿀-수-있는-것과-없는-것)을 보세요.

### 버튼을 붙이기 전에 알아둘 두 가지

**한 스레드의 재시도는 직렬로.** 같은 `previous_run_id`로 재시도를 두 개
동시에 보내지 마세요 — 제작 스레드 하나에 진행 중인 턴 하나입니다. 첫 재시도가
terminal에 도달할 때까지 두 번째는 큐에 넣어 두세요. 한 턴에서 여러 씬을
고치는 용도로는 `scene_ids`가 있습니다.

**`previous_run_id`는 스레드를 고르는 것이지 스냅샷을 고르는 것이 아닙니다.**
예전 run id를 넣어도 그 시점 receipt에서 분기되지 않습니다. 새 receipt는
스레드의 **최신 씬 상태**로 조립되므로, 앞선 턴이 이미 바꾼 씬은 바뀐 채로
남습니다. 재시도를 비교할 때는 create가 아니라 **직전 턴**의 receipt와
비교하세요.

## 4. Retry receipt 예시

다시 전체 receipt입니다 — `opening` / `middle` / `closing` 목록 전체이며,
패치도 변경분도 아닙니다. `sc_7`만 새 미디어 URL을 갖고, 나머지 씬은 갖고
있던 URL을 **그대로** 유지하며 `full_video`가 다시 조립됩니다. 눈으로 비교할
수 있게 위와 같은 세 행을 실었습니다:

```json
{
  "live_title": "AHC 아쿠아리치 선크림 듀오 라이브",
  "live_description": "SPF50+ PA++++ 선케어와 듀오세트 구성, 증정 혜택을 한 번에 소개합니다.",
  "full_video": {
    "type": "video",
    "url": "https://media.sume.com/artifacts/artf_QhBuhWEgA9eWzGheRZ65mEGM2aJF9TXj/timeline.mp4",
    "content_type": "video/mp4",
    "file_name": null,
    "size_bytes": 12080322,
    "width": 720,
    "height": 1280,
    "duration_ms": 73360,
    "expires_at": null
  },
  "opening": [
    {
      "id": "sc_0",
      "role": "talk",
      "act": "Intro",
      "status": "succeeded",
      "script": "안녕하세요, AI 쇼호스트 유라입니다.",
      "tags": ["Intro"],
      "duration_seconds": 2.67,
      "video": {
        "type": "video",
        "url": "https://media.sume.com/artifacts/artf_l67fvKiIvfr2VGKnGCJ9oBP8UoJOHyAi/video.mp4",
        "content_type": "video/mp4",
        "file_name": null,
        "size_bytes": null,
        "width": 720,
        "height": 1280,
        "duration_ms": 2670,
        "expires_at": null
      }
    }
  ],
  "middle": [
    {
      "id": "sc_4",
      "role": "broll",
      "act": "Mid",
      "status": "succeeded",
      "script": "이번에는 듀오세트 두 개, 즉 삼십 밀리리터 용량 네개로 구성되어 있어요. 여기에 증정품까지 함께 준비되어 있는데요.",
      "tags": ["Mid"],
      "duration_seconds": 7.2,
      "video": {
        "type": "video",
        "url": "https://media.sume.com/artifacts/artf_ZVzP_Y02FpCdBIf2g-0Z6vLdpcfYaAw2/timeline.mp4",
        "content_type": "video/mp4",
        "file_name": null,
        "size_bytes": null,
        "width": 720,
        "height": 1280,
        "duration_ms": 7200,
        "expires_at": null
      }
    }
  ],
  "closing": [
    {
      "id": "sc_7",
      "role": "under_banner",
      "act": "Fin",
      "status": "succeeded",
      "script": "특히 유브이비뿐 아니라 롱 유브이에이, 숏 유브이에이까지 모두 차단하는 사중 유브이 에이징 케어가 적용됐다는 점이 눈에 띄는데요. …",
      "tags": ["Fin"],
      "duration_seconds": 17.76,
      "video": {
        "type": "video",
        "url": "https://media.sume.com/artifacts/artf_Hy18xtqPCFNhombVGGN4r7laaFkZ37_o/timeline.mp4",
        "content_type": "video/mp4",
        "file_name": null,
        "size_bytes": null,
        "width": 720,
        "height": 1280,
        "duration_ms": 17760,
        "expires_at": null
      }
    }
  ],
  "schedule_images": []
}
```

두 receipt가 실제로 증명한 것:

| | Create `arun_42ed6a48b3d44092` | Retry `arun_808e4eb3e6c04cf7` |
| --- | --- | --- |
| `thread_id` | `thr_96012fce-…` | 동일 |
| 돌아온 씬 행 수 | 11 | 11 — 패치가 아니라 전체 목록 |
| 나머지 씬 URL | — | 11개 중 10개가 바이트 단위로 동일 |
| `sc_7` URL | `artf_nlQMSVkb…` | 새 URL — `artf_Hy18xtqP…` |
| `sc_7` 길이 | 17.76초 | 17.76초 — VO 스파인 안 움직임 |
| `full_video` | 73.360초, 12,049,030 B | 재조립, 73.360초, 12,080,322 B |
| 청구액 | $14.959638 | **$0.742530** — 약 20분의 1 |
| 소요 시간 | 11분 08초 | 2분 53초 |

재시도 뒤에도 이전 `sc_7` URL은 여전히 `200`으로 열립니다. 이전 턴의
아티팩트는 변경되지 않으므로, 이미 저장해 둔 create receipt는 계속 유효합니다.
두 테이크를 모두 보관하고 운영자가 고르게 해도 됩니다.

**다시 만든 클립은 재인코딩이 아니라 새 생성입니다.** 프레임 안의 생성물은
테이크마다 달라질 수 있습니다. 위 사례에서도 구조화된 오버레이 사실(가격,
할인 표기, SPF 등급, 구성)은 정확히 유지됐지만, 제품 아트에 렌더링된 장식용
패키지 문구는 표현이 바뀌었습니다. 운영자에게 “한 군데만 고쳐진 똑같은 프레임”을
약속하지 마세요. 약속할 수 있는 것은 **다른 테이크**입니다.

## 5. 더 짧은 한 쌍, 측정값

§2–§4는 73초, 씬 11개짜리 제작물입니다. §1에 나온 34.72초 컷에서 같은 루프를 한
번 더 볼 만합니다. 한 씬만 고치는 재시도가 전체를 다시 만드는 것에 비해 얼마나
싼지 숫자로 나오기 때문입니다:

| | Create `arun_206837aa88d34c8d` | 지시형 retry `arun_0c315b373e24433a` |
| --- | --- | --- |
| thread | `thr_45c64858-…` | 동일 |
| `previous_run_id` | `null` | `arun_206837aa88d34c8d` |
| 보낸 대본 | 한국어 283자, `INTRO` / `MID` / `FIN` | — |
| 결과 | 34.72초, 씬 7개 전부 `succeeded` | 34.72초, 씬 7개 중 하나만 이동 |
| 청구액 | $7.0221 | **$0.7361** |
| 소요 시간 | 10분 08초 | **2분 24초** |

retry body는 §3의 모양 그대로이고 그 이상은 없습니다 — `previous_run_id`,
`input: { "scene_id": "sc_5" }`, **create와 동일한 `output_schema`**, 그리고
씬·결함·수정을 지목하는 `instruction`. `sc_5`는 클로징 가격 카드로, 제품명과
가격만 얹힌 `under_banner` 씬이었습니다. 줄여서 옮기면:

> 고칠 씬: sc_5 (under_banner / Fin, 클로징 가격 카드, 9.44초).
>
> 지금 무엇이 잘못됐는지: 카드에 제품명과 가격만 있고, 이 상품의 핵심 소구점인
> 자외선 차단 지수(SPF50+ PA++++)가 빠져 있습니다. … "30% 할인"이 카드에서 가장
> 크게 잡혀 있고, 정작 소비자가 결제하는 금액인 23,030원이 그보다 작게 보입니다.
> … 제품 이미지 위쪽에 빈 여백이 크게 남아 카드 상단이 비어 보입니다.
>
> 어떻게 바꿀지: 제품명 바로 아래에 "SPF50+ PA++++" 한 줄을 배지 형태로 추가해
> 주세요. … 판매가 23,030원을 카드에서 가장 큰 요소로 만들어 주세요. "30%
> 할인"은 그보다 작게, 보조 배지로 내려 주세요. 정가 32,900원은 취소선을 유지해
> 주세요. … 배경 톤, 카드 색감, 제품 사진 구도(박스 1개 + 튜브 2개)는 지금 그대로
> 두세요.

형용사가 아니라 편집 지시로 된 요구 여덟 개, 그리고 그 뒤에 **바꾸지 말 것**
목록. 여덟 개가 모두 반영됐고, `sc_5` 밖은 아무것도 움직이지 않았습니다:

| 행 | 변경 |
| --- | --- |
| `sc_0`–`sc_4`, `sc_6` | 없음 — 여섯 개 모두 아티팩트 URL 동일 |
| `sc_5` | 있음 — `artf_psot5zRM…/images.png` → `artf_GWQTB6n2…/timeline.mp4` |
| `full_video` | 있음 — 재조립, 34,720 ms 그대로, 바이트는 다름 |

**`full_video`가 아니라 씬별 URL을 비교하세요.** `full_video`는 턴마다 다시
조립되므로 클립이 실제로 다시 렌더링됐는지와 무관하게 URL이 바뀝니다. 그것만으로는
아무것도 증명하지 못합니다. 신호는 씬별 URL이고, `talk` 씬은 바이트 해시까지
비교하세요 — talk 재시도가 새 URL에 바이트 단위로 동일한 미디어를 돌려준 사례가
있습니다.

**`scene.video`가 항상 영상인 것은 아닙니다.** create receipt에서 `sc_5.video`는
PNG 스틸이었습니다 — `"type": "image"`, `"content_type": "image/png"`이고
`width`·`height`·`duration_ms`가 전부 `null`, 길이는 행의
`duration_seconds: 9.44`에만 있었습니다. 재시도 뒤 같은 씬 id는 MP4를 들고
있었습니다 — `"type": "video"`, 720×1280, `duration_ms: 9440`. 영상 엘리먼트가
재생할 수 있다고 가정하지 말고 행마다 `type`과 `content_type`을 읽고, 길이는
`duration_seconds`에서 가져오세요. 같은 씬 id에서도 턴이 바뀌면 타입이 바뀔 수
있습니다.

**한 씬만 고치는 재시도가 여기서는 재생성의 약 10분의 1이었습니다** — $7.02 대비
$0.74, 10분 08초 대비 2분 24초. 운영자가 클립 하나를 바꾸고 싶어 한다면 그 클립을
재시도하세요. 제작물을 다시 돌리는 것은 같은 수정을 비싸게 얻는 방법입니다.

루프는 여기까지입니다. 세부는
[라이브 커머스 API](/enterprise/mobidoo/live-commerce)와
[구조화 출력](/formats/structured-output)에 있습니다.
