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:
| Modalidade | Requisito de hardware |
|---|---|
| Texto | GPU >4GB ou CPU 16GB RAM + 4 cores |
| Imagem | GPU >4GB ou CPU 16GB RAM + 4 cores |
| Áudio | GPU 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:
| Tipo | Descrição | Caso de uso |
|---|---|---|
HTMLImageElement | Tag <img> do DOM | Imagens já carregadas na página |
SVGImageElement | Elemento SVG <image> | Gráficos vetoriais com imagem embutida |
HTMLVideoElement | Tag <video> (frame atual) | Análise de frame de vídeo |
HTMLCanvasElement | Canvas 2D | Desenhos, capturas, processamento |
ImageBitmap | Bitmap decodificado | Alto desempenho, workers |
OffscreenCanvas | Canvas off-screen | Processamento sem DOM |
VideoFrame | Frame de vídeo WebCodecs | Processamento de vídeo avançado |
Blob | Arquivo binário (JPEG, PNG, WebP) | Upload de arquivo, fetch |
ImageData | Pixels RGBA crus | Manipulaçã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
| Tipo | Descrição | Caso de uso |
|---|---|---|
AudioBuffer | Buffer de áudio Web Audio API | Gravação de microfone processada |
ArrayBufferView | Vista tipada de buffer (Uint8Array, Float32Array) | Dados de áudio crus |
ArrayBuffer | Buffer binário | Arquivo de áudio carregado |
Blob | Arquivo 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
- Sempre verifique disponibilidade com as mesmas modalidades que vai usar em
create() - Áudio exige GPU — tenha sempre um fallback pra dispositivos sem GPU
- Imagens menores processam mais rápido — redimensione antes de enviar
- Use
append()pra dados pesados — evita que o usuário fique esperando na hora da análise - Declare só modalidades que vai usar — não peça áudio se só precisa de imagem
- 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.