prompt api multimodalgemini nano imagemia áudio browser

IA multimodal no browser: imagens e áudio com Gemini Nano

Prompt API Brasil

IA multimodal no browser: imagens e áudio com Gemini Nano

A Prompt API não para no texto. O Gemini Nano mastica imagens e áudio direto no browser — sem servidor, sem upload para lugar nenhum. Desde o Chrome 148, dá pra fazer coisas que antes exigiam um backend inteiro: gerar descrição automática de imagem, comparar fotos lado a lado, transcrever áudio. Tudo on-device. Tudo privado.

TL;DR

  • Gemini Nano aceita texto, imagem e áudio como input — output é sempre texto, processado localmente
  • Suporta HTMLImageElement, Blob, Canvas, AudioBuffer e mais via expectedInputs na sessão
  • Áudio exige GPU obrigatoriamente; imagem funciona em CPU mas é 2-5x mais lenta

Essa é a parte da API que mais me pegou de surpresa. Pensei: gerar alt text a partir de uma foto que o usuário acabou de subir, sem que essa foto saia do dispositivo dele? Isso é genuinamente novo na web. Não é marketing — é diferente de verdade.

Diagrama de streams de texto, imagem e áudio convergindo para um chip de IA e saindo como texto.

Como funciona

O fluxo é direto:

  1. Input: texto, imagem e/ou áudio (qualquer combinação)
  2. Processamento: inferência local no Gemini Nano
  3. Output: sempre texto

Tudo acontece no dispositivo. A imagem da webcam do usuário, o áudio do microfone — nada cruza a rede.

Configurando expectedInputs

Para usar inputs multimodais, declare as modalidades ao criar a sessão:

const session = await LanguageModel.create({
  expectedInputs: [
    { type: "text", languages: ["pt", "en"] },
    { type: "image" },
    { type: "audio" }
  ],
  expectedOutputs: [
    { type: "text", languages: ["pt"] }
  ]
});

Se você não declarar { type: "image" } ou { type: "audio" } em expectedInputs, o modelo rejeita esses inputs com NotSupportedError. É um erro fácil de cometer — se estiver vendo esse erro, confira o expectedInputs.

Tipos de input suportados

Imagem

Tipo JavaScriptCaso de uso típico
HTMLImageElementImagem já no DOM
HTMLCanvasElementDesenhos, screenshots
HTMLVideoElementFrame atual de vídeo
ImageBitmapAlto desempenho, off-thread
OffscreenCanvasProcessamento em Web Worker
VideoFramePipelines WebCodecs
BlobUpload de arquivo
ImageDataManipulação pixel a pixel

Áudio

Tipo JavaScriptCaso de uso típico
AudioBufferÁudio gravado/processado
ArrayBufferArquivo carregado
ArrayBufferViewChunks de áudio
BlobUpload direto

Formato de mensagem multimodal

No modo multimodal, o content vira um array de partes:

const resposta = await session.prompt([
  {
    role: "user",
    content: [
      { type: "text", value: "Descreva esta imagem:" },
      { type: "image", value: minhaImagem }
    ]
  }
]);

Cada parte tem type (“text”, “image” ou “audio”) e value (o conteúdo correspondente).

Demo 1: Descrever imagem uploaded pelo usuário

O usuário faz upload, recebe uma descrição textual. Útil para alt text, legendas, ou catalogação:

// HTML: <input type="file" id="fileInput" accept="image/*">
const fileInput = document.getElementById('fileInput');

const session = await LanguageModel.create({
  expectedInputs: [
    { type: "text", languages: ["pt"] },
    { type: "image" }
  ],
  expectedOutputs: [{ type: "text", languages: ["pt"] }]
});

fileInput.addEventListener('change', async (e) => {
  const arquivo = e.target.files[0];
  if (!arquivo) return;

  const blob = new Blob([await arquivo.arrayBuffer()], { type: arquivo.type });

  const descricao = await session.prompt([
    {
      role: "user",
      content: [
        { type: "text", value: "Descreva esta imagem em português, 2-3 frases, focando nos elementos principais:" },
        { type: "image", value: blob }
      ]
    }
  ]);

  document.getElementById('resultado').textContent = descricao;
});

