Enviando Prompts — Guia de prompt() e promptStreaming()

O coração da Prompt API são os métodos session.prompt() e session.promptStreaming(). Ambos mandam conteúdo ao Gemini Nano pra inferência local, mas diferem em como a resposta chega até você. Vamos ver cada um em detalhe.

prompt() vs promptStreaming()

Característica prompt() promptStreaming()
Retorno Promise<string> ReadableStream
Entrega Resposta completa de uma vez Chunks incrementais
Uso ideal Respostas curtas, classificação, extração Respostas longas, UX interativa
Cancelamento Via signal (AbortController) Via signal (AbortController)
Tempo até primeira resposta Espera geração completa Imediato (primeiro chunk)

Quando usar prompt()

Use prompt() quando:

  • A resposta esperada é curta (classificação, extração, sim/não)
  • Você precisa do resultado completo antes de processar (parsing JSON)
  • A latência total é aceitável pro caso de uso
  • Você usa responseConstraint com JSON Schema
const session = await LanguageModel.create();

// Classificação binária — resposta curta
const resultado = await session.prompt(
  'Is the following text spam? Answer only "yes" or "no": ' +
  '"Buy now! Limited offer! Click here for free prizes!"'
);
console.log(resultado); // "yes"

Quando usar promptStreaming()

Use promptStreaming() quando:

  • A resposta pode ser longa (textos, explicações)
  • Você quer mostrar progresso ao usuário em tempo real
  • A percepção de velocidade importa pra UX
  • O usuário pode querer cancelar durante a geração
const session = await LanguageModel.create();

const stream = session.promptStreaming(
  'Explain the differences between REST and GraphQL APIs.'
);

const outputEl = document.getElementById('output');
outputEl.textContent = '';

for await (const chunk of stream) {
  outputEl.textContent += chunk;
}

Anatomia do content

O parâmetro content aceita dois formatos:

String simples

O formato mais direto — uma string é tratada como mensagem do role user:

const resposta = await session.prompt('What is machine learning?');

Array de mensagens

Pra conversas multi-turno ou quando você precisa de contexto adicional:

const resposta = await session.prompt([
  {
    role: 'user',
    content: 'What is the weather like in São Paulo?'
  },
  {
    role: 'assistant',
    content: 'I don\'t have access to real-time weather data.'
  },
  {
    role: 'user',
    content: 'Then tell me what the typical climate is like.'
  }
]);

Roles disponíveis

Role O que faz Onde usar
system Define comportamento/persona do modelo Sempre na posição 0 — no initialPrompts, ou como primeira mensagem do primeiro append() ou prompt()
user Mensagem do usuário Em prompt(), promptStreaming(), initialPrompts, append()
assistant Resposta anterior do modelo Contexto de conversa, prefixing

A regra do system é posicional, não de método. Ele pode ser a primeira mensagem de qualquer uma das três formas, mas se aparecer em qualquer outra posição a chamada rejeita com TypeError:

// ✅ Os três caminhos válidos
await LanguageModel.create({
  initialPrompts: [{ role: 'system', content: 'Você é um assistente técnico.' }],
});

const s2 = await LanguageModel.create();
await s2.append([{ role: 'system', content: 'Você é um assistente técnico.' }]);

const s3 = await LanguageModel.create();
await s3.prompt([
  { role: 'system', content: 'Você é um assistente técnico.' },
  { role: 'user', content: 'O que é WebAssembly?' },
]);

// ❌ TypeError: system não está na posição 0
await s3.prompt([
  { role: 'user', content: 'Oi' },
  { role: 'system', content: 'Você é um assistente.' },
]);

Uma consequência importante: como o system prompt é preservado em overflow de contexto, ele não pode ser usado na forma de multi-mensagem quando você está customizando roles para mediação. Nesse cenário, passe a persona no initialPrompts do create() e use só user/assistant no prompt().

Content multimodal (array de partes)

Quando o content de uma mensagem mistura tipos (texto + imagem, por exemplo), use um array de objetos:

const resposta = await session.prompt([
  {
    role: 'user',
    content: [
      { type: 'text', value: 'Describe this image in detail:' },
      { type: 'image', value: document.getElementById('minha-imagem') }
    ]
  }
]);

Cada parte tem:

  • type: 'text', 'image' ou 'audio'
  • value: O conteúdo correspondente (string pra texto, elemento/blob pra mídia)

Prefixing: guiando o formato de resposta

O recurso de prefix permite iniciar a resposta do modelo com um texto específico, guiando o formato de saída:

const resposta = await session.prompt([
  {
    role: 'user',
    content: 'Create a JSON object with name, age, and city for a fictional person'
  },
  {
    role: 'assistant',
    content: '```json\n{',
    prefix: true
  }
]);

console.log('{' + resposta); // A resposta continua de onde o prefix parou

O prefix: true na última mensagem assistant diz pro modelo continuar a partir daquele ponto, não gerar uma nova resposta. Na prática é útil pra:

  • Forçar formato de saída (JSON, TOML, Markdown)
  • Garantir que a resposta começa com determinado texto
  • Guiar o modelo pra respostas mais estruturadas

