Vai al contenuto principale

Integra la consegna di immagini e video in un'app di compagni AI

Crea immagini e clip approvati per un compagno: fissa la scena, chiedi preventivi, recupera i job asincroni, consegna risorse revisionate senza doppioni.

Autore
Lydia
Pubblicato
Lettura
14 min di lettura
Aggiornato
Un uomo adulto si china verso l'oculare di un telescopio mentre accanto a lui una donna con un cappotto rosso sorride vicino a una porta illuminata dalla luce calda.

Un utente dice a Rowan, un personaggio compagno adulto di fantasia: «mostrami l'osservatorio stasera». Il modello di chat può rispondere in personaggio, ma la tua applicazione deve creare l'immagine, mantenere coerente la scena e allegare una risorsa vera quando esiste. Mantieni la chat reattiva mentre i job di immagine e video girano separatamente.

Dai all'utente il controllo di due acquisti:

  • Approvare l'immagine fissa: mostra una scheda scena gratuita, chiedi un preventivo per l'immagine e creala solo dopo che l'utente ha accettato il prezzo.
  • Approvare la clip: mostra l'immagine fissa finita, chiedi un preventivo per la sua animazione e crea il video solo dopo che l'utente ha accettato quell'immagine e il prezzo.

Salva ogni richiesta prima dell'invio, usa una chiave separata per ogni fase e consegna dalla tua copia archiviata. Una chiamata a uno strumento avvia la scheda scena; non autorizza mai un acquisto e non annuncia mai un allegato inesistente.

Come budget concreto, una modifica Qwen in 1k ($0.036) più una clip Seedance 2.5 da cinque secondi in 720p arrivano a $1.4225, prima degli addebiti per immagini di riferimento aggiuntive, dei token di chat e dei nuovi tentativi. Chiedi un preventivo (quote) prima di mostrare un prezzo a un utente.

Il flusso completo, in ordine:

text
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

Trasforma una richiesta in chat in un'immagine che l'app può davvero creare

Un utente può scrivere: «Mandami un'altra foto con il cappotto verde», mentre è già in generazione un video con il cappotto blu navy. La risposta della chat e la richiesta di media non dovrebbero condividere un'unica descrizione modificabile. L'app deve sapere quale adulto di fantasia, quale cappotto, quale ambientazione e se l'utente vuole un'immagine fissa o una clip. Altrimenti «un'altra» può creare per errore un nuovo volto, riusare una scena superata o comprare un video quando l'utente chiedeva una foto.

Tieni piccoli i dati permanenti del personaggio: il ritratto approvato, la maggiore età, note stabili sull'aspetto e il personaggio scelto dall'utente. Tieni separata la scena corrente: cappotto, stanza, inquadratura e azione richiesta. La conversazione può suggerire una scena, ma è la tua applicazione a risolvere il personaggio salvato e le risorse sorgente approvate. Non lasciare che il modello di chat inventi un URL di riferimento o un ID task, né che dichiari che un'immagine esiste.

Per l'esempio dell'osservatorio, trasforma la richiesta in una descrizione esplicita della scena prima del preventivo: Rowan, il ritratto approvato, cappotto blu navy, balcone dell'osservatorio, piano medio, un'immagine fissa. Per una successiva richiesta «fallo muovere», usa l'immagine fissa accettata dell'osservatorio e una piccola rotazione della testa. Non inviare l'intera conversazione al modello di immagine: descrizioni non correlate di altri personaggi o di abiti precedenti possono entrare in conflitto con la richiesta attuale.

L'interfaccia può mostrare un riepilogo compatto della scena mentre l'immagine viene generata. Se un utente cambia verde in blu navy, crea una nuova revisione ed etichetta di conseguenza il risultato in sospeso precedente. Una vecchia revisione già finita può restare nella sua cronologia privata, ma non deve comparire in silenzio come risposta all'ultima richiesta.

