Inputs Multimodais na Prompt API — Imagem e Áudio On-Device

A Prompt API não se limita a texto. A partir do Chrome 148, o Gemini Nano processa imagens e áudio direto no dispositivo — análise visual, transcrição contextual, interações multimodais. Tudo sem mandar nada pra servidor nenhum.

Configuração de expectedInputs

Pra usar inputs multimodais, declare as modalidades esperadas ao verificar disponibilidade e criar a sessão:

// Verificar se o hardware suporta as modalidades desejadas
const status = await LanguageModel.availability({
  expectedInputs: [
    { type: 'text', languages: ['en'] },
    { type: 'image' },
    { type: 'audio' }
  ],
  expectedOutputs: [{ type: 'text', languages: ['en'] }]
});

if (status === 'unavailable') {
  console.log('Hardware não suporta inputs multimodais');
}

Importante: disponibilidade por modalidade

Cada modalidade tem requisitos de hardware diferentes:

ModalidadeRequisito de hardware
TextoGPU >4GB ou CPU 16GB RAM + 4 cores
ImagemGPU >4GB ou CPU 16GB RAM + 4 cores
ÁudioGPU obrigatória (não funciona apenas com CPU)

Sempre verifique a disponibilidade com as mesmas opções que usará em create(). Se o usuário não tem GPU adequada, availability() retornará 'unavailable' quando áudio é solicitado.

Sessão multimodal completa

const session = await LanguageModel.create({
  expectedInputs: [
    { type: 'text', languages: ['en'] },
    { type: 'image' },
    { type: 'audio' }
  ],
  expectedOutputs: [{ type: 'text', languages: ['en'] }],
  initialPrompts: [
    {
      role: 'system',
      content: 'You are a multimodal assistant that can analyze images and audio.'
    }
  ]
});

Inputs de imagem

Tipos aceitos para imagem

A Prompt API aceita os seguintes tipos como valor de input de imagem:

TipoDescriçãoCaso de uso
HTMLImageElementTag <img> do DOMImagens já carregadas na página
SVGImageElementElemento SVG <image>Gráficos vetoriais com imagem embutida
HTMLVideoElementTag <video> (frame atual)Análise de frame de vídeo
HTMLCanvasElementCanvas 2DDesenhos, capturas, processamento
ImageBitmapBitmap decodificadoAlto desempenho, workers
OffscreenCanvasCanvas off-screenProcessamento sem DOM
VideoFrameFrame de vídeo WebCodecsProcessamento de vídeo avançado
BlobArquivo binário (JPEG, PNG, WebP)Upload de arquivo, fetch
ImageDataPixels RGBA crusManipulação pixel a pixel

Exemplo: Analisar imagem de um

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

const imgElement = document.getElementById('foto-produto');

const descricao = await session.prompt([
  {
    role: 'user',
    content: [
      { type: 'text', value: 'Describe this product image for an alt text:' },
      { type: 'image', value: imgElement }
    ]
  }
]);

console.log(descricao);
// "A sleek wireless mouse in matte black with RGB lighting..."

Exemplo: Analisar imagem via fetch (Blob)

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

// Carregar imagem como Blob
const response = await fetch('/images/chart.png');
const imageBlob = await response.blob();

const analise = await session.prompt([
  {
    role: 'user',
    content: [
      { type: 'text', value: 'What trends do you see in this chart?' },
      { type: 'image', value: imageBlob }
    ]
  }
]);

Exemplo: Comparar duas imagens

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

const imagemReferencia = await (await fetch('referencia.jpg')).blob();
const imagemUsuario = document.querySelector('canvas');

const critica = await session.prompt([
  {
    role: 'user',
    content: [
      {
        type: 'text',
        value: 'Give a helpful artistic critique of how well the second image matches the first:'
      },
      { type: 'image', value: imagemReferencia },
      { type: 'image', value: imagemUsuario }
    ]
  }
]);

Exemplo: Imagem do Canvas

const canvas = document.getElementById('meu-canvas');
const ctx = canvas.getContext('2d');

// Desenhar algo no canvas
ctx.fillStyle = 'blue';
ctx.fillRect(10, 10, 100, 100);

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

const descricao = await session.prompt([
  {
    role: 'user',
    content: [
      { type: 'text', value: 'What shapes and colors do you see?' },
      { type: 'image', value: canvas }
    ]
  }
]);

Exemplo: Frame de vídeo

const video = document.getElementById('meu-video');

