Pular para o conteúdo principal

Integre a entrega de imagens e vídeos em um app de companheiros IA

Crie imagens e clipes aprovados de companheiros: congele a cena, cote custos, recupere tarefas assíncronas e entregue recursos revisados sem duplicatas.

Autor
Lydia
Publicado
Leitura
15 min de leitura
Atualizado
Um homem adulto se inclina em direção ao ocular de um telescópio enquanto uma mulher de casaco vermelho sorri ao lado dele, perto de uma porta com iluminação quente.

Um usuário diz a Rowan, um personagem companheiro adulto fictício: "mostre o observatório para mim hoje à noite". O modelo de chat pode responder no papel do personagem, mas seu aplicativo precisa criar a imagem, manter a cena consistente e anexar um recurso real quando ele existir. Mantenha o chat responsivo enquanto os jobs de imagem e vídeo rodam separadamente.

Dê ao usuário o controle de duas compras:

  • Aprovar a imagem estática: mostre um cartão de cena gratuito, cote a imagem e só a crie depois que o usuário aceitar o preço.
  • Aprovar o clipe: mostre a imagem estática pronta, cote a animação dela e só crie o vídeo depois que o usuário aceitar essa imagem e o preço.

Persista cada solicitação antes de enviá-la, use uma chave separada para cada etapa e entregue a partir da sua própria cópia armazenada. Uma chamada de ferramenta inicia o cartão de cena; ela nunca autoriza uma compra nem anuncia um anexo inexistente.

Como orçamento concreto, uma edição Qwen em 1k ($0,036) mais um clipe de cinco segundos do Seedance 2.5 em 720p somam $1,4225, antes das cobranças por imagens de referência extras, dos tokens de chat e das novas tentativas. Peça uma cotação (quote) antes de mostrar qualquer preço a um usuário.

O fluxo completo, em ordem:

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

Transforme um pedido de chat em uma imagem que o app realmente consiga criar

Um usuário pode escrever: "Mande outra foto com o casaco verde", enquanto um vídeo com casaco azul-marinho já está sendo gerado. A resposta do chat e a solicitação de mídia não devem compartilhar uma única descrição mutável. O app precisa saber qual adulto fictício, qual casaco, qual cenário e se o usuário quer uma imagem estática ou um clipe. Caso contrário, "outra" pode criar sem querer um rosto novo, reaproveitar uma cena desatualizada ou comprar um vídeo quando o usuário pediu uma foto.

Mantenha pequenos os dados permanentes do personagem: o retrato aprovado, a maioridade, notas estáveis de aparência e o personagem escolhido pelo usuário. Mantenha a cena atual separada: casaco, ambiente, visão de câmera e ação solicitada. A conversa pode sugerir uma cena, mas seu aplicativo resolve o personagem armazenado e os recursos de origem aprovados. Não deixe o modelo de chat inventar uma URL de referência ou um ID de tarefa, nem afirmar que uma imagem existe.

No exemplo do observatório, transforme o pedido em uma descrição explícita da cena antes de cotar: Rowan, o retrato aprovado, casaco azul-marinho, sacada do observatório, plano médio, uma imagem estática. Para um pedido posterior de "faça mexer", use a imagem estática aceita do observatório e um pequeno giro de cabeça. Não envie a conversa inteira ao modelo de imagem; descrições sem relação sobre outros personagens ou roupas anteriores podem conflitar com o pedido atual.

A interface pode mostrar um resumo compacto da cena enquanto a imagem é gerada. Se o usuário trocar verde por azul-marinho, crie uma nova revisão e identifique de acordo o resultado pendente anterior. Uma revisão antiga já concluída pode ficar no histórico privado dele, mas não deve aparecer em silêncio como resposta ao pedido mais recente.

Em um produto de companhia NSFW, verifique separadamente a adequação da imagem e do vídeo. O fato de uma tarefa de imagem aceitar conteúdo adulto não prova que o próximo modelo de vídeo aceite a mesma cena. Consulte a página do modelo escolhido para cada etapa e apresente com honestidade uma tarefa indisponível, em vez de prometer um clipe adulto que o modelo escolhido não suporta.

Experimente primeiro no Playground

Abra o Playground do Qwen Image 2.1 edit. Em Images, escolha Add a file e envie o retrato aprovado do personagem, descreva a nova cena em Prompt (o prompt abaixo funciona), defina Resolution como 1k e Aspect ratio como 16:9, pressione Run model e avalie a imagem estática. Aprove essa composição e abra o Playground do Seedance 2.5 image-to-video: adicione a imagem estática como Input image, descreva o movimento em Prompt e pressione Run model de novo. Closing frame é opcional.

Deixe o chat pedir mídia com uma chamada de ferramenta

Dê ao modelo de chat uma ferramenta request_scene_image que peça ao seu aplicativo para preparar um cartão de cena. Use o usuário autenticado e o retrato aprovado do personagem do seu aplicativo, não IDs inventados na resposta do chat.

  • O modelo de chat: descreve a cena e pergunta se o usuário quer que ela seja criada.
  • Seu aplicativo: verifica a propriedade do personagem, mostra o preço e espera a aprovação.
  • A tarefa de mídia: cria a imagem só depois da aprovação; a animação é uma compra separada.

