O que é Votação?
Votação (Voting) é um padrão de projeto de IA para apps com LLM e agentes, do grupo Composição. Rodar a mesma tarefa várias vezes ou com vários modelos e decidir por maioria ou limiar.
Voting
Votação (Voting) é um padrão de projeto de IA para apps com LLM e agentes, do grupo Composição. Rodar a mesma tarefa várias vezes ou com vários modelos e decidir por maioria ou limiar.
Rodar a mesma tarefa várias vezes, ou com vários modelos ou prompts, e decidir por maioria ou por limiar. Serve para decisões de classificação em que um erro custa caro e uma única resposta do modelo não é confiável o bastante.
Uma banca de três juízes: uma opinião pode errar, a maioria erra menos.
Natália cuida da moderação de comentários de uma rede de cursos online. Um classificador decide se cada comentário viola a política de assédio. Em casos claros, ele acerta. Em casos de fronteira (ironia, gíria, alguém citando uma ofensa para criticá-la), a mesma mensagem às vezes é aprovada e às vezes removida, dependendo da execução.
Remover um comentário legítimo gera reclamação e apelação. Deixar um assédio no ar gera algo muito pior: um aluno exposto e uma marca na imprensa. O time quer duas coisas. Que a decisão seja estável, e que os casos realmente duvidosos cheguem a uma pessoa em vez de serem decididos no cara ou coroa.
Natália reescreveu o prompt três vezes. Cada versão melhorou os casos claros. Nos ambíguos, uma chamada só continua oscilando, porque a ambiguidade está no comentário, não no prompt.
A pergunta que este padrão responde: como tornar uma decisão mais confiável quando uma única resposta do modelo varia?
Peça a mesma decisão várias vezes, de formas independentes, e combine os votos no código. A diversidade dos votos pode vir de três fontes:
A combinação é uma regra explícita: maioria simples, unanimidade para ações graves ou um limiar (“remove se 2 de 3 votarem por remover”). A discordância entre votos é o sinal mais útil do padrão. Ela diz que o caso é difícil, e casos difíceis vão para revisão humana.
O post “Building effective agents” da Anthropic (dezembro de 2024) descreve a votação como uma das duas formas de paralelização, ao lado do Seccionamento. Os exemplos do post são os mesmos deste padrão: vários prompts revisando código em busca de vulnerabilidades e vários prompts avaliando se um conteúdo é impróprio, com limiares diferentes para equilibrar falso positivo e falso negativo.
Uma conta ilustrativa mostra o mecanismo. Suponha que cada voto acerte 90% das vezes e que os erros sejam independentes. A maioria de três só erra quando dois ou três votos erram juntos. Isso acontece em cerca de 2,8% dos casos, então a maioria acerta cerca de 97%.
A palavra que segura a conta é “independentes”. Se os três votos usam o mesmo modelo, o mesmo prompt e temperatura zero, eles são a mesma opinião repetida três vezes. Erram juntos, e a maioria continua acertando 90%. Você pagou três vezes por uma resposta.
Na prática os erros nunca são totalmente independentes. Todos os votos leem o mesmo comentário e tropeçam na mesma ironia. Por isso o ganho real é menor que o da conta, e só o eval diz quanto ele vale no seu caso.
Um aluno escreve no fórum:
Alguém me chamou de "burro" ontem no grupo da turma. Isso não é jeito de tratar colega.
Os três votos, cada um com saída estruturada:
[
{ "voter": "literal", "decision": "remove", "policy": "assedio", "rationale": "contém o insulto 'burro'" },
{ "voter": "intencao", "decision": "allow", "policy": "assedio", "rationale": "o autor relata uma ofensa, não ataca ninguém" },
{ "voter": "contexto", "decision": "allow", "policy": "assedio", "rationale": "a conversa é sobre convivência na turma" }
]
Com maioria simples, o comentário fica no ar. Com a regra de Natália (remover só com unanimidade), ele também fica, e a divisão de 1 a 2 manda o caso para a fila de revisão com as três justificativas. A moderadora decide em segundos, porque já recebe o argumento de cada lado.
Repare que a regra mudou o resultado mais que o prompt. Escolher a regra é decidir qual erro você prefere cometer.
flowchart LR
IN(["1. Comentário"]):::io --> V1(["2. Voto A"]):::llm
IN --> V2(["2. Voto B"]):::llm
IN --> V3(["2. Voto C"]):::llm
V1 --> AG{"3. Combinar votos"}
V2 --> AG
V3 --> AG
AG -->|"consenso"| OUT(["4. Decisão automática"]):::io
AG -->|"divididos"| H((("5. Revisão humana"))):::human
schema Vote { decision: "allow" | "remove", policy: string, rationale: string }
VOTERS = [
{ prompt: LITERAL_PROMPT, model: "medium" },
{ prompt: INTENT_PROMPT, model: "medium" },
{ prompt: CONTEXT_PROMPT, model: "other_provider" },
]
function moderate(comment, thread):
parallel { # 2
votes = [llm(v.prompt, comment, thread, model=v.model) -> Vote for v in VOTERS]
}
removes = count(v.decision == "remove" for v in votes) # 3
trace.span("votes", votes)
if removes == len(votes): return remove(comment, votes) # 4. unanimidade
if removes == 0: return allow(comment)
return human.review(comment, votes) # 5. divididos
Quem começa costuma pedir “responda se o comentário deve ser removido” e contar votos procurando a palavra “sim” na resposta. Um voto diz “Sim, mas com ressalvas”, outro diz “Removeria”, outro “Não vejo problema grave”. A contagem vira interpretação.
Cada voto precisa de uma Saída Estruturada com valores fechados. A justificativa vai num campo separado, para a pessoa que revisa, e nunca entra na contagem.
Uma conta ilustrativa da votação adaptativa: suponha 100 mil comentários por dia e que os dois primeiros votos concordem em 85% deles. Você paga 200 mil chamadas pelos dois primeiros votos e mais 15 mil pelo terceiro. São 215 mil chamadas, contra 300 mil com três votos sempre.
| Aspecto | O que acontece | Como aparece no trace |
|---|---|---|
| Chamadas | Uma por voto | spans vote irmãos |
| Custo | Multiplicado pelo número de votos | custo por item comparado com uma chamada |
| Latência | A do voto mais lento, se em paralelo | spans sobrepostos |
| Falha: votos correlacionados | Mesmo prompt e modelo erram juntos | unanimidade em casos que o eval marca como errados |
| Falha: divisão demais | Prompts muito diferentes nunca concordam | taxa de revisão humana acima da capacidade |
| Falha: custo sem ganho | Casos claros pagam três votos | unanimidade em quase todos os itens |
Um caso de eval para o sarcasmo:
{ "comment": "Esse professor é um gênio... de tão ruim que é", "thread": "fixtures/thread-88.json", "expected": "allow", "note": "sarcasmo, sem assédio" }
Monte o dataset com os casos que hoje oscilam. Rode o classificador antigo cinco vezes em cada comentário de uma amostra e guarde os que mudaram de resposta: esses são exatamente os casos que a votação precisa melhorar.
| Prós | Contras |
|---|---|
| Decisões mais estáveis | Custo multiplicado |
| Discordância como sinal de ambiguidade | Votos correlacionados dão falsa confiança |
| Ações graves só com consenso | Exige um caminho humano para os divididos |
| Diversidade de modelos reduz erros sistemáticos | Mais spans e configurações para manter |
As duas abas rodam três votos e decidem por unanimidade, mandando divisões para revisão.
Python. Use quando a moderação roda em Python e você quer os votos em asyncio.
import asyncio
from typing import Literal
import anthropic
from pydantic import BaseModel
client = anthropic.AsyncAnthropic()
class Vote(BaseModel):
decision: Literal["allow", "remove"]
policy: str
rationale: str
VOTERS = [ # diversidade de ângulo e de modelo
("claude-sonnet-5-5", "Julgue só o texto literal contra a política de assédio."),
("claude-sonnet-5-5", "Julgue a intenção do autor: ataque a uma pessoa ou crítica a ideias?"),
("claude-haiku-4-5", "Julgue o comentário no contexto da conversa contra a política de assédio."),
]
async def vote(model: str, angle: str, comment: str, thread: str) -> Vote:
reply = await client.messages.parse(
model=model, max_tokens=500,
system=f"Você modera comentários de uma plataforma de cursos. {angle}",
messages=[{"role": "user", "content": f"<conversa>{thread}</conversa>\n<comentario>{comment}</comentario>"}],
output_format=Vote,
)
return reply.parsed_output
async def moderate(comment: str, thread: str) -> dict:
votes = await asyncio.gather(*(vote(m, a, comment, thread) for m, a in VOTERS)) # 2
removes = sum(v.decision == "remove" for v in votes) # 3
if removes == len(votes):
return {"action": "remove", "votes": votes} # 4. unanimidade
if removes == 0:
return {"action": "allow", "votes": votes}
return {"action": "human_review", "votes": votes} # 5O que você ganhou: decisões automáticas só quando todos concordam. O que você perdeu: três chamadas por comentário; vote só nos incertos se o volume for alto.
TypeScript. Use quando a moderação roda em Node e você quer votação adaptativa.
import Anthropic from "@anthropic-ai/sdk";
import { zodOutputFormat } from "@anthropic-ai/sdk/helpers/zod";
import { z } from "zod";
const client = new Anthropic();
const Vote = z.object({ decision: z.enum(["allow", "remove"]), policy: z.string(), rationale: z.string() });
type Vote = z.infer<typeof Vote>;
const VOTERS: [string, string][] = [
["claude-sonnet-5-5", "Julgue só o texto literal contra a política de assédio."],
["claude-sonnet-5-5", "Julgue a intenção do autor: ataque a uma pessoa ou crítica a ideias?"],
["claude-haiku-4-5", "Julgue o comentário no contexto da conversa contra a política de assédio."],
];
async function vote([model, angle]: [string, string], comment: string, thread: string): Promise<Vote> {
const { parsed_output } = await client.messages.parse({
model, max_tokens: 500,
system: `Você modera comentários de uma plataforma de cursos. ${angle}`,
messages: [{ role: "user", content: `<conversa>${thread}</conversa>\n<comentario>${comment}</comentario>` }],
output_config: { format: zodOutputFormat(Vote) },
});
if (!parsed_output) throw new Error("voto inválido");
return parsed_output;
}
export async function moderate(comment: string, thread: string) {
// votação adaptativa: dois votos primeiro
const first = await Promise.all(VOTERS.slice(0, 2).map((v) => vote(v, comment, thread)));
if (first[0].decision === "allow" && first[1].decision === "allow") return { action: "allow", votes: first };
const votes = [...first, await vote(VOTERS[2], comment, thread)]; // terceiro só se precisar
const removes = votes.filter((v) => v.decision === "remove").length;
if (removes === votes.length) return { action: "remove", votes };
return { action: "human_review", votes };
}O que você ganhou: comentários claramente aceitáveis pagam dois votos, não três. O que você perdeu: a latência de quem precisa do terceiro voto sobe, porque ele roda depois.
Votação (Voting) é um padrão de projeto de IA para apps com LLM e agentes, do grupo Composição. Rodar a mesma tarefa várias vezes ou com vários modelos e decidir por maioria ou limiar.
Nos cenários reais Moderação de conteúdo, Triagem de currículos com controle de viés e Triagem automática de tickets.