Pular para o conteúdo
Cursos

DevClub

LógicaFront-endBack-endMobile

IA Club

IA na prática
Estudar programaçãoEstudar IA
LiçãoIntermediáriocódigo testado

Few-shot prompting: ensine pelo exemplo, sem confusão

Aprenda a selecionar, ordenar e validar exemplos few-shot, detectar vazamento de rótulo, controlar tokens e repetir avaliações antes de publicar.

Rodolfo Mori7 min de leitura

Few-shot prompting não é encher o prompt com conversas que “parecem boas”. É escolher uma pequena tabela de decisões capaz de mostrar ao modelo onde estão as fronteiras da tarefa. Um exemplo pode esclarecer uma regra; uma coleção enviesada pode criar outra regra, invisível e errada.

Esta lição começa onde a introdução à engenharia de prompt termina. Não vamos repetir a anatomia básica de tarefa, contexto, regras e formato. O foco agora é tratar exemplos como dados versionados: selecionar, ordenar, auditar o que vazou, limitar custo e avaliar a mesma mudança mais de uma vez. Se você prefere percorrer o assunto em sequência, o curso de engenharia de prompts organiza a prática do básico ao intermediário.

Todo código desta página foi executado em conjunto no Node 26.3.0. Ele monta e verifica texto de forma determinística. Não houve chamada a modelo, API ou SDK de IA, e os resultados exibidos são saídas do programa local — não resultados atribuídos a um LLM.

Selecione fronteiras, não frases bonitas

Para classificar mensagens de atendimento, quatro exemplos quase iguais de “quero trocar” ocupam contexto sem explicar muito. Um conjunto melhor varia o significado, não apenas o vocabulário. Ele inclui um pedido claro, uma paráfrase e casos que contêm palavras tentadoras, mas pertencem a outra classe.

O conjunto abaixo guarda faceta e papel como metadados de curadoria. O montador não envia esses dois campos ao prompt: eles servem para a equipe saber por que cada item existe.

js
import assert from "node:assert/strict";
import { createHash } from "node:crypto";

const ROTULOS = ["cancelamento", "troca", "duvida"];
const exemplos = [
  {
    id: "cancelar-pedido",
    entrada: "Quero cancelar o pedido 81.",
    saida: { rotulo: "cancelamento", precisaDeHumano: false },
    faceta: "pedido explicito",
    papel: "positivo",
  },
  {
    id: "troca-tamanho",
    entrada: "O tenis ficou pequeno; posso escolher outro numero?",
    saida: { rotulo: "troca", precisaDeHumano: false },
    faceta: "troca sem a palavra troca",
    papel: "positivo",
  },
  {
    id: "cancelar-cartao",
    entrada: "Nao reconheco a cobranca e quero cancelar o cartao.",
    saida: { rotulo: "duvida", precisaDeHumano: true },
    faceta: "cancelar nao se refere ao pedido",
    papel: "contraexemplo",
  },
  {
    id: "prazo-de-troca",
    entrada: "Qual e o prazo para trocar um presente?",
    saida: { rotulo: "duvida", precisaDeHumano: false },
    faceta: "pergunta sobre politica",
    papel: "contraexemplo",
  },
];

function normalizar(texto) {
  return texto.normalize("NFKD").replace(/\p{Diacritic}/gu, "").toLowerCase().trim();
}

Os dois contraexemplos não são respostas erradas. Cada um traz a saída correta para um caso próximo da fronteira: “cancelar o cartão” não significa cancelar o pedido, e perguntar sobre o prazo de troca não é solicitar uma troca. Esse tipo de contraste combate atalhos lexicais.

Diversidade também precisa acompanhar o uso real. Idioma, tamanho, canal, abreviação e presença de ruído podem importar, mas não inclua uma variação só para completar uma lista. Primeiro observe falhas ou uma distribuição relevante; depois registre qual faceta o novo exemplo cobre. Dados pessoais devem ser removidos ou substituídos antes de entrar no repositório.