Per un prodotto compagno NSFW, verifica separatamente l'idoneità di immagine e video. Il fatto che un task di immagine accetti contenuti per adulti non dimostra che il modello video successivo accetti la stessa scena. Controlla la pagina del modello scelto per ogni fase e presenta con onestà un task non disponibile, invece di promettere una clip per adulti che il modello scelto non supporta.

Prova prima nel Playground

Apri il Playground di Qwen Image 2.1 edit. Sotto Images scegli Add a file e carica il ritratto approvato del personaggio, descrivi la nuova scena in Prompt (va bene il prompt qui sotto), imposta Resolution su 1k e Aspect ratio su 16:9, premi Run model e valuta l'immagine fissa. Approva quella composizione, poi apri il Playground di Seedance 2.5 image-to-video: aggiungi l'immagine fissa come Input image, descrivi il movimento in Prompt e premi di nuovo Run model. Closing frame è facoltativo.

Lascia che la chat richieda i media con una chiamata a strumento

Dai al modello di chat uno strumento request_scene_image che chiede alla tua applicazione di preparare una scheda scena. Usa l'utente autenticato e il ritratto approvato del personaggio della tua applicazione, non ID inventati nella risposta della chat.

  • Il modello di chat: descrive la scena e chiede se l'utente vuole che venga creata.
  • La tua applicazione: verifica la proprietà del personaggio, mostra il prezzo e attende l'approvazione.
  • Il task media: crea l'immagine solo dopo l'approvazione; l'animazione è un acquisto separato.

Grok 4.6 chat supporta le chiamate a strumenti nel formato OpenAI. L'API non conserva memoria della conversazione, quindi reinvia a ogni richiesta la scheda del personaggio e il contesto di scrittura di fiction; le ricette per la chat mostrano il formato del messaggio di strumento.

json
{
  "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"]
    }
  }
}

Rispondi alla chiamata con un messaggio tool che dice che una scheda bozza è in attesa di approvazione, così il modello scrive una riga onesta («Ecco la scheda scena, vuoi che la crei?») invece di dichiarare una consegna.

Tieni separati personaggio e scena in record distinti

Tieni il ritratto approvato separato dalla scena richiesta: un cappotto blu navy, un balcone di osservatorio e una piccola rotazione verso un telescopio. Il ritratto ancora il personaggio; l'immagine di scena accettata diventa il fotogramma iniziale del video. Non sostituire il ritratto del personaggio a ogni nuova scena.

Salva un record per richiesta prima di inviarla (sono campi della tua applicazione, non un payload API):

json
{
  "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
}

Se l'utente cambia il cappotto in verde, crea la revisione 2 con una nuova chiave; non modificare mai la richiesta del cappotto blu mentre è in esecuzione.

Archivia tu l'immagine fissa approvata. Il link del risultato di un task dura circa 20 minuti e il file generato circa 14 giorni, quindi copia l'immagine fissa accettata nel tuo archivio e dai a Seedance un URL HTTPS pubblico stabile che restituisca il file immagine vero e proprio (un link a una pagina di condivisione non funziona). Passare il link del risultato Qwen direttamente alla fase video funziona solo finché non scade. Riusa l'immagine fissa archiviata per le revisioni del movimento; cambiare insieme ritratto e movimento rende più difficile correggere l'uno o l'altro.

Imposta separatamente i controlli di immagine e video

La route Qwen Image 2.1 edit accetta un prompt e da uno a dieci image_urls. Le immagini di riferimento aggiuntive aumentano il prezzo in 1k; la 2k costa $0.048 con da una a dieci immagini di riferimento. Per la prima scena usa un ritratto approvato, 1k, 16:9, PNG e l'espansione del prompt disattivata.

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.

Confronta volto e abbigliamento con il ritratto approvato prima di proporre l'immagine fissa all'utente. Alla seconda approvazione l'utente accetta quell'immagine fissa e il prezzo della clip prima dell'animazione. Se il telescopio fluttua o il lato destro non lascia spazio al movimento, correggi la fase di immagine; il video non può riparare un cattivo primo fotogramma.