Cancelamento com AbortController

Ambos os métodos suportam cancelamento via AbortSignal:

const controller = new AbortController();

// Timeout de 5 segundos
setTimeout(() => controller.abort(), 5000);

try {
  const resposta = await session.prompt('Write a detailed essay.', {
    signal: controller.signal
  });
} catch (error) {
  if (error.name === 'AbortError') {
    console.log('Prompt cancelado (timeout)');
  }
}

Cancelamento interativo com streaming

const controller = new AbortController();

document.getElementById('btn-parar').addEventListener('click', () => {
  controller.abort();
});

const stream = session.promptStreaming('Write a long story.', {
  signal: controller.signal
});

try {
  for await (const chunk of stream) {
    document.getElementById('output').textContent += chunk;
  }
} catch (error) {
  if (error.name === 'AbortError') {
    document.getElementById('output').textContent += '\n[Geração interrompida]';
  }
}

Padrões práticos

Classificação com resposta booleana

const session = await LanguageModel.create({
  initialPrompts: [
    { role: 'system', content: 'You are a content classifier. Answer only true or false.' }
  ]
});

async function classificar(texto, criterio) {
  const resposta = await session.prompt(
    `Does the following text match this criteria: "${criterio}"?\n\nText: ${texto}`
  );
  return resposta.trim().toLowerCase() === 'true';
}

const isSpam = await classificar(
  'Buy cheap watches now!!!',
  'spam or unsolicited commercial content'
);
// true

Extração de dados com prompt estruturado

const session = await LanguageModel.create({
  initialPrompts: [
    {
      role: 'system',
      content: 'Extract information and respond in the exact format requested. No additional text.'
    }
  ]
});

const email = 'Hi, I\'m John Smith from Acme Corp. Call me at 555-0123.';

const resposta = await session.prompt(
  `Extract name, company, and phone from this text. ` +
  `Respond as JSON: {"name":"","company":"","phone":""}\n\n${email}`
);

const dados = JSON.parse(resposta);
// { name: "John Smith", company: "Acme Corp", phone: "555-0123" }

Streaming com renderização Markdown

async function streamComMarkdown(prompt) {
  const session = await LanguageModel.create();
  const stream = session.promptStreaming(prompt);

  const container = document.getElementById('output');
  let textoAcumulado = '';

  for await (const chunk of stream) {
    textoAcumulado += chunk;
    container.innerHTML = marked.parse(textoAcumulado);
  }
}

Retry com backoff

async function promptComRetry(session, texto, maxTentativas = 3) {
  for (let tentativa = 1; tentativa <= maxTentativas; tentativa++) {
    try {
      return await session.prompt(texto);
    } catch (error) {
      if (error.name === 'AbortError') throw error; // Não retry em abort

      if (tentativa === maxTentativas) throw error;

      // Backoff exponencial
      await new Promise(r => setTimeout(r, 1000 * Math.pow(2, tentativa)));
    }
  }
}

Múltiplos prompts em sequência

A sessão mantém contexto entre chamadas de prompt():

const session = await LanguageModel.create({
  initialPrompts: [
    { role: 'system', content: 'You are a helpful tutor.' }
  ]
});

// Primeira pergunta
const r1 = await session.prompt('What is recursion in programming?');
console.log(r1);

// Segunda pergunta — o modelo lembra da conversa
const r2 = await session.prompt('Can you give me an example in JavaScript?');
console.log(r2); // Vai dar um exemplo de recursão, porque lembra do contexto

// Terceira pergunta
const r3 = await session.prompt('What are the risks of using it?');
console.log(r3); // Vai falar sobre stack overflow e riscos de recursão

Opções do segundo parâmetro

Tanto prompt() quanto promptStreaming() aceitam um segundo parâmetro com opções:

const resposta = await session.prompt(content, {
  signal: controller.signal,           // AbortSignal pra cancelamento
  responseConstraint: jsonSchema,       // JSON Schema ou regex
  omitResponseConstraintInput: false    // Se true, não inclui schema no contexto
});
Opção Tipo O que faz
signal AbortSignal Permite cancelar a operação
responseConstraint object ou RegExp Força formato de resposta (JSON Schema)
omitResponseConstraintInput boolean Não consome tokens de contexto com o schema
thinking object Configura raciocínio estendido (effort, includeThoughts)

Thinking mode: raciocínio estendido

Modelos modernos geram tokens de raciocínio intermediários antes da resposta final. Isso melhora precisão em matemática, lógica, geração de código e planejamento multi-step — ao custo de latência e bateria.

O thinking é configurado com effort e, opcionalmente, includeThoughts:

effort Comportamento Use para
"none" Sem raciocínio intermediário (padrão) Tarefas simples
"low" Orçamento mínimo de raciocínio Multi-step direto
"medium" Equilíbrio entre precisão e latência Padrão para tarefas intermediárias
"high" Orçamento máximo Matemática, lógica, planejamento