Valide o conjunto antes de montar o prompt

Uma revisão automática não decide se o exemplo é pedagogicamente bom, mas pega defeitos objetivos. O validador local recusa identificadores e entradas duplicados, rótulos desconhecidos, classes sem representação e conjuntos sem contraexemplo.

js
function validarExemplos(itens) {
  const erros = [];
  const ids = new Set();
  const entradas = new Set();
  const presentes = new Set();

  for (const item of itens) {
    if (ids.has(item.id)) erros.push(`id duplicado: ${item.id}`);
    ids.add(item.id);

    const entrada = normalizar(item.entrada);
    if (entradas.has(entrada)) erros.push(`entrada duplicada: ${item.id}`);
    entradas.add(entrada);

    if (!ROTULOS.includes(item.saida.rotulo)) {
      erros.push(`rotulo desconhecido: ${item.id}`);
    }
    presentes.add(item.saida.rotulo);
  }

  for (const rotulo of ROTULOS) {
    if (!presentes.has(rotulo)) erros.push(`rotulo sem exemplo: ${rotulo}`);
  }
  if (!itens.some((item) => item.papel === "contraexemplo")) {
    erros.push("falta contraexemplo");
  }
  return erros;
}

const errosSelecao = validarExemplos(exemplos);
assert.deepEqual(errosSelecao, []);
console.log(JSON.stringify({
  exemplos: exemplos.length,
  rotulos: new Set(exemplos.map((item) => item.saida.rotulo)).size,
  contraexemplos: exemplos.filter((item) => item.papel === "contraexemplo").length,
}));
{"exemplos":4,"rotulos":3,"contraexemplos":2}

Cobrir cada rótulo uma vez é um piso, não uma amostra representativa. Se 70% dos casos reais são dúvidas, um conjunto perfeitamente equilibrado pode ocultar o comportamento mais frequente; se a classe rara traz o maior risco, seguir apenas a frequência também é ruim. Mantenha uma justificativa por exemplo e meça os resultados por classe, não apenas pela média total.

Remova vazamento de rótulo da entrada

Vazamento acontece quando o material de origem carrega a resposta em uma coluna, prefixo ou nome de arquivo que não existirá em produção. “Categoria correta: troca” torna a demonstração fácil demais. O problema não é a palavra “troca” numa fala natural; é o marcador de anotação ter sido copiado para a entrada.

js
function encontrarVazamento(itens) {
  const marcador = /\b(?:rotulo|categoria correta|label)\s*:/i;
  return itens
    .filter((item) => marcador.test(normalizar(item.entrada)))
    .map((item) => item.id);
}

const exemploContaminado = {
  ...exemplos[1],
  id: "vazamento-demo",
  entrada: "Categoria correta: troca. Cliente: o tenis ficou pequeno.",
};
assert.deepEqual(encontrarVazamento(exemplos), []);
assert.deepEqual(encontrarVazamento([...exemplos, exemploContaminado]), ["vazamento-demo"]);
console.log(JSON.stringify({ vazamentos: encontrarVazamento([...exemplos, exemploContaminado]) }));
{"vazamentos":["vazamento-demo"]}

Essa expressão regular é um alarme específico, não uma defesa completa. Uma planilha pode vazar o rótulo por cor, ordem, pasta ou identificador. Faça também uma revisão manual comparando a entrada preparada com o dado que o produto realmente recebe. Separe o conjunto de exemplos do conjunto de avaliação: usar o mesmo item nos dois lados mede memorização do contexto, não generalização.

A ordem é uma variável do experimento

Se todos os exemplos de uma classe aparecem juntos, o prompt cria um padrão de posição além do padrão semântico. Intercalar rótulos reduz sequências óbvias. O algoritmo a seguir escolhe primeiro uma fila mais cheia sem repetir o rótulo anterior quando existe alternativa.