Un uomo adulto di fantasia con un cappotto blu navy sta sul balcone di un osservatorio, con le mani rilassate, accanto a un telescopio su un treppiede ben appoggiato.

Illustrazione di un primo fotogramma proposto: il personaggio ha spazio per una piccola rotazione e il telescopio è fisicamente sostenuto. Non è un output di Qwen né un test di generazione.

La route Seedance 2.5 image-to-video accetta image_url, da 4 a 30 secondi, 480p, 720p o 1080p, un interruttore audio e un seed facoltativo. aspect_ratio accetta solo adaptive, quindi la clip segue la forma dell'immagine fissa; ritaglia prima l'immagine fissa nel formato di consegna. La route reference-to-video ha un contratto diverso, quindi non mescolarne i campi.

Chiedi il preventivo nel punto di decisione

Le modifiche Qwen sono a listino a $0.036 in 1k, $0.048 in 2k e $0.072 in 1.5k, con un'immagine di riferimento inclusa. Le immagini di riferimento aggiuntive aumentano i prezzi in 1k e 1.5k. Seedance costa $0.1234 al secondo a 480p, $0.2773 a 720p e $0.6823 a 1080p, con audio attivo o disattivo alla stessa tariffa.

Riusa l'immagine fissa accettata quando ritenti il movimento.

PianoCosto
Solo immagine fissa 1k$0.036
Immagine fissa + clip da 5 s a 480p$0.653
Immagine fissa + clip da 5 s a 720p$1.4225
Immagine fissa + clip da 5 s a 1080p$3.4475
Immagine fissa + due tentativi di clip a 720p$2.809

Questi importi includono un riferimento nella modifica 1k ed escludono gli addebiti per immagini di riferimento aggiuntive, i token di chat e qualsiasi modifica extra dell'immagine fissa.

Mostra il prezzo dell'immagine fissa prima della generazione e il prezzo della clip dopo che l'utente ha accettato l'immagine fissa, non a ogni turno di chat. Un preventivo è gratuito, non riserva nulla e resta valido cinque minuti per quell'account, quella chiave API, quel modello e quell'input; se cambi durata, risoluzione o riferimenti ne serve uno nuovo. Un task inviato trattiene subito il costo stimato, e quella trattenuta è il massimo che può essere addebitato. Riusare l'immagine approvata limita una revisione del movimento all'addebito del video.

Un client per la fase video che sopravvive a una risposta persa

Installa spicyapi con python -m pip install spicyapi, imposta SPICY_API_KEY e APPROVED_STILL_URL, poi esegui questo dopo aver approvato l'immagine fissa. Conserva il file di esecuzione salvato per riprendere lo stesso tentativo. Per un'altra versione creativa usa un nuovo nome di esecuzione.

python
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"])

L'SDK Python e la documentazione dei task coprono l'integrazione in produzione.

Una sola funzione di aggiornamento per polling e callback

Usa un unico gestore dell'applicazione sia per un risultato ottenuto con il polling sia per un evento di completamento verificato. Deve allegare la risorsa una sola volta, mantenerla privata e avvisare la chat solo quando è pronta per la revisione. Un evento duplicato non deve creare un'altra voce in galleria.

Per un'app compagno live, invia il completamento a un webhook verificato e mantieni una scansione periodica dello stato per i callback che non arrivano mai. I corpi dei callback contengono il prompt e link di risultato firmati, quindi trattali come privati; la documentazione dei task spiega la configurazione. I webhook con una scansione di riconciliazione (vedi l'articolo «async-task-api-polling-vs-webhook») sono adatti alla produzione; per un prototipo basta il polling. Usa l'invio limitato quando molti utenti richiedono media insieme.

Stati di consegna, errori e annullamento

Usa draft, submitted, generating, ready_for_review, delivered, failed e rejected. Lo succeeded dell'API significa che inizia la gestione del risultato; non significa che l'utente lo veda. Rivedi l'intera clip per verificare deriva del volto, oggetti che si muovono e tagli non richiesti prima di passare a delivered.

