Un utilisateur dit à Rowan, un personnage de compagnon adulte fictif : « montre-moi l'observatoire ce soir ». Le modèle de chat peut répondre dans le rôle du personnage, mais votre application doit créer l'image, garder la scène cohérente et joindre une vraie ressource dès qu'elle existe. Gardez le chat réactif pendant que les tâches d'image et de vidéo s'exécutent séparément.
Donnez à l'utilisateur le contrôle de deux achats :
- Valider l'image fixe : affichez une carte de scène gratuite, demandez un devis pour l'image et ne la créez qu'une fois que l'utilisateur a accepté le prix.
- Valider le clip : affichez l'image fixe terminée, demandez un devis pour son animation et ne créez la vidéo qu'une fois que l'utilisateur a accepté cette image et le prix.
Enregistrez chaque demande avant de l'envoyer, utilisez une clé distincte pour chaque étape et livrez depuis votre propre copie stockée. Un appel d'outil lance la carte de scène ; il n'autorise jamais un achat et n'annonce jamais une pièce jointe inexistante.
Pour un budget concret, une édition Qwen en 1k ($0.036) plus un clip Seedance 2.5 de cinq secondes en 720p reviennent à $1.4225, hors frais d'images de référence supplémentaires, jetons de chat et nouvelles tentatives. Demandez un devis (quote) avant d'afficher un prix à un utilisateur.
Le flux complet, dans l'ordre :
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 -> deliveredTransformer une demande de chat en image que l'app peut réellement produire
Un utilisateur peut écrire : « Envoie-moi une autre photo avec le manteau vert », alors qu'une vidéo en manteau bleu marine est déjà en cours de génération. La réponse du chat et la demande de média ne doivent pas partager une seule description modifiable. L'app doit savoir quel adulte fictif, quel manteau, quel décor, et si l'utilisateur veut une image fixe ou un clip. Sinon, « une autre » peut créer par erreur un nouveau visage, réutiliser une scène périmée ou acheter une vidéo alors que l'utilisateur demandait une photo.
Gardez les données permanentes du personnage réduites : le portrait validé, la majorité, des notes d'apparence stables et le personnage choisi par l'utilisateur. Gardez la scène actuelle à part : manteau, pièce, vue de caméra et action demandée. La conversation peut suggérer une scène, mais votre application résout le personnage stocké et les ressources sources validées. Ne laissez pas le modèle de chat inventer une URL de référence ou un ID de tâche, ni affirmer qu'une image existe.
Pour l'exemple de l'observatoire, transformez la demande en description de scène explicite avant le devis : Rowan, le portrait validé, manteau bleu marine, balcon de l'observatoire, plan moyen, une image fixe. Pour une demande ultérieure du type « anime-la », utilisez l'image fixe acceptée de l'observatoire et un petit mouvement de tête. N'envoyez pas toute la conversation au modèle d'image : des descriptions sans rapport d'autres personnages ou de tenues précédentes peuvent entrer en conflit avec la demande actuelle.
L'interface peut afficher un résumé compact de la scène pendant la génération de l'image. Si un utilisateur remplace le vert par le bleu marine, créez une nouvelle révision et étiquetez en conséquence le résultat en attente précédent. Une ancienne révision terminée peut rester dans son historique privé, mais ne doit pas apparaître silencieusement comme la réponse à la dernière demande.
Pour un produit de compagnon NSFW, vérifiez séparément l'adéquation de l'image et de la vidéo. Qu'une tâche d'image accepte du contenu pour adultes ne prouve pas que le modèle vidéo suivant accepte la même scène. Consultez la page du modèle choisi pour chaque étape et présentez honnêtement une tâche indisponible plutôt que de promettre un clip pour adultes que le modèle choisi ne prend pas en charge.
Essayer d'abord dans le Playground
Ouvrez le Playground Qwen Image 2.1 edit. Sous Images, choisissez Add a file et importez le portrait validé du personnage, décrivez la nouvelle scène dans Prompt (le prompt ci-dessous convient), réglez Resolution sur 1k et Aspect ratio sur 16:9, appuyez sur Run model et jugez l'image fixe. Validez cette composition, puis ouvrez le Playground Seedance 2.5 image-to-video : ajoutez l'image fixe comme Input image, décrivez le mouvement dans Prompt et appuyez de nouveau sur Run model. Closing frame est facultatif.
Laisser le chat demander des médias par appel d'outil
Donnez au modèle de chat un outil request_scene_image qui demande à votre application de préparer une carte de scène. Utilisez l'utilisateur connecté et le portrait validé du personnage issus de votre application, et non des ID inventés dans la réponse du chat.
- Le modèle de chat : décrit la scène et demande si l'utilisateur souhaite qu'elle soit créée.
- Votre application : vérifie la propriété du personnage, affiche le prix et attend la validation.
- La tâche média : ne crée l'image qu'après validation ; l'animation est un achat distinct.
Grok 4.6 chat prend en charge les appels d'outils au format OpenAI. L'API ne garde aucune mémoire de la conversation : renvoyez donc la fiche du personnage et le contexte d'écriture de fiction à chaque requête ; les recettes de chat montrent le format du message d'outil.
{
"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"]
}
}
}Répondez à l'appel par un message tool indiquant qu'une carte brouillon attend la validation, afin que le modèle écrive une phrase honnête (« Voici la carte de scène, je la crée ? ») au lieu d'affirmer qu'une livraison a eu lieu.
Séparer le personnage et la scène dans des enregistrements distincts
Gardez le portrait validé à part de la scène demandée : un manteau bleu marine, un balcon d'observatoire et un petit mouvement vers un télescope. Le portrait ancre le personnage ; l'image de scène acceptée devient la première image de la vidéo. Ne remplacez pas le portrait du personnage à chaque nouvelle scène.
Enregistrez une entrée par demande avant de l'envoyer (ce sont des champs de votre application, pas une charge utile d'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
}Si l'utilisateur change le manteau en vert, créez la révision 2 avec une nouvelle clé ; ne modifiez jamais la demande du manteau bleu pendant son exécution.
Stockez vous-même l'image fixe validée. Le lien de résultat d'une tâche dure environ 20 minutes et le fichier généré environ 14 jours : copiez donc l'image fixe acceptée dans votre propre stockage et donnez à Seedance une URL HTTPS publique stable qui renvoie le fichier image lui-même (un lien vers une page de partage échoue). Transmettre directement le lien de résultat Qwen à l'étape vidéo ne fonctionne que jusqu'à son expiration. Réutilisez l'image fixe stockée pour les révisions de mouvement ; modifier à la fois le portrait et le mouvement rend plus difficile la correction de l'un comme de l'autre.
Régler séparément les contrôles d'image et de vidéo
La route Qwen Image 2.1 edit accepte un prompt et de une à dix image_urls. Les images de référence supplémentaires augmentent le prix en 1k ; la 2k coûte $0.048 avec une à dix images de référence. Pour la première scène, utilisez un portrait validé, 1k, 16:9, PNG et l'expansion de prompt désactivée.
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.
Comparez le visage et la tenue avec le portrait validé avant de proposer l'image fixe à l'utilisateur. À la validation 2, l'utilisateur accepte cette image fixe et le prix du clip avant l'animation. Si le télescope flotte ou si le côté droit ne laisse pas de place au mouvement, corrigez l'étape d'image ; la vidéo ne peut pas réparer une mauvaise première image.