Mockup de demo multimodal com área de upload de imagem e descrição gerada por IA.

Se a imagem já está no DOM, pode passar o elemento direto:

const img = document.querySelector('#minha-foto');

const altText = await session.prompt([
  {
    role: "user",
    content: [
      { type: "text", value: "Gere um alt text acessível, máximo 125 caracteres:" },
      { type: "image", value: img }
    ]
  }
]);

img.alt = altText;

Demo 2: Comparar duas imagens

Múltiplas imagens na mesma mensagem — comparação visual:

const imagemReferencia = await (await fetch("modelo.jpg")).blob();
const canvasUsuario = document.querySelector("#canvas-desenho");

const critica = await session.prompt([
  {
    role: "user",
    content: [
      {
        type: "text",
        value: "Compare a segunda imagem com a primeira (referência). Critique similaridade, proporções e cores:"
      },
      { type: "image", value: imagemReferencia },
      { type: "image", value: canvasUsuario }
    ]
  }
]);

console.log(critica);

Casos reais: e-commerce (produto recebido vs. anúncio), educação (exercício vs. modelo), QA visual (diff entre versões de UI).

Demo 3: Transcrever áudio do microfone

Para áudio, capture com MediaRecorder e envie ao modelo:

const session = await LanguageModel.create({
  expectedInputs: [
    { type: "text", languages: ["en"] },
    { type: "audio" }
  ],
  expectedOutputs: [{ type: "text", languages: ["pt"] }]
});

async function gravarAudio(duracaoSegundos) {
  const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
  const audioContext = new AudioContext();
  const mediaRecorder = new MediaRecorder(stream);
  const chunks = [];

  mediaRecorder.ondataavailable = (e) => chunks.push(e.data);

  return new Promise((resolve) => {
    mediaRecorder.onstop = async () => {
      stream.getTracks().forEach(t => t.stop());
      const blob = new Blob(chunks, { type: 'audio/webm' });
      const arrayBuffer = await blob.arrayBuffer();
      const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
      resolve(audioBuffer);
    };

    mediaRecorder.start();
    setTimeout(() => mediaRecorder.stop(), duracaoSegundos * 1000);
  });
}

// Gravar 10 segundos e transcrever
document.getElementById('btn-gravar').addEventListener('click', async () => {
  const status = document.getElementById('status');
  status.textContent = "Gravando 10 segundos...";

  const audioBuffer = await gravarAudio(10);
  status.textContent = "Transcrevendo...";

  const transcricao = await session.prompt([
    {
      role: "user",
      content: [
        { type: "text", value: "Transcribe this audio to text:" },
        { type: "audio", value: audioBuffer }
      ]
    }
  ]);

  document.getElementById('transcricao').textContent = transcricao;
  status.textContent = "Pronto!";
});

Mockup de app de áudio com botão de gravação, waveform e texto transcrito.

append() para inputs assíncronos

Para cenários com upload incremental (várias imagens, uma de cada vez), use append() para pré-carregar no contexto:

// Usuário faz upload de imagens uma a uma
uploadInput.onchange = async () => {
  const file = uploadInput.files[0];
  await session.append([
    {
      role: "user",
      content: [
        { type: "text", value: `Nova imagem: ${file.name}` },
        { type: "image", value: file }
      ]
    }
  ]);
  console.log("Imagem adicionada ao contexto");
};

// Quando pronto para análise conjunta
btnAnalisar.onclick = async () => {
  const analise = await session.prompt(
    "Analise todas as imagens e descreva padrões em comum."
  );
  console.log(analise);
};

O append() é útil porque inferência multimodal é lenta — pré-processando o conteúdo, o modelo já está pronto quando o usuário pede a análise.

Limitações de hardware

Esse é o ponto que pega:

ModalidadeCPU (16GB, 4 cores)GPU (>4GB VRAM)
Texto✅ Funciona✅ Funciona
Imagem✅ Funciona (lento)✅ Funciona
Áudio❌ Não suportado✅ Obrigatório