js
function intercalarPorRotulo(itens) {
  const filas = new Map(ROTULOS.map((rotulo) => [rotulo, []]));
  for (const item of itens) filas.get(item.saida.rotulo).push(item);

  const resultado = [];
  let anterior = null;
  while (resultado.length < itens.length) {
    const candidatas = ROTULOS
      .filter((rotulo) => filas.get(rotulo).length > 0)
      .sort((a, b) => filas.get(b).length - filas.get(a).length);
    const proximo = candidatas.find((rotulo) => rotulo !== anterior) ?? candidatas[0];
    resultado.push(filas.get(proximo).shift());
    anterior = proximo;
  }
  return resultado;
}

function maiorSequenciaDeRotulo(itens) {
  let maior = 0;
  let atual = 0;
  let anterior = null;
  for (const item of itens) {
    atual = item.saida.rotulo === anterior ? atual + 1 : 1;
    anterior = item.saida.rotulo;
    maior = Math.max(maior, atual);
  }
  return maior;
}

const exemplosOrdenados = intercalarPorRotulo(exemplos);
assert.equal(maiorSequenciaDeRotulo(exemplosOrdenados), 1);
console.log(JSON.stringify({ ordem: exemplosOrdenados.map((item) => item.id), maiorSequencia: 1 }));
{"ordem":["cancelar-cartao","cancelar-pedido","troca-tamanho","prazo-de-troca"],"maiorSequencia":1}

Intercalar não descobre a ordem universalmente melhor. Posição recente, primeiro exemplo e agrupamentos podem afetar modelos de formas diferentes. A decisão madura é versionar uma ordem inicial e depois testar rotações. Se uma melhoria desaparece ao inverter quatro itens, ela não está pronta para ser tratada como ganho estável.

Monte um prefixo estável e deixe o caso atual no fim

O montador serializa somente entrada e saída. faceta e papel continuam no arquivo de curadoria, sem oferecer pistas extras. Também escapa delimitadores para que texto recebido não quebre a estrutura. Isso ajuda a leitura, embora não transforme XML em barreira de segurança.

js
function escaparXml(valor) {
  return valor.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
}

function montarPrompt(itens, entrada) {
  const exemplosXml = itens.map((item, indice) => [
    `<exemplo id="${indice + 1}">`,
    `<entrada>${escaparXml(item.entrada)}</entrada>`,
    `<saida>${escaparXml(JSON.stringify(item.saida))}</saida>`,
    "</exemplo>",
  ].join("\n")).join("\n");

  return [
    "<objetivo>Classifique uma solicitacao de atendimento.</objetivo>",
    "<regras>Use somente os rotulos permitidos. Nao invente dados.</regras>",
    `<rotulos>${ROTULOS.join(" | ")}</rotulos>`,
    `<exemplos>\n${exemplosXml}\n</exemplos>`,
    '<formato>{"rotulo":"string","precisaDeHumano":"boolean"}</formato>',
    `<entrada_atual>${escaparXml(entrada)}</entrada_atual>`,
  ].join("\n\n");
}

O conteúdo estático vem antes e a entrada variável ocupa o final. Essa ordem também favorece mecanismos de cache de prefixo quando o provedor oferece o recurso. O teste abaixo confirma quatro exemplos e impede que algum campo seja acrescentado depois do dado atual por acidente.

js
const entradaAtual = "O produto veio com defeito; quero receber outro.";
const prompt = montarPrompt(exemplosOrdenados, entradaAtual);
assert.equal((prompt.match(/<exemplo id=/g) ?? []).length, exemplos.length);
assert.ok(prompt.endsWith(`</entrada_atual>`));
console.log(JSON.stringify({ caracteres: prompt.length, exemplos: exemplos.length, entradaPorUltimo: true }));
{"caracteres":959,"exemplos":4,"entradaPorUltimo":true}

O JSON mostrado dentro de cada exemplo ensina a forma, mas não garante um objeto válido na execução real. Quando a aplicação depende desse contrato, use validação no código e o recurso apropriado descrito em Structured Outputs.

Conte tokens como orçamento, não como detalhe

Exemplos entram no contexto de cada execução. Dez itens excelentes ainda podem ser a escolha errada se aumentarem latência, preço ou ocuparem o espaço necessário para os dados do usuário. O estimador didático abaixo usa quatro caracteres por token. Ele serve apenas como alarme relativo; o total de cobrança deve vir do tokenizador e dos dados de uso do modelo escolhido.

js
function estimarTokens(texto) {
  return Math.ceil([...texto].length / 4);
}

const semExemplos = montarPrompt([], entradaAtual);
const custo = {
  semExemplos: estimarTokens(semExemplos),
  comExemplos: estimarTokens(prompt),
};
custo.delta = custo.comExemplos - custo.semExemplos;
assert.ok(custo.delta > 0);
console.log(JSON.stringify(custo));
{"semExemplos":90,"comExemplos":240,"delta":150}

Neste montador, quatro exemplos acrescentaram aproximadamente 150 tokens de entrada. Isso não diz se eles “valem a pena”. Para decidir, compare o ganho em casos relevantes com tokens, latência e falhas por classe. Remova um item de cada vez; se a métrica permanece dentro do limite, o exemplo era redundante. Entender a janela de contexto em o que é um LLM evita confundir espaço disponível com qualidade.

Repita a avaliação e varie a ordem

Uma única execução mistura o efeito do prompt com a variabilidade da geração. Antes de publicar, fixe casos que não aparecem nos exemplos e execute cada versão várias vezes. Registre o texto ou hash do prompt, ordem dos exemplos, modelo, parâmetros, tokens, latência e resultado por critério.

Primeiro criamos três ordens determinísticas e provamos que elas geram prompts diferentes. Os hashes são identificadores locais, não notas de qualidade.

js
function rotacionar(itens, casas) {
  const n = casas % itens.length;
  return [...itens.slice(n), ...itens.slice(0, n)];
}

const ordensDeAvaliacao = [
  exemplosOrdenados,
  rotacionar(exemplosOrdenados, 1),
  [...exemplosOrdenados].reverse(),
];
const hashes = ordensDeAvaliacao.map((ordem) =>
  createHash("sha256").update(montarPrompt(ordem, entradaAtual)).digest("hex").slice(0, 10)
);
assert.equal(new Set(hashes).size, 3);
console.log(JSON.stringify({ variantesDeOrdem: hashes.length, hashes }));
{"variantesDeOrdem":3,"hashes":["aacf69b64e","6ac64b5f6c","14ae71a20e"]}

Agora o harness repete os mesmos quatro casos três vezes. Para executar a demonstração sem rede, injetamos uma regra determinística chamada executorLocalDeContrato. Ela não imita nem representa um modelo; só prova que o agendamento e o cálculo de acertos funcionam. Num teste de produto, a função executar seria substituída pelo caminho real, com consentimento, orçamento e registro da versão usada.

js
const casosDeContrato = [
  { entrada: "Quero cancelar o pedido 90.", esperado: "cancelamento" },
  { entrada: "O tenis apertou; preciso de outro numero.", esperado: "troca" },
  { entrada: "Qual e o prazo para troca?", esperado: "duvida" },
  { entrada: "Nao reconheco esta cobranca.", esperado: "duvida" },
];

function executorLocalDeContrato(entrada) {
  const texto = normalizar(entrada);
  if (/prazo|cobranca|cartao/.test(texto)) return "duvida";
  if (/outro numero|trocar o produto/.test(texto)) return "troca";
  if (/cancelar o pedido/.test(texto)) return "cancelamento";
  return "duvida";
}

async function avaliarRepeticoes({ casos, repeticoes, executar }) {
  const rodadas = [];
  for (let rodada = 1; rodada <= repeticoes; rodada += 1) {
    let acertos = 0;
    for (const caso of casos) {
      if (await executar(caso.entrada) === caso.esperado) acertos += 1;
    }
    rodadas.push({ rodada, acertos, total: casos.length });
  }
  return rodadas;
}

const rodadas = await avaliarRepeticoes({
  casos: casosDeContrato,
  repeticoes: 3,
  executar: executorLocalDeContrato,
});
assert.ok(rodadas.every((rodada) => rodada.acertos === 4));
console.log(JSON.stringify(rodadas));
[{"rodada":1,"acertos":4,"total":4},{"rodada":2,"acertos":4,"total":4},{"rodada":3,"acertos":4,"total":4}]

O 4/4 confirma somente o executor local e o harness. Não é evidência sobre a qualidade de qualquer modelo. Para uma avaliação real, mantenha os rótulos esperados fora do prompt, execute todas as ordens e repetições, e compare a pior classe e a variação entre rodadas. A lição de avaliação de aplicações de IA aprofunda graders, métricas e barreiras de publicação.

Um mapa para decidir a próxima mudança

Use o sintoma medido para escolher a intervenção. Acrescentar exemplos por intuição costuma transformar o prompt num arquivo de exceções difícil de manter.

Sintoma observado Mudança pequena para testar Sinal de alerta
Confusão entre duas classes Um caso de fronteira com saída correta Exemplo contém a palavra do rótulo como pista artificial
Formato varia Padronizar a forma de todas as saídas Misturar JSON, prosa e listas no mesmo conjunto
Só uma ordem funciona Rodar rotações e intercalar classes Escolher a melhor rodada e esconder as demais
Custo cresce sem ganho Remover um exemplo por vez Manter duplicatas “por segurança”
Classe rara falha Adicionar caso raro representativo Otimizar apenas a acurácia média

O ciclo termina quando cada exemplo possui uma razão registrada, o prompt cabe no orçamento, nenhuma pista impossível vazou para a entrada e a melhora aparece em casos separados sob várias ordens e rodadas. Few-shot prompting fica muito mais previsível quando deixa de ser coleção de frases e passa a ser uma parte pequena, auditável e removível do sistema.

  • few-shot prompting
  • prompt engineering
  • llm
  • exemplos de prompt
  • avaliacao de ia

Perguntas frequentes

Quantos exemplos few-shot devo colocar no prompt?
Não existe um número universal. Comece com o menor conjunto que represente as fronteiras importantes, meça nos mesmos casos e acrescente um exemplo somente quando ele corrigir uma falha sem criar regressões ou custo inútil.
A ordem dos exemplos few-shot muda o resultado?
Pode mudar. Compare ordens intercaladas, rotações e a ordem inversa nos mesmos casos. Se uma versão só funciona numa sequência, ela ainda é frágil.
Contraexemplo é uma resposta errada mostrada ao modelo?
Não. É um caso próximo da fronteira, acompanhado da saída correta, que impede uma regra superficial. Mostrar uma resposta propositalmente errada sem explicação pode ensinar exatamente o comportamento indesejado.
O que é vazamento de rótulo em few-shot prompting?
É deixar na entrada uma pista que não existirá no uso real, como "categoria correta: troca". A avaliação parece ótima, mas mede a leitura da pista, não a capacidade de resolver a tarefa.
Few-shot prompting substitui uma avaliação de IA?
Não. Os exemplos fazem parte do prompt; os casos de avaliação devem ficar separados. Execute várias rodadas, registre versão, ordem, custo e falhas antes de comparar prompts ou modelos.

Dúvidas e comentários

Travou em algum passo? Pergunte aqui — a equipe e outros alunos respondem.

Todo o código deste artigo foi executado em Node 26.3.0; montador, validador, casos de entrada e harness determinístico executados localmente; nenhuma chamada a modelo ou API, e as saídas exibidas são as reais — como produzimos este conteúdo.

Fontes consultadas

  1. OpenAI Docs — Prompt engineering e few-shot learning — developers.openai.com
  2. Anthropic Docs — Use examples effectively — platform.claude.com
  3. Google AI for Developers — Prompt design strategies — ai.google.dev

Continue por aqui