O Grok 4.6 chat oferece suporte a chamadas de ferramenta no formato OpenAI. A API não guarda memória da conversa, então reenvie a ficha do personagem e o contexto de escrita de ficção a cada solicitação; as receitas de chat mostram o formato da mensagem de ferramenta.

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

Responda à chamada com uma mensagem tool dizendo que um cartão de rascunho aguarda aprovação, para que o modelo escreva uma frase honesta ("Aqui está o cartão de cena, quer que eu crie?") em vez de afirmar que houve uma entrega.

Mantenha o personagem e a cena em registros separados

Mantenha o retrato aprovado separado da cena solicitada: um casaco azul-marinho, uma sacada de observatório e um pequeno giro em direção a um telescópio. O retrato ancora o personagem; a imagem de cena aceita se torna o quadro inicial do vídeo. Não substitua o retrato do personagem a cada nova cena.

Persista um registro por solicitação antes de enviá-la (são campos do seu aplicativo, não um payload da 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 o usuário trocar o casaco por verde, crie a revisão 2 com uma chave nova; nunca edite a solicitação do casaco azul enquanto ela estiver em execução.

Armazene você mesmo a imagem estática aprovada. O link de resultado de uma tarefa dura cerca de 20 minutos e o arquivo gerado cerca de 14 dias, então copie a imagem estática aceita para o seu próprio armazenamento e dê ao Seedance uma URL HTTPS pública estável que retorne o próprio arquivo de imagem (um link de página de compartilhamento falha). Passar o link de resultado do Qwen direto para a etapa de vídeo só funciona até ele expirar. Reutilize a imagem estática armazenada nas revisões de movimento; mudar o retrato e o movimento ao mesmo tempo dificulta corrigir qualquer um dos dois.

Defina separadamente os controles de imagem e de vídeo

A rota Qwen Image 2.1 edit aceita um prompt e de uma a dez image_urls. Imagens de referência extras aumentam o preço do 1k; o 2k custa $0,048 com de uma a dez imagens de referência. Para a primeira cena, use um retrato aprovado, 1k, 16:9, PNG e a expansão de prompt desativada.

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.

Compare o rosto e a roupa com o retrato aprovado antes de oferecer a imagem estática ao usuário. Na aprovação 2, o usuário aceita essa imagem estática e o preço do clipe antes da animação. Se o telescópio flutuar ou o lado direito não tiver espaço para o movimento, corrija a etapa de imagem; o vídeo não consegue consertar um quadro inicial ruim.

Um homem adulto fictício de casaco azul-marinho está em pé na sacada de um observatório, com as mãos relaxadas, ao lado de um telescópio em um tripé bem apoiado.

Ilustração de um quadro inicial proposto: o personagem tem espaço para um pequeno giro e o telescópio está fisicamente apoiado. Isto não é uma saída do Qwen nem um teste de geração.

A rota Seedance 2.5 image-to-video recebe image_url, de 4 a 30 segundos, 480p, 720p ou 1080p, uma opção de áudio e um seed opcional. aspect_ratio aceita apenas adaptive, então o clipe segue o formato da imagem estática; corte antes a imagem estática no formato de entrega. A rota reference-to-video tem um contrato diferente, então não misture os campos dela.

Cote no ponto de decisão

As edições do Qwen custam $0,036 em 1k, $0,048 em 2k e $0,072 em 1.5k, com uma imagem de referência incluída. Imagens de referência adicionais aumentam os preços de 1k e 1.5k. O Seedance custa $0,1234 por segundo em 480p, $0,2773 em 720p e $0,6823 em 1080p, com áudio ligado ou desligado pela mesma tarifa.

Reutilize a imagem estática aceita ao tentar o movimento de novo.

PlanoCusto
Só a imagem estática em 1k$0,036
Imagem estática + clipe de 5 s em 480p$0,653
Imagem estática + clipe de 5 s em 720p$1,4225
Imagem estática + clipe de 5 s em 1080p$3,4475
Imagem estática + duas tentativas de clipe em 720p$2,809

Os valores incluem uma referência na edição em 1k e excluem cobranças por imagens de referência extras, tokens de chat e quaisquer edições adicionais da imagem estática.

Mostre o preço da imagem estática antes da geração e o preço do clipe depois que o usuário aceitar a imagem estática, não a cada turno do chat. Uma cotação é gratuita, não reserva nada e continua válida por cinco minutos para aquela conta, chave de API, modelo e entrada; se você mudar a duração, a resolução ou as referências, precisa de uma nova. Uma tarefa enviada retém o custo estimado imediatamente, e essa retenção é o máximo que pode ser cobrado. Reutilizar a imagem aprovada mantém uma revisão de movimento apenas na cobrança do vídeo.

Um cliente para a etapa de vídeo que sobrevive à perda de uma resposta

Instale o spicyapi com python -m pip install spicyapi, defina SPICY_API_KEY e APPROVED_STILL_URL e execute isto depois de aprovar a imagem estática. Guarde o arquivo de execução salvo para retomar a mesma tentativa. Para outra versão criativa, use um novo nome de execução.

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

O SDK Python e a documentação de tarefas cobrem a integração em produção.

Uma função de atualização para polling e callbacks

Use um único manipulador do aplicativo tanto para um resultado obtido por polling quanto para um evento de conclusão verificado. Ele deve anexar o recurso uma só vez, mantê-lo privado e avisar o chat apenas quando estiver pronto para revisão. Um evento duplicado não pode criar outra entrada na galeria.

Em um app de companheiros em produção, envie a conclusão a um webhook verificado e mantenha uma varredura periódica de status para os callbacks que nunca chegam. Os corpos dos callbacks trazem o prompt e links de resultado assinados, então trate-os como privados; a documentação de tarefas explica a configuração. Webhooks com uma varredura de reconciliação (veja o artigo "async-task-api-polling-vs-webhook") são adequados à produção; para um protótipo, o polling basta. Use o despacho limitado quando muitos usuários pedirem mídia ao mesmo tempo.

Estados de entrega, falhas e cancelamento

Use draft, submitted, generating, ready_for_review, delivered, failed e rejected. O succeeded da API significa que o tratamento do resultado começa; não significa que o usuário o vê. Revise o clipe inteiro quanto a deriva do rosto, objetos que se movem e cortes não solicitados antes de passar para delivered.

Resultado da APIO que mostrarCobrança
succeeded, recurso prontorevisar e depois entregarler settled e cost
succeeded, recurso pending"ainda em preparo", consultar de novo em segundosainda não é final
succeeded, recurso unavailablerestaurar uma cópia já salva ou oferecer uma nova geração deliberadaler cost
failed ou expireduma falha clara e um botão de nova tentativaretenção normalmente liberada; algumas recusas de conteúdo são cobradas quando a página do modelo indica

Uma cotação expirada precisa de uma nova cotação e de nova aprovação; recupere uma solicitação já aceita com a chave original. Uma tarefa aceita não pode ser cancelada. Se o usuário disser "deixa pra lá", oculte o anexo pendente e deixe o job terminar; não prometa que fechar o chat libera a cobrança. Uma nova tentativa é uma nova solicitação com a própria cotação.

Mantenha privada a mídia privada do companheiro

Verifique se uma cena pertence ao usuário autenticado antes de servi-la. Copie a mídia aceita para um armazenamento com suas próprias regras de acesso; links temporários de resultado não devem virar endereços públicos de galeria.

Para a exclusão, trate separadamente a cópia da plataforma e a sua. Reduza por quanto tempo a plataforma guarda um resultado com a configuração de retenção por tarefa e, quando um usuário excluir uma cena ou a conta, destrua o conteúdo armazenado de cada tarefa concluída (o registro de cobrança permanece). Uma retenção de 0 pode remover o resultado antes que seu worker o copie. Exclua a sua própria cópia e revogue separadamente o acesso de entrega dela; os detalhes estão na documentação de retenção. Use os controles de retenção e um pipeline de armazenamento durável adequados ao produto.

Teste quatro falhas com fixtures

Simule um clique duplo, uma resposta de aceitação perdida, um evento de sucesso duplicado e um recurso indisponível. Você deve obter uma única solicitação lógica, recuperação com a chave original, um só anexo na galeria e um estado honesto de indisponível. Nenhum desses testes exige uma geração paga. A consistência do rosto entre imagens estáticas e clipes é uma questão visual, então inspecione cada saída. Não deixe uma mensagem de chat dizendo "seu vídeo está pronto" se sobrepor ao estado real da tarefa ou do recurso. A ilustração do observatório é uma cena editorial, não uma saída do Qwen nem do Seedance.

Deixe um Agent cuidar da chamada

Use um Agent quando quiser que ele prepare as entradas e devolva o resultado enquanto você aprova o preço e as escolhas criativas.

  • CLI: com um input.json como {"image_urls": ["<approved portrait URL>"], "prompt": "<the scene prompt above>", "resolution": "1k", "aspect_ratio": "16:9", "output_format": "png", "enable_prompt_expansion": false}, execute npx @spicyapi/cli tasks create --model alibaba/qwen-image-2.1/edit --input-file input.json --wait. A CLI confirma o preço; guarde o ID da tarefa retornado para a recuperação.
  • MCP: peça ao seu assistente que use spicyapi_task_create para alibaba/qwen-image-2.1/edit e depois spicyapi_task_wait. O servidor MCP pede confirmação do preço antes da criação.
  • Skill: instale com npx skills add spicyapi-ai/spicy-skill e peça então a $spicyapi que execute o mesmo fluxo e pare para a sua aprovação. A Skill usa uma conexão CLI ou MCP configurada.

Também em English · 日本語 · 한국어 · Deutsch · Français · Español · Русский · Italiano · Polski

Experimente você mesmo

Você paga só pelo que executar — sem plano e sem assinatura.

Feito com MiniMax H3 · ByteDance Seedance 2.0 Fast