Você define um nível base na sessão e sobrescreve por chamada:

// Verificar suporte antes de criar a sessão
const status = await LanguageModel.availability({
  thinking: { effort: 'high' },
});

if (status !== 'unavailable') {
  const session = await LanguageModel.create({
    thinking: { effort: 'high' },
  });

  // Turno 1: usa o padrão da sessão ("high") — tarefa complexa
  const code = await session.prompt(
    'Escreva uma função para achar o caminho mais curto em um grafo ponderado.',
  );

  // Turno 2: sobrescreve para "none" — tarefa simples, economiza latência
  const doc = await session.prompt('Adicione comentários JSDoc.', {
    thinking: { effort: 'none' },
  });
}

Vendo os Thoughts intermediários

Por padrão includeThoughts é false, então você recebe só a resposta final. Com includeThoughts: true, prompt() e promptStreaming() passam a emitir dicionários { type, value } em vez de strings, com type sendo "thought" ou "text":

const session = await LanguageModel.create({
  thinking: {
    effort: 'medium',
    includeThoughts: true,
  },
});

const stream = session.promptStreaming('Monte um itinerário de 3 dias em Tóquio.');

for await (const chunk of stream) {
  // Chunks de thought e text podem conter Markdown
  if (chunk.type === 'thought') {
    thinkingContainer.append(chunk.value);
  } else if (chunk.type === 'text') {
    responseContainer.append(chunk.value);
  }
}

⚠️ Atenção ao breaking change de tipo. Todo código que assume string do prompt() ou promptStreaming() quebra se includeThoughts: true. Com o padrão false, o comportamento é o de sempre — string.

Os tokens de pensamento são removidos do histórico da conversa depois de cada turno, então não acumulam em session.contextUsage. O user agent mapeia cada nível de effort para um orçamento de raciocínio do modelo subjacente; o modelo para de pensar quando atinge o teto ou quando chega a uma conclusão.

Tool use: dando ferramentas ao modelo

Além de texto, o modelo pode invocar funções que você define. Isso transforma a Prompt API de “responde texto” em “age sobre o seu app”.

Uma tool é um objeto com name, description, inputSchema e execute. Quando o modelo decide usar a ferramenta, o user agent chama o execute e devolve o resultado para o modelo:

const session = await LanguageModel.create({
  initialPrompts: [
    {
      role: 'system',
      content: 'Você é um assistente útil. Use ferramentas para ajudar o usuário.',
    },
  ],
  // tool-response e tool-call precisam ser declarados explicitamente
  expectedInputs: [
    { type: 'text', languages: ['en'] },
    { type: 'tool-response' },
  ],
  expectedOutputs: [
    { type: 'text', languages: ['en'] },
    { type: 'tool-call' },
  ],
  tools: [
    {
      name: 'getWeather',
      description: 'Retorna o clima de uma localização.',
      inputSchema: {
        type: 'object',
        properties: {
          location: {
            type: 'string',
            description: 'A cidade para consultar a condição do tempo.',
          },
        },
        required: ['location'],
      },
      async execute({ location }) {
        const res = await fetch(`https://api.exemplo.com/clima?cidade=${location}`);
        // Retorna o resultado como string JSON
        return JSON.stringify(await res.json());
      },
    },
  ],
});

const result = await session.prompt('Como está o clima em Seattle?');

Sem declarar { type: 'tool-response' } em expectedInputs e { type: 'tool-call' } em expectedOutputs, a sessão não suporta tools.

Uso concorrente

O modelo pode chamar a mesma tool várias vezes em paralelo. Não assuma execução sequencial:

const result = await session.prompt(
  'Qual dessas cidades tem a temperatura mais alta agora? Seattle, Tóquio, Berlim',
);
// pode chamar getWeather('Seattle'), getWeather('Tóquio') e getWeather('Berlim')
// em paralelo, aguardando os três antes de compor a resposta

O user agent usa algo equivalente a Promise.all() internamente. Isso significa que seu execute precisa ser idempotente e seguro para execução concorrente — e você deve tratar duplicação de requests (cache por argumento) para não disparar chamadas repetidas à mesma API.


Perguntas frequentes

O streaming retorna a resposta completa acumulada ou só os chunks novos?

O promptStreaming() retorna apenas os novos chunks a cada iteração. Você precisa acumular manualmente se quiser o texto completo.

Posso enviar múltiplos prompts ao mesmo tempo na mesma sessão?

Não é recomendado. Cada sessão processa um prompt por vez. Pra processamento paralelo, crie múltiplas sessões ou use session.clone().

Qual o tamanho máximo de um prompt?

Depende da contextWindow da sessão. Use session.contextWindow pra ver o máximo e session.contextUsage pra saber quanto já foi consumido. Se o prompt exceder o espaço disponível, um QuotaExceededError é lançado.

O prompt() pode retornar resposta vazia?

Sim, em casos raros o modelo pode não gerar conteúdo útil. Sempre valide a resposta antes de usar em produção.


Referências