Áudio exige GPU. Sem GPU dedicada com >4GB VRAM, a sessão com expectedInputs: [{ type: "audio" }] retorna unavailable. Isso elimina boa parte dos laptops mais modestos.

Outras limitações a ter em mente:

  • Output é sempre texto — não gera imagem ou áudio
  • Áudio funciona melhor em inglês; outros idiomas com precisão reduzida
  • Imagens grandes são redimensionadas internamente
  • Inferência multimodal é 2-5x mais lenta que texto puro
  • Imagens e áudio consomem muitos tokens do context window

Feature detection robusta

Sempre verifique antes de habilitar features multimodais:

async function verificarSuporteMultimodal() {
  if (!('LanguageModel' in window)) {
    return { texto: false, imagem: false, audio: false };
  }

  const textoOk = await LanguageModel.availability({
    expectedInputs: [{ type: "text" }],
    expectedOutputs: [{ type: "text" }]
  });

  const imagemOk = await LanguageModel.availability({
    expectedInputs: [{ type: "text" }, { type: "image" }],
    expectedOutputs: [{ type: "text" }]
  });

  const audioOk = await LanguageModel.availability({
    expectedInputs: [{ type: "text" }, { type: "audio" }],
    expectedOutputs: [{ type: "text" }]
  });

  return {
    texto: textoOk !== 'unavailable',
    imagem: imagemOk !== 'unavailable',
    audio: audioOk !== 'unavailable'
  };
}

const suporte = await verificarSuporteMultimodal();
if (suporte.imagem) mostrarBotaoUploadImagem();
if (suporte.audio) mostrarBotaoGravacao();
else mostrarMensagem("Transcrição requer GPU dedicada");

Combinando multimodal com structured output

Classificar imagens com output estruturado — uma combinação poderosa:

const schema = {
  type: "object",
  properties: {
    tipo_documento: {
      type: "string",
      enum: ["rg", "cnh", "passaporte", "comprovante", "outro"]
    },
    confianca: { type: "number" },
    legivel: { type: "boolean" }
  },
  required: ["tipo_documento", "confianca", "legivel"],
  additionalProperties: false
};

const classificacao = await session.prompt(
  [
    {
      role: "user",
      content: [
        { type: "text", value: "Classifique o tipo de documento nesta imagem:" },
        { type: "image", value: imagemDocumento }
      ]
    }
  ],
  { responseConstraint: schema }
);

const resultado = JSON.parse(classificacao);
// { tipo_documento: "cnh", confianca: 0.85, legivel: true }

Imagine isso num fluxo de onboarding: o usuário tira foto do documento, o modelo classifica e verifica legibilidade — tudo no browser, sem enviar o documento para servidor nenhum.

Fluxo de upload de documento, classificação multimodal, schema e resultado JSON.

FAQ

Quais formatos de imagem são aceitos?

Qualquer formato que o browser decodifica: JPEG, PNG, WebP, GIF (primeiro frame), SVG (rasterizado), BMP. Input via elemento DOM ou Blob.

O áudio precisa ser em formato específico?

Não. Aceita AudioBuffer, Blob de qualquer codec suportado pelo browser (WebM/Opus, WAV, MP3, AAC), ou ArrayBuffer com dados raw.

Multimodal funciona em extensões do Chrome?

Sim. Funciona tanto em páginas web quanto em Chrome Extensions, desde que hardware suporte.

Posso enviar vídeo completo?

Não diretamente. Pode enviar um frame via HTMLVideoElement (frame na posição atual) ou VideoFrame (WebCodecs). Para vídeo completo, extraia keyframes e envie como múltiplas imagens.

A Prompt API gera imagens ou áudio?

Não. Output é exclusivamente texto. Para gerar imagens no browser, considere Stable Diffusion via WebGPU.

Conclusão

Multimodal on-device abre aplicações que antes pediam backend obrigatório: acessibilidade (alt text automático), segurança (classificação de documentos sem expor dados), produtividade (transcrição offline). Tudo local, tudo privado.

Combina inputs visuais com structured output pra classificação robusta, e usa o polyfill quando o browser não suportar.


Próximo artigo: Polyfill da Prompt API: como dar fallback para Firefox e Safari

Referências