Illustration d'une première image proposée : le personnage a de la place pour un petit mouvement et le télescope est physiquement soutenu. Ce n'est ni une sortie Qwen ni un test de génération.
La route Seedance 2.5 image-to-video prend image_url, de 4 à 30 secondes, 480p, 720p ou 1080p, un bouton audio et un seed facultatif. aspect_ratio n'accepte que adaptive : le clip suit donc la forme de l'image fixe ; recadrez d'abord l'image fixe au format de livraison. La route reference-to-video a un contrat différent : n'y mélangez pas ses champs.
Demander un devis au moment de la décision
Les éditions Qwen sont affichées à $0.036 en 1k, $0.048 en 2k et $0.072 en 1.5k, avec une image de référence incluse. Les images de référence supplémentaires augmentent les prix en 1k et 1.5k. Seedance coûte $0.1234 par seconde en 480p, $0.2773 en 720p et $0.6823 en 1080p, avec ou sans audio au même tarif.
Réutilisez l'image fixe acceptée pour retenter le mouvement.
| Scénario | Coût |
|---|---|
| Image fixe 1k seule | $0.036 |
| Image fixe + clip de 5 s en 480p | $0.653 |
| Image fixe + clip de 5 s en 720p | $1.4225 |
| Image fixe + clip de 5 s en 1080p | $3.4475 |
| Image fixe + deux essais de clip en 720p | $2.809 |
Ces montants incluent une référence dans l'édition 1k et excluent les frais d'images de référence supplémentaires, les jetons de chat et toute édition d'image fixe supplémentaire.
Affichez le prix de l'image fixe avant la génération et le prix du clip une fois que l'utilisateur a accepté l'image fixe, pas à chaque tour de chat. Un devis est gratuit, ne réserve rien et reste valable cinq minutes pour ce compte, cette clé d'API, ce modèle et cette entrée ; si vous changez la durée, la résolution ou les références, il vous en faut un nouveau. Une tâche soumise retient immédiatement son coût estimé, et cette retenue est le maximum qui peut être facturé. Réutiliser l'image validée limite une révision de mouvement au coût de la vidéo.
Un client pour l'étape vidéo qui résiste à une réponse perdue
Installez spicyapi avec python -m pip install spicyapi, définissez SPICY_API_KEY et APPROVED_STILL_URL, puis exécutez ceci après avoir validé l'image fixe. Conservez le fichier d'exécution enregistré pour reprendre la même tentative. Pour une autre prise créative, utilisez un nouveau nom d'exécution.
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"])Le SDK Python et la documentation des tâches couvrent l'intégration en production.
Une seule fonction de mise à jour pour le polling et les callbacks
Utilisez un seul gestionnaire d'application pour un résultat obtenu par polling comme pour un événement de fin vérifié. Il doit joindre la ressource une seule fois, la garder privée et ne prévenir le chat que lorsqu'elle est prête pour la revue. Un événement en double ne doit pas créer une autre entrée de galerie.
Pour une app de compagnons en production, envoyez la fin de tâche vers un webhook vérifié et gardez un balayage périodique de l'état pour les callbacks qui n'arrivent jamais. Les corps de callback contiennent le prompt et des liens de résultat signés : traitez-les donc comme privés ; la documentation des tâches explique la configuration. Les webhooks avec un balayage de réconciliation (voir l'article « async-task-api-polling-vs-webhook ») conviennent à la production ; le polling suffit pour un prototype. Utilisez un envoi borné lorsque de nombreux utilisateurs demandent des médias en même temps.
États de livraison, échecs et annulation
Utilisez draft, submitted, generating, ready_for_review, delivered, failed et rejected. Le succeeded de l'API signifie que le traitement du résultat commence ; cela ne veut pas dire que l'utilisateur le voit. Examinez le clip entier pour détecter une dérive du visage, des accessoires qui bougent et des coupes non demandées avant de passer à delivered.
| Résultat de l'API | Quoi afficher | Facturation |
|---|---|---|
succeeded, ressource prête | revoir, puis livrer | lire settled et cost |
succeeded, ressource pending | « toujours en préparation », réinterroger dans quelques secondes | pas encore définitif |
succeeded, ressource unavailable | restaurer une copie déjà enregistrée, ou proposer une nouvelle génération délibérée | lire cost |
failed ou expired | un échec clair et un bouton de nouvelle tentative | retenue normalement libérée ; certains refus de contenu sont facturés lorsque la page du modèle l'indique |
Un devis expiré demande un nouveau devis et une nouvelle validation ; récupérez une demande déjà acceptée avec sa clé d'origine. Une tâche acceptée ne peut pas être annulée. Si l'utilisateur dit « laisse tomber », masquez la pièce jointe en attente et laissez la tâche se terminer ; ne promettez pas que fermer le chat libère la facturation. Une nouvelle tentative est une nouvelle demande avec son propre devis.
Garder privés les médias privés du compagnon
Vérifiez qu'une scène appartient à l'utilisateur connecté avant de la servir. Copiez les médias acceptés vers un stockage avec vos propres règles d'accès ; les liens de résultat temporaires ne doivent pas devenir des adresses publiques de galerie.
Pour la suppression, traitez séparément la copie de la plateforme et la vôtre. Raccourcissez la durée pendant laquelle la plateforme conserve un résultat avec le réglage de rétention par tâche et, lorsqu'un utilisateur supprime une scène ou son compte, détruisez le contenu stocké de chaque tâche terminée (l'enregistrement de facturation demeure). Une rétention de 0 peut supprimer le résultat avant que votre worker ne l'ait copié. Supprimez votre propre copie et révoquez séparément son accès de livraison ; les détails figurent dans la documentation sur la rétention. Utilisez des contrôles de rétention et un pipeline de stockage durable adaptés au produit.
Tester quatre échecs avec des fixtures
Simulez un double clic, une réponse d'acceptation perdue, un événement de succès en double et une ressource indisponible. Vous devez obtenir une seule demande logique, une récupération avec la clé d'origine, une seule pièce jointe en galerie et un état « indisponible » honnête. Aucun de ces tests ne nécessite une génération payante. La cohérence du visage entre images fixes et clips est une question visuelle : inspectez donc chaque sortie. Ne laissez pas un message de chat disant « votre vidéo est prête » prévaloir sur l'état réel de la tâche ou de la ressource. L'illustration de l'observatoire est une scène éditoriale, pas une sortie de Qwen ou de Seedance.
Laisser un Agent se charger de l'appel
Utilisez un Agent lorsque vous voulez qu'il prépare les entrées et renvoie le résultat pendant que vous validez le prix et les choix créatifs.
- CLI : avec un
input.jsontel que{"image_urls": ["<approved portrait URL>"], "prompt": "<the scene prompt above>", "resolution": "1k", "aspect_ratio": "16:9", "output_format": "png", "enable_prompt_expansion": false}, lanceznpx @spicyapi/cli tasks create --model alibaba/qwen-image-2.1/edit --input-file input.json --wait. La CLI confirme le prix ; conservez l'ID de tâche renvoyé pour la récupération. - MCP : demandez à votre assistant d'utiliser
spicyapi_task_createpouralibaba/qwen-image-2.1/edit, puisspicyapi_task_wait. Le serveur MCP demande une confirmation du prix avant la création. - Skill : installez-le avec
npx skills add spicyapi-ai/spicy-skill, puis demandez à$spicyapid'exécuter le même flux et de s'arrêter pour obtenir votre validation. Le Skill utilise une connexion CLI ou MCP déjà configurée.