// Pausar no frame desejado
video.currentTime = 30; // segundo 30
await new Promise(r => video.addEventListener('seeked', r, { once: true }));

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

// HTMLVideoElement usa o frame na posição atual
const descricao = await session.prompt([
  {
    role: 'user',
    content: [
      { type: 'text', value: 'Describe what is happening in this video frame:' },
      { type: 'image', value: video }
    ]
  }
]);

Exemplo: Upload de arquivo pelo usuário

const fileInput = document.getElementById('file-input');

fileInput.addEventListener('change', async () => {
  const file = fileInput.files[0];

  if (!file.type.startsWith('image/')) {
    alert('Por favor, selecione uma imagem');
    return;
  }

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

  // File é um Blob, aceito diretamente
  const analise = await session.prompt([
    {
      role: 'user',
      content: [
        { type: 'text', value: 'Describe this image in detail:' },
        { type: 'image', value: file }
      ]
    }
  ]);

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

Inputs de áudio

Tipos aceitos para áudio

TipoDescriçãoCaso de uso
AudioBufferBuffer de áudio Web Audio APIGravação de microfone processada
ArrayBufferViewVista tipada de buffer (Uint8Array, Float32Array)Dados de áudio crus
ArrayBufferBuffer binárioArquivo de áudio carregado
BlobArquivo binário (WAV, MP3, etc.)Upload de arquivo de áudio

Limitação crítica: GPU obrigatória pra áudio

⚠️ Input de áudio requer GPU. Se o dispositivo do usuário não tem GPU com mais de 4 GB de VRAM, a Prompt API com áudio simplesmente não vai funcionar. Sempre verifique:

const statusAudio = await LanguageModel.availability({
  expectedInputs: [
    { type: 'text', languages: ['en'] },
    { type: 'audio' }
  ],
  expectedOutputs: [{ type: 'text', languages: ['en'] }]
});

if (statusAudio === 'unavailable') {
  console.log('Áudio não suportado — GPU insuficiente');
  // Oferecer alternativa apenas com texto
}

Exemplo: Gravar e transcrever áudio do microfone

async function gravarETranscrever(duracaoSegundos = 10) {
  // 1. Capturar áudio do microfone
  const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
  const audioContext = new AudioContext();
  const source = audioContext.createMediaStreamSource(stream);
  const recorder = audioContext.createScriptProcessor(4096, 1, 1);

  const chunks = [];
  recorder.onaudioprocess = (e) => {
    chunks.push(new Float32Array(e.inputBuffer.getChannelData(0)));
  };

  source.connect(recorder);
  recorder.connect(audioContext.destination);

  // 2. Gravar por N segundos
  await new Promise(r => setTimeout(r, duracaoSegundos * 1000));

  // 3. Parar gravação
  recorder.disconnect();
  source.disconnect();
  stream.getTracks().forEach(t => t.stop());

  // 4. Criar AudioBuffer
  const totalLength = chunks.reduce((acc, c) => acc + c.length, 0);
  const audioBuffer = audioContext.createBuffer(
    1, totalLength, audioContext.sampleRate
  );
  const channelData = audioBuffer.getChannelData(0);
  let offset = 0;
  for (const chunk of chunks) {
    channelData.set(chunk, offset);
    offset += chunk.length;
  }

  // 5. Enviar para a Prompt API
  const session = await LanguageModel.create({
    expectedInputs: [
      { type: 'text', languages: ['en'] },
      { type: 'audio' }
    ],
    expectedOutputs: [{ type: 'text', languages: ['en'] }]
  });

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

  return transcricao;
}

Exemplo: Analisar arquivo de áudio (Blob)

const audioFileInput = document.getElementById('audio-input');

audioFileInput.addEventListener('change', async () => {
  const file = audioFileInput.files[0];

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

  const analise = await session.prompt([
    {
      role: 'user',
      content: [
        { type: 'text', value: 'What is being said in this audio recording?' },
        { type: 'audio', value: file }
      ]
    }
  ]);

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

Exemplo: MediaRecorder para áudio

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

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

  mediaRecorder.start();

  // Gravar por 10 segundos
  await new Promise(r => setTimeout(r, 10000));
  mediaRecorder.stop();

  await new Promise(r => { mediaRecorder.onstop = r; });
  stream.getTracks().forEach(t => t.stop());

  // Criar Blob de áudio
  const audioBlob = new Blob(chunks, { type: 'audio/webm' });

  // Enviar para análise
  const session = await LanguageModel.create({
    expectedInputs: [
      { type: 'text', languages: ['en'] },
      { type: 'audio' }
    ],
    expectedOutputs: [{ type: 'text', languages: ['en'] }]
  });

  return await session.prompt([
    {
      role: 'user',
      content: [
        { type: 'text', value: 'Summarize what was said:' },
        { type: 'audio', value: audioBlob }
      ]
    }
  ]);
}

Combinando múltiplas modalidades

Uma sessão pode processar texto, imagem e áudio no mesmo contexto:

const session = await LanguageModel.create({
  expectedInputs: [
    { type: 'text', languages: ['en'] },
    { type: 'image' },
    { type: 'audio' }
  ],
  expectedOutputs: [{ type: 'text', languages: ['en'] }],
  initialPrompts: [
    {
      role: 'system',
      content: 'You are an assistant that correlates information across text, images, and audio.'
    }
  ]
});

// Primeiro turno: análise de imagem
const r1 = await session.prompt([
  {
    role: 'user',
    content: [
      { type: 'text', value: 'Here is a photo of a product:' },
      { type: 'image', value: document.getElementById('produto-img') }
    ]
  }
]);

// Segundo turno: áudio com contexto da imagem anterior
const audioReview = await capturarAudio(15);
const r2 = await session.prompt([
  {
    role: 'user',
    content: [
      { type: 'text', value: 'Here is a verbal review of the product shown earlier:' },
      { type: 'audio', value: audioReview }
    ]
  }
]);

// Terceiro turno: pedir síntese
const sintese = await session.prompt(
  'Based on the product image and the verbal review, give a summary rating.'
);

Usando append() para pré-processamento

Para inputs pesados (imagens/áudio), use append() para processar incrementalmente enquanto o usuário interage:

const session = await LanguageModel.create({
  expectedInputs: [{ type: 'image' }, { type: 'text', languages: ['en'] }],
  expectedOutputs: [{ type: 'text', languages: ['en'] }],
  initialPrompts: [
    { role: 'system', content: 'You are a skilled image analyst.' }
  ]
});

// Pré-processar imagens à medida que são carregadas
async function adicionarImagem(file, nota) {
  await session.append([
    {
      role: 'user',
      content: [
        { type: 'text', value: `Image with note: ${nota}` },
        { type: 'image', value: file }
      ]
    }
  ]);
}

// Quando todas estiverem carregadas, solicitar análise
async function analisar(pergunta) {
  return await session.prompt(pergunta);
}

Boas práticas pra inputs multimodais

  1. Sempre verifique disponibilidade com as mesmas modalidades que vai usar em create()
  2. Áudio exige GPU — tenha sempre um fallback pra dispositivos sem GPU
  3. Imagens menores processam mais rápido — redimensione antes de enviar
  4. Use append() pra dados pesados — evita que o usuário fique esperando na hora da análise
  5. Declare só modalidades que vai usar — não peça áudio se só precisa de imagem
  6. Trate NotSupportedError — pode rolar se o modelo encontrar input que não suporta

Perguntas frequentes

Quais formatos de imagem são suportados?

Qualquer formato que o Chrome renderize: JPEG, PNG, WebP, GIF (primeiro frame), SVG, AVIF. O formato é determinado pelo tipo do Blob ou pelo elemento HTML.

Qual o tamanho máximo de imagem ou áudio?

Não há limite documentado de tamanho de arquivo, mas inputs maiores consomem mais tokens da context window e levam mais tempo para processar. Recomenda-se limitar imagens a 2-4 MB e áudio a 30 segundos.

Posso usar a Prompt API para transcrever áudio longo (podcasts, reuniões)?

O Gemini Nano é um modelo compacto. Para áudio longo, divida em segmentos de 15-30 segundos e processe sequencialmente. Para transcrição de alta qualidade de áudio longo, considere APIs cloud dedicadas.

O áudio precisa estar em um formato específico?

A API aceita qualquer formato decodificável como AudioBuffer, ArrayBuffer ou Blob. Os formatos mais comuns (WAV, MP3, WebM/Opus, OGG) são aceitos. O Chrome decodifica internamente.

Por que áudio requer GPU mas imagem não?

O processamento de áudio pelo Gemini Nano envolve mais operações de tensor paralelas que se beneficiam exclusivamente da GPU. Imagens podem ser processadas com decodificação mais simples que o CPU consegue manejar dentro dos limites de performance aceitáveis.


Referências