Esito APICosa mostrareAddebito
succeeded, risorsa prontarivedi, poi consegnaleggi settled e cost
succeeded, risorsa pending«ancora in preparazione», interroga di nuovo tra pochi secondinon ancora definitivo
succeeded, risorsa unavailableripristina una copia già salvata, oppure offri una nuova generazione deliberataleggi cost
failed o expiredun errore chiaro e un pulsante per riprovaretrattenuta normalmente rilasciata; alcuni rifiuti di contenuto sono addebitati quando lo indica la pagina del modello

Un preventivo scaduto richiede un nuovo preventivo e una nuova approvazione; recupera una richiesta già accettata con la sua chiave originale. Un task accettato non può essere annullato. Se l'utente dice «lascia perdere», nascondi l'allegato in sospeso e lascia terminare il job; non promettere che chiudere la chat rilasci l'addebito. Un nuovo tentativo è una nuova richiesta con il proprio preventivo.

Mantieni privati i media privati del compagno

Verifica che una scena appartenga all'utente autenticato prima di servirla. Copia i media accettati in un archivio con le tue regole di accesso; i link temporanei dei risultati non devono diventare indirizzi pubblici della galleria.

Per l'eliminazione, gestisci separatamente la copia della piattaforma e la tua. Accorcia per quanto tempo la piattaforma conserva un risultato con l'impostazione di conservazione per task e, quando un utente elimina una scena o il suo account, distruggi il contenuto archiviato di ogni task terminato (il record di fatturazione resta). Una conservazione di 0 può rimuovere il risultato prima che il tuo worker lo copi. Elimina la tua copia e revoca separatamente il suo accesso di consegna; i dettagli sono nella documentazione sulla conservazione. Usa i controlli di conservazione e una pipeline di archiviazione durevole adatti al prodotto.

Testa quattro errori con fixture

Simula un doppio clic, una risposta di accettazione persa, un evento di successo duplicato e una risorsa non disponibile. Dovresti ottenere una sola richiesta logica, il recupero con la chiave originale, un solo allegato in galleria e uno stato onesto di non disponibile. Nessuno di questi test richiede una generazione a pagamento. La coerenza del volto tra immagini fisse e clip è una questione visiva, quindi ispeziona ogni output. Non lasciare che un messaggio di chat che dice «il tuo video è pronto» prevalga sullo stato reale del task o della risorsa. L'illustrazione dell'osservatorio è una scena editoriale, non un output di Qwen o Seedance.

Lascia che un Agent gestisca la chiamata

Usa un Agent quando vuoi che prepari gli input e restituisca il risultato mentre tu approvi il prezzo e le scelte creative.

  • CLI: con un input.json come {"image_urls": ["<approved portrait URL>"], "prompt": "<the scene prompt above>", "resolution": "1k", "aspect_ratio": "16:9", "output_format": "png", "enable_prompt_expansion": false}, esegui npx @spicyapi/cli tasks create --model alibaba/qwen-image-2.1/edit --input-file input.json --wait. La CLI conferma il prezzo; conserva l'ID task restituito per il recupero.
  • MCP: chiedi al tuo assistente di usare spicyapi_task_create per alibaba/qwen-image-2.1/edit, poi spicyapi_task_wait. Il server MCP richiede la conferma del prezzo prima della creazione.
  • Skill: installala con npx skills add spicyapi-ai/spicy-skill, poi chiedi a $spicyapi di eseguire lo stesso flusso e di fermarsi per la tua approvazione. La Skill usa una connessione CLI o MCP configurata.

Anche in English · 日本語 · 한국어 · Deutsch · Français · Español · Português (Brasil) · Русский · Polski

Provalo tu stesso

Paghi solo quello che esegui: nessun piano, nessun abbonamento.

Creato con MiniMax H3 · ByteDance Seedance 2.0 Fast