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.
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.
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.
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,
}));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.
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]) }));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.
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 }));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.
function escaparXml(valor) {
return valor.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
}
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.
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 }));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.
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));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.
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 }));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.
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));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.
Perguntas frequentes
Quantos exemplos few-shot devo colocar no prompt?
A ordem dos exemplos few-shot muda o resultado?
Contraexemplo é uma resposta errada mostrada ao modelo?
O que é vazamento de rótulo em few-shot prompting?
Few-shot prompting substitui uma avaliação de IA?
Dúvidas e comentários
Travou em algum passo? Pergunte aqui — a equipe e outros alunos respondem.
Entrar para perguntarÉ o mesmo login gratuito dos cursos.
Nenhuma dúvida por aqui ainda — a primeira pode ser a sua.
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
- OpenAI Docs — Prompt engineering e few-shot learning — developers.openai.com
- Anthropic Docs — Use examples effectively — platform.claude.com
- Google AI for Developers — Prompt design strategies — ai.google.dev


