Пользователь пишет Роуэну, вымышленному взрослому персонажу-компаньону: «Покажи мне обсерваторию сегодня вечером». Чат-модель может ответить в образе, но ваше приложение должно создать картинку, сохранить согласованность сцены и прикрепить реальный ассет, когда он появится. Не блокируйте чат, пока задания на изображение и видео выполняются отдельно.
Дайте пользователю контроль над двумя покупками:
- Утвердить стоп-кадр: покажите бесплатную карточку сцены, запросите цену изображения и создавайте его только после того, как пользователь примет цену.
- Утвердить клип: покажите готовый стоп-кадр, запросите цену его анимации и создавайте видео только после того, как пользователь примет это изображение и цену.
Сохраняйте каждый запрос до отправки, используйте отдельный ключ для каждого этапа и отдавайте контент из собственной сохранённой копии. Вызов инструмента запускает карточку сцены; он никогда не разрешает покупку и не сообщает о вложении, которого нет.
Для конкретного бюджета: правка Qwen в 1k ($0.036) и пятисекундный клип Seedance 2.5 в 720p в сумме составляют $1.4225 без учёта платы за дополнительные референсные изображения, токенов чата и повторных попыток. Запросите расчёт цены (quote), прежде чем показывать пользователю любую цену.
Весь процесс по порядку:
1. Chat model calls request_scene_image(scene, details)
2. Server validates owner and scene -> free draft card -> tool result back to the chat
3. Gate 1: user approves the still's price -> Qwen edit task (own idempotency key)
4. Callback or poll -> still ready -> copy it to your own storage
5. Gate 2: user accepts the still -> quote the clip -> user approves its price
6. Seedance task (own key) -> callback or poll -> ready_for_review
7. Review the whole clip -> copy to your storage -> deliveredПревратите запрос из чата в картинку, которую приложение действительно сможет создать
Пользователь может написать: «Пришли ещё одно фото в зелёном пальто», пока уже генерируется видео в тёмно-синем пальто. Ответ чата и запрос на медиа не должны делить одно изменяемое описание. Приложению нужно знать, какой вымышленный взрослый, какое пальто, какая обстановка и нужен ли пользователю стоп-кадр или клип. Иначе «ещё одно» может случайно создать новое лицо, использовать устаревшую сцену или купить видео, когда пользователь просил фото.
Держите постоянные данные персонажа небольшими: утверждённый портрет, совершеннолетие, стабильные заметки о внешности и выбранный пользователем персонаж. Текущую сцену храните отдельно: пальто, комната, ракурс камеры и запрошенное действие. Разговор может предложить сцену, но сохранённого персонажа и утверждённые исходные ассеты определяет ваше приложение. Не позволяйте чат-модели выдумывать URL референса или ID задачи и утверждать, что изображение существует.
В примере с обсерваторией превратите запрос в явное описание сцены до расчёта цены: Роуэн, утверждённый портрет, тёмно-синее пальто, балкон обсерватории, средний план, один стоп-кадр. Для более позднего запроса «оживи это» используйте принятый стоп-кадр обсерватории и небольшой поворот головы. Не отправляйте модели изображений весь разговор: не относящиеся к делу описания других персонажей или прежних нарядов могут конфликтовать с текущим запросом.
Интерфейс может показывать краткую сводку сцены, пока изображение генерируется. Если пользователь меняет зелёное на тёмно-синее, сделайте это новой ревизией и пометьте предыдущий ожидающий результат соответствующим образом. Готовая старая ревизия может остаться в его личной истории, но не должна молча появляться как ответ на последний запрос.
Для NSFW-продукта с компаньоном проверяйте пригодность изображения и видео по отдельности. То, что задача на изображение принимает контент для взрослых, не означает, что следующая видеомодель примет ту же сцену. Для каждого этапа смотрите страницу выбранной модели и честно показывайте недоступную задачу, вместо того чтобы обещать клип для взрослых, который выбранная модель не поддерживает.
Сначала попробуйте в Playground
Откройте Playground Qwen Image 2.1 edit. В разделе Images выберите Add a file и загрузите утверждённый портрет персонажа, опишите новую сцену в Prompt (подойдёт промпт ниже), задайте Resolution 1k и Aspect ratio 16:9, нажмите Run model и оцените стоп-кадр. Утвердите эту композицию, затем откройте Playground Seedance 2.5 image-to-video: добавьте стоп-кадр как Input image, опишите движение в Prompt и снова нажмите Run model. Closing frame необязателен.
Пусть чат запрашивает медиа вызовом инструмента
Дайте чат-модели инструмент request_scene_image, который просит ваше приложение подготовить карточку сцены. Берите вошедшего пользователя и утверждённый портрет персонажа из вашего приложения, а не ID, выдуманные в ответе чата.
- Чат-модель: описывает сцену и спрашивает, хочет ли пользователь её создать.
- Ваше приложение: проверяет принадлежность персонажа, показывает цену и ждёт утверждения.
- Медиазадача: создаёт изображение только после утверждения; анимация — отдельная покупка.
Grok 4.6 chat поддерживает вызовы инструментов в формате OpenAI. API не хранит память разговора, поэтому с каждым запросом заново отправляйте карточку персонажа и контекст художественного письма; рецепты для чата показывают формат сообщения инструмента.
{
"type": "function",
"function": {
"name": "request_scene_image",
"description": "Ask the app to prepare a picture of the character. Call only when the user asks to see something. The app shows a free scene card first; nothing is made or charged until the user approves a quoted price.",
"parameters": {
"type": "object",
"properties": {
"scene": {"type": "string", "enum": ["observatory-balcony", "kitchen-morning", "rooftop-dusk"]},
"details": {"type": "string", "description": "Clothing, props and mood the user asked for, in one sentence."}
},
"required": ["scene"]
}
}
}Ответьте на вызов сообщением tool о том, что черновая карточка ждёт утверждения, — тогда модель напишет честную реплику («Вот карточка сцены, создать?»), а не заявит о доставке.
Храните персонажа и сцену в отдельных записях
Храните утверждённый портрет отдельно от запрошенной сцены: тёмно-синее пальто, балкон обсерватории и небольшой поворот к телескопу. Портрет закрепляет персонажа; принятое изображение сцены становится первым кадром видео. Не заменяйте портрет персонажа с каждой новой сценой.
Сохраняйте по одной записи на запрос до его отправки (это поля вашего приложения, а не полезная нагрузка API):
{
"mediaRequestId": "scene-104-r1",
"ownerId": "application-user-id",
"sceneRevision": 1,
"referenceAssetId": "approved-rowan-portrait",
"approvedStillAssetId": null,
"model": "alibaba/qwen-image-2.1/edit",
"inputSnapshot": {},
"idempotencyKey": "<uuid generated for this request>",
"taskId": null,
"state": "draft",
"deliveredAssetId": null
}Если пользователь меняет пальто на зелёное, создайте ревизию 2 с новым ключом; никогда не редактируйте запрос с синим пальто, пока он выполняется.
Сохраняйте утверждённый стоп-кадр у себя. Ссылка на результат задачи живёт около 20 минут, а сгенерированный файл — около 14 дней, поэтому скопируйте принятый стоп-кадр в собственное хранилище и передайте Seedance стабильный публичный HTTPS-URL, который отдаёт сам файл изображения (ссылка на страницу для обмена не сработает). Если передать ссылку на результат Qwen прямо на этап видео, это работает лишь до её истечения. Для ревизий движения используйте сохранённый стоп-кадр; если менять портрет и движение одновременно, труднее исправить что-то одно.
Настройте управление изображением и видео по отдельности
Маршрут Qwen Image 2.1 edit принимает промпт и от одного до десяти image_urls. Дополнительные референсные изображения повышают цену для 1k; 2k стоит $0.048 при количестве референсных изображений от одного до десяти. Для первой сцены используйте один утверждённый портрет, 1k, 16:9, PNG и выключенное расширение промпта.
Use the supplied portrait as the identity reference for the same fictional adult Rowan. Create a medium shot on an observatory balcony, wearing a navy coat. Keep both hands relaxed and visible. A telescope rests on its tripod beside Rowan. Leave open space on the right for a small head turn. Warm doorway light, natural night background, no text or extra people.
Сравните лицо и наряд с утверждённым портретом, прежде чем предлагать стоп-кадр пользователю. На втором шаге утверждения пользователь принимает этот стоп-кадр и цену клипа до анимации. Если телескоп парит в воздухе или справа нет места для движения, исправляйте это на этапе изображения; видео не исправит плохой первый кадр.

Иллюстрация предлагаемого первого кадра: у персонажа есть место для небольшого поворота, а телескоп физически опирается на штатив. Это не результат Qwen и не тест генерации.
Маршрут Seedance 2.5 image-to-video принимает image_url, от 4 до 30 секунд, 480p, 720p или 1080p, переключатель звука и необязательный seed. aspect_ratio принимает только adaptive, поэтому клип повторяет форму стоп-кадра; сначала обрежьте стоп-кадр под нужный формат доставки. У маршрута reference-to-video другой контракт, не смешивайте его поля.
Запрашивайте цену в момент решения
Правки Qwen стоят $0.036 для 1k, $0.048 для 2k и $0.072 для 1.5k с одним референсным изображением в комплекте. Дополнительные референсные изображения повышают цены для 1k и 1.5k. Seedance стоит $0.1234 за секунду в 480p, $0.2773 в 720p и $0.6823 в 1080p, со звуком или без него по одной ставке.
При повторной попытке движения используйте принятый стоп-кадр.
| План | Стоимость |
|---|---|
| Только стоп-кадр 1k | $0.036 |
| Стоп-кадр + клип 5 с в 480p | $0.653 |
| Стоп-кадр + клип 5 с в 720p | $1.4225 |
| Стоп-кадр + клип 5 с в 1080p | $3.4475 |
| Стоп-кадр + две попытки клипа в 720p | $2.809 |
В эти суммы входит одно референсное изображение в правке 1k; не входят плата за дополнительные референсные изображения, токены чата и любые дополнительные правки стоп-кадра.
Показывайте цену стоп-кадра до генерации, а цену клипа — после того, как пользователь принял стоп-кадр, а не в каждой реплике чата. Расчёт цены бесплатен, ничего не резервирует и действует пять минут для этого аккаунта, API-ключа, модели и входных данных; если вы измените длительность, разрешение или референсы, понадобится новый. Отправленная задача сразу удерживает расчётную стоимость, и это удержание — максимум, который может быть списан. Повторное использование утверждённого изображения ограничивает ревизию движения стоимостью видео.
Клиент для этапа видео, переживающий потерянный ответ
Установите spicyapi командой python -m pip install spicyapi, задайте SPICY_API_KEY и APPROVED_STILL_URL и запустите это после утверждения стоп-кадра. Сохраняйте файл запуска, чтобы продолжить ту же попытку. Для другого творческого дубля используйте новое имя запуска.
import json
import os
import time
import uuid
from pathlib import Path
from spicyapi import SpicyClient, output_assets
client = SpicyClient()
def run(model, inputs, name):
path = Path(name + ".json")
request = {"model": model, "input": inputs}
saved = json.loads(path.read_text()) if path.exists() else {"request": request, "key": str(uuid.uuid4())}
if saved["request"] != request:
raise ValueError("Changed input needs a new run name; preserve the old attempt.")
def save():
temporary = path.with_suffix(".tmp")
temporary.write_text(json.dumps(saved))
temporary.chmod(0o600)
temporary.replace(path)
if not saved.get("taskId"):
if time.time() - saved.get("sentAt", time.time()) >= 23 * 3600:
raise RuntimeError("Reconcile the saved attempt before submitting again.")
if "quote" not in saved:
saved["quote"] = client.quote_task(model=model, input_data=inputs)
if input(f"Maximum ${saved['quote']['maxCharge']}. Create? [y/N] ") != "y":
raise SystemExit("Stopped without creating this task.")
saved.setdefault("sentAt", time.time())
save()
quote = saved["quote"]
task = client.create_task(model=model, input_data=inputs,
idempotency_key=saved["key"], quote_id=quote["quoteId"], expected_cost=quote["estimatedCost"])
saved["taskId"] = task["taskId"]
save()
print("Task:", saved["taskId"])
for _ in range(360):
task = client.get_task(saved["taskId"])
if task["state"] in {"failed", "expired", "canceled"}:
raise RuntimeError(str(task.get("errorMessage", task["state"])))
if task["state"] == "succeeded":
assets = output_assets(task)
if assets and assets[0].get("unavailable"):
raise RuntimeError("Output unavailable; keep the task ID for reconciliation.")
if assets and assets[0].get("url") and not assets[0].get("pending"):
return assets[0]
time.sleep(5)
raise TimeoutError("Rerun the same saved attempt to resume; no new task is needed.")
asset = run("bytedance/seedance-2.5/image-to-video", {
"image_url": os.environ["APPROVED_STILL_URL"],
"prompt": "The adult character slowly turns toward the telescope. Hold the camera steady. Keep the navy coat and balcony unchanged.",
"duration_seconds": 5, "resolution": "720p", "aspect_ratio": "adaptive", "generate_audio": True,
}, "companion-video")
print(asset["url"])Python SDK и документация по задачам описывают интеграцию для продакшена.
Одна функция обновления для опроса и callback-вызовов
Используйте один обработчик приложения и для результата, полученного опросом, и для проверенного события завершения. Он должен прикрепить ассет один раз, держать его приватным и уведомлять чат только тогда, когда ассет готов к проверке. Повторное событие не должно создавать ещё одну запись в галерее.
Для работающего приложения-компаньона направляйте уведомление о завершении на проверяемый webhook и сохраняйте периодическую проверку статуса для callback-вызовов, которые так и не пришли. Тела callback-вызовов содержат промпт и подписанные ссылки на результат, поэтому считайте их приватными; документация по задачам описывает настройку. Webhook с проверочным опросом (см. статью «async-task-api-polling-vs-webhook») подходят для продакшена; для прототипа достаточно опроса. Используйте ограниченную отправку, когда много пользователей запрашивают медиа одновременно.
Состояния доставки, сбои и отмена
Используйте draft, submitted, generating, ready_for_review, delivered, failed и rejected. succeeded в API означает, что начинается обработка результата; это не значит, что пользователь его видит. Проверьте клип целиком на дрейф лица, движущиеся предметы и незапрошенные склейки, прежде чем переводить его в delivered.
| Исход API | Что показать | Списание |
|---|---|---|
succeeded, ассет готов | проверить, затем доставить | прочитать settled и cost |
succeeded, ассет pending | «ещё готовится», повторить запрос через несколько секунд | ещё не окончательно |
succeeded, ассет unavailable | восстановить уже сохранённую копию или предложить осознанную новую генерацию | прочитать cost |
failed или expired | понятное сообщение о сбое и кнопка повтора | удержание обычно снимается; некоторые отказы по содержанию тарифицируются, если это указано на странице модели |
Для просроченного расчёта цены нужен новый расчёт и новое утверждение; уже принятый запрос восстанавливайте по его исходному ключу. Принятую задачу отменить нельзя. Если пользователь говорит «забудь», скройте ожидающее вложение и дайте заданию завершиться; не обещайте, что закрытие чата снимет списание. Повторная попытка — это новый запрос со своим расчётом цены.
Храните приватные медиа компаньона приватными
Перед выдачей проверяйте, что сцена принадлежит вошедшему пользователю. Копируйте принятые медиа в хранилище с вашими правилами доступа; временные ссылки на результат не должны становиться публичными адресами галереи.
При удалении обрабатывайте копию платформы и вашу копию по отдельности. Сокращайте срок хранения результата на платформе настройкой хранения для каждой задачи, а когда пользователь удаляет сцену или аккаунт, уничтожайте сохранённое содержимое каждой завершённой задачи (запись о тарификации остаётся). Хранение 0 может удалить результат раньше, чем ваш воркер его скопирует. Удалите собственную копию и отдельно отзовите доступ к её выдаче; подробности есть в документации по хранению. Используйте управление хранением и конвейер долговременного хранения, подходящие вашему продукту.
Проверьте четыре сбоя на фикстурах
Смоделируйте двойной щелчок, потерянный ответ о принятии, повторное событие об успехе и недоступный ассет. Вы должны получить один логический запрос, восстановление по исходному ключу, одно вложение в галерее и честное состояние «недоступно». Ни одна из этих проверок не требует платной генерации. Согласованность лица в стоп-кадрах и клипах — вопрос визуальный, поэтому проверяйте каждый результат глазами. Не позволяйте сообщению в чате «ваше видео готово» перекрывать реальное состояние задачи или ассета. Иллюстрация с обсерваторией — редакционная сцена, а не результат Qwen или Seedance.
Поручите вызов Agent
Используйте Agent, когда хотите, чтобы он подготовил входные данные и вернул результат, пока вы утверждаете цену и творческие решения.
- CLI: с файлом
input.jsonвроде{"image_urls": ["<approved portrait URL>"], "prompt": "<the scene prompt above>", "resolution": "1k", "aspect_ratio": "16:9", "output_format": "png", "enable_prompt_expansion": false}выполнитеnpx @spicyapi/cli tasks create --model alibaba/qwen-image-2.1/edit --input-file input.json --wait. CLI подтверждает цену; сохраните возвращённый ID задачи для восстановления. - MCP: попросите ассистента использовать
spicyapi_task_createдляalibaba/qwen-image-2.1/edit, а затемspicyapi_task_wait. Сервер MCP запрашивает подтверждение цены перед созданием. - Skill: установите командой
npx skills add spicyapi-ai/spicy-skill, затем попросите$spicyapiвыполнить тот же процесс и остановиться для вашего утверждения. Skill использует настроенное подключение CLI или MCP.








