O que é Dataset de Ouro?
Dataset de Ouro (Golden Dataset) é um padrão de projeto de IA para apps com LLM e agentes, do grupo Operação e qualidade. Manter um conjunto curado de entradas e respostas esperadas, crescendo com bugs reais.
O Marcos montou o primeiro eval do classificador de tickets numa tarde. Ele pediu ao próprio modelo que gerasse cem tickets de exemplo, cada um com a fila certa.
O eval deu 96%. E em produção, o time de atendimento reclamava de tickets na fila errada todo santo dia.
Quando olhou tickets reais, ele entendeu. Os clientes escrevem com erro de digitação. Misturam dois assuntos. Colam print e respondem e-mail antigo. Os cem tickets gerados eram limpos, curtos e sempre sobre uma coisa só. O eval media um mundo que não existia.
E tinha um problema mais sutil. O mesmo modelo que escreveu os tickets também classificava. Ele escreveu do jeito que ele entendia, então acertava quase todos. O eval era um espelho.
Pior ainda. Quando um ticket real caía na fila errada, alguém corrigia o prompt, mas o caso nunca entrava no eval. Três meses depois, o mesmo erro voltava com outra mudança de prompt. Nesta aula, você vai aprender de onde vêm os casos de um eval confiável, e como mantê-los confiáveis com o tempo.
A resposta é tratar o dataset como um ativo do produto. Com dono, versões e regras do que entra.
Origem real. Os casos vêm da produção, anonimizados. Casos sintéticos só entram para cobrir lacunas conhecidas, e ficam marcados como sintéticos.
Rótulos revisados. A resposta esperada vem de alguém que conhece o domínio. E casos difíceis ganham uma segunda revisão.
Cobertura por categoria. Toda categoria tem casos, inclusive as raras, e casos difíceis de propósito.
Crescimento por bug. Todo erro de produção vira um caso, antes de ser corrigido.
E separação. Uma parte do dataset é para desenvolvimento. Outra fica guardada, só para a decisão final, para você não sobreajustar.
Das cinco, o crescimento por bug é a que mais rende com o tempo. Um dataset que só recebe casos no dia em que foi criado envelhece junto com o produto. Um que recebe todo erro de produção vira a memória do time. Tudo o que quebrou uma vez fica vigiado para sempre.
E a ordem importa. Registre o caso, rode o eval e veja o caso falhar. Só então corrija. Se o caso não falha antes da correção, ele não reproduz o bug. E o teste não protege nada.
Aqui está o fluxo. Os casos novos vêm de dois lugares. Amostras do tráfego de produção, e bugs reportados.
Dados pessoais são removidos ou mascarados, antes de qualquer coisa.
Um especialista do domínio rotula a resposta esperada. Casos ambíguos ganham uma segunda revisão.
O dataset é versionado como código, com histórico de quem acrescentou o quê, e por quê.
Uma parte serve para iterar no prompt. A outra só é usada na decisão de deploy.
E o eval harness roda o sistema contra tudo isso.
Vamos seguir um bug até ele entrar no dataset. Numa segunda, o atendimento sinaliza um ticket. Paguei o boleto, mas o pedido 4521 continua pendente, e quero trocar o tamanho. Ele foi para a fila de pedidos. O financeiro só viu dois dias depois.
A pessoa de plantão acha o trace do ticket e manda para o dataset com um clique. Ele cai na fila de rotulagem, já mascarado.
Quem rotula aplica o guia. Dois assuntos, e o pagamento trava o pedido, então é financeiro. Como é um caso difícil, uma segunda pessoa confirma.
O caso entra, marcado como vindo de um bug, com o motivo. O eval roda no prompt atual, e o caso novo falha. Esperado financeiro, veio pedidos. Ótimo. Agora ele reproduz o bug.
Aí o prompt ganha a regra do assunto que trava, com um exemplo que não é esse ticket.
O eval roda de novo. O caso passa, e você confere se nada que passava agora falha. Só então o bug é fechado. E daqui a seis meses, quando alguém reescrever o prompt por outro motivo, aquele caso continua lá, e avisa se o erro voltar.
E esse guia de rotulagem é o que faz duas pessoas concordarem. É um documento curto que responde as dúvidas que vão aparecer. Dois assuntos? Use a fila do que trava o cliente. Só um print? Rotule pelo conteúdo dele. Na dúvida? Liste as duas e mande para segunda revisão. Todo empate novo achado na revisão vira regra no guia. E o guia é versionado junto com o dataset, porque rótulos e regras andam juntos.
Uma pergunta para você. A fila jurídica é 2% dos tickets. A sua amostra aleatória de duzentos casos trouxe quatro deles, e o eval mostra 75% nessa fila. O que você conclui?
Que com quatro casos o número quase não diz nada. Um erro vale vinte e cinco pontos. Um único ticket estranho explica os 75%. Puxe mais casos dessa fila.
E não ignore porque 2% quase não mexe na média. Pesar pouco na média não quer dizer custar pouco. Um ticket jurídico na fila errada pode virar processo. Categorias raras e caras merecem cobertura própria.
Por isso a amostra é estratificada. Você puxa de propósito mais casos das categorias raras, até cada uma ter algumas dezenas. Numa categoria com vinte casos, cada erro mexe cinco pontos no placar. Numa com quatro, vinte e cinco.
O efeito colateral é que a média simples deixa de refletir a produção. Então mostre cada categoria separada. E calcule o placar geral ponderado pela frequência real de cada categoria.
E antes de comemorar dois pontos de ganho, veja quais casos mudaram. Em cem casos, dois pontos são dois tickets.
Então, dado sintético é proibido? Não. Digamos que o time quer dobrar o eval esta semana, e alguém sugere pedir ao modelo de produção trezentos tickets novos, já rotulados. Volume não compensa origem. Casos gerados pelo modelo que você está avaliando tendem a ser fáceis para ele. O eval acaba medindo a si mesmo.
Mas uma proibição total vai longe demais. Sem casos sintéticos, uma categoria rara pode ficar sem nenhum. Use para preencher lacunas conhecidas. Pegue casos reais difíceis e gere variações, com erro de digitação, dois assuntos, outro idioma. Marque como sintético e revise os rótulos.
Agora, as armadilhas. Quem escreveu o prompt rotula sozinho. A tendência é rotular do jeito que o prompt já responde. Chame alguém do atendimento ou do financeiro.
Apagar o caso que sempre falha. Se um caso falha em todas as versões, revise o rótulo. Se o rótulo está certo, esse caso é exatamente o que o dataset precisa.
Espiar a parte guardada. Olhar ela a cada iteração, e ficar com a versão que foi melhor nela, transforma ela em dado de desenvolvimento.
E copiar casos do eval para o prompt como exemplos. O eval passa de memória, e o placar sobe sem a produção melhorar.
Em produção, facilite. Um botão ou um comando que manda um trace direto para a fila de rotulagem.
Um painel com o tamanho do dataset por categoria, e a data do caso mais recente de cada uma.
Um alerta quando uma categoria nova aparece em produção sem casos no dataset.
E uma fila de rotulagem com dono e prazo. Fila sem dono vira cemitério de traces.
E lembre que o dataset guarda dado real. Mascarar só CPF e e-mail não basta. Tickets trazem nome, endereço, telefone, número de cartão e, às vezes, dado de saúde. Rode uma detecção mais completa, revise uma amostra na mão a cada lote, e restrinja quem tem acesso.
Então, a ideia para levar. Um eval é tão honesto quanto os casos dele. Tire da produção, e rotule com gente que conhece o domínio. Cubra as categorias raras de propósito, guarde uma fatia que você nunca espia, e transforme todo bug num caso antes de corrigir. Esse arquivo vira a memória do seu time.
Na próxima aula, a gente coloca esses evals para trabalhar num smell que quase todo time tem, e quase ninguém percebe até o fechamento do mês: texto passado entre duas máquinas. Free-text handoff. Até lá.
Golden Dataset
Dataset de Ouro (Golden Dataset) é um padrão de projeto de IA para apps com LLM e agentes, do grupo Operação e qualidade. Manter um conjunto curado de entradas e respostas esperadas, crescendo com bugs reais.
Manter um conjunto curado de entradas com a resposta esperada, que cresce a cada bug real e serve de referência para toda mudança. Serve para qualquer sistema com IA que precisa de um eval confiável ao longo do tempo.
O gabarito oficial de uma prova, revisado a cada vez que alguém acha uma questão mal corrigida.
Marcos montou o primeiro eval do classificador de tickets em uma tarde: pediu ao próprio modelo para gerar 100 tickets de exemplo com a fila certa. O eval dava 96%. Em produção, o time de suporte reclamava de tickets na fila errada todos os dias.
Quando ele olhou os tickets reais, entendeu. Clientes escrevem com erros, misturam dois assuntos, colam prints, respondem a um email antigo. Os 100 tickets gerados eram limpos, curtos e sempre sobre uma coisa só. O eval media um mundo que não existia.
Tinha um segundo problema, mais sutil. O mesmo modelo que gerou os tickets também os classificava. Ele escrevia casos do jeito que ele próprio entendia, então acertava quase todos. O eval era um espelho.
Pior: quando um ticket real caía na fila errada, alguém corrigia o prompt, mas o caso nunca entrava no eval. Três meses depois, o mesmo erro voltou com outra mudança de prompt, e ninguém percebeu até o suporte reclamar de novo.
A pergunta que este padrão responde: de onde vêm os casos de um eval que represente de verdade a produção, e como ele continua representando com o tempo?
Trate o dataset como um ativo do produto, com dono, versão e regras de entrada:
Das cinco regras, o crescimento por bug é a que mais rende com o tempo. Um dataset que só recebe casos no dia em que foi criado envelhece junto com o produto. Um que recebe cada erro de produção vira a memória do time: tudo que já quebrou uma vez fica vigiado para sempre.
A ordem “caso antes da correção” também importa. Você grava o caso, roda o eval e vê o caso falhar. Só então corrige. Se o caso não falha antes da correção, ele não reproduz o bug, e o teste não protege nada.
flowchart LR
P[("1. Produção: traces e feedback")]:::store --> S["2. Amostrar e anonimizar"]:::tool
B(["1. Bug reportado"]):::io --> S
S --> L((("3. Rotular e revisar"))):::human
L --> D[("4. Dataset versionado")]:::store
D --> DEV["5a. Parte de desenvolvimento"]:::tool
D --> HO["5b. Parte reservada"]:::tool
DEV --> E(["6. Eval"]):::llm
HO --> E
schema GoldenCase {
id: string, input: any, expected: any, category: string,
source: "production" | "bug" | "synthetic", difficulty: "easy" | "hard",
split: "dev" | "holdout", added_by: string, reason: string
}
function add_from_bug(report):
raw = traces.find(report.trace_id)
case = GoldenCase(input=redact_pii(raw.input), source="bug", difficulty="hard",
split=assign_split(), reason=report.summary)
case.expected = human.label(case) # 3
if disagreement(case): case.expected = human.review(case)
dataset.append(case) # 4
dataset.commit("bug " + report.id)
function sample_production(n):
for raw in traces.sample(n, stratify_by="category"): # 1 e 2
queue_for_labeling(redact_pii(raw.input))
function coverage_report():
return dataset.count(by=["category", "difficulty", "source"])
Dois rotuladores só concordam se a regra estiver escrita. A instrução é um documento curto que responde às dúvidas que vão aparecer. Um trecho do guia da triagem:
Fila de um ticket com dois assuntos:
use a fila do assunto que impede o cliente de usar o produto.
"paguei e o pedido segue pendente, e quero trocar o tamanho"
-> billing (o pagamento não confirmado bloqueia o pedido)
Ticket só com print, sem texto:
rotule pelo conteúdo do print; se ilegível, marque "unclear" e não use no eval.
Na dúvida entre duas filas:
marque as duas em "alternatives" e mande para segunda revisão.
Cada vez que uma revisão encontra um empate novo, a regra entra no guia. O guia é versionado junto com o dataset, porque rótulo e regra andam juntos.
A conta é simples e assusta. Numa categoria com 20 casos, cada erro move a taxa em 5 pontos. Numa com 4 casos, em 25.
Suponha que a fila “jurídico” seja 2% dos tickets. Uma amostra aleatória de 200 casos traz uns 4 dela. Um erro e a fila aparece com 75%; nenhum erro e ela aparece com 100%. Nenhum dos dois números diz muito.
Por isso a amostra é estratificada: você puxa de propósito mais casos das filas raras, até cada uma ter algumas dezenas. O efeito colateral é que a média simples do dataset deixa de refletir a produção. Resolva assim:
Uma segunda-feira, o suporte avisa: um cliente escreveu “oi, paguei o boleto mas o pedido 4521 continua como pendente e quero trocar o tamanho”. O ticket foi para a fila de pedidos. O financeiro só viu dois dias depois.
billing. Como é caso difícil, uma segunda pessoa confirma.tk-0912, com source: "bug" e o motivo. O eval roda com o prompt atual:golden/tickets v41 (212 casos: 158 dev, 54 reserva) exemplo ilustrativo
prompt triage@v17
dev 91,1% reserva 88,9%
FALHA tk-0912 esperado=billing obtido=orders motivo: dois assuntos
tk-0912.tk-0912 passa, e você confere se algum caso que passava começou a falhar. Só então o bug é fechado.O passo 5 é o que protege o futuro. Daqui a seis meses, quando alguém reescrever o prompt para outra coisa, o tk-0912 continua lá e avisa se o erro voltar.
| Aspecto | O que acontece | Como aparece |
|---|---|---|
| Custo | Tempo de pessoas do domínio para rotular | horas de rotulagem por mês |
| Execução | O tamanho do dataset define custo e tempo de cada eval | custo do job de eval |
| Falha: rótulo errado | O eval reprova a resposta certa | casos que falham em todas as versões |
| Falha: dataset enviesado | Só casos fáceis ou só de uma categoria | taxa alta no eval e reclamações em produção |
| Falha: vazamento | Casos do eval viram exemplos do prompt | acurácia no eval muito acima da produção |
| Falha: reserva gasta | A reserva foi usada para escolher entre versões | reserva e dev sempre com a mesma nota |
| Falha: dados pessoais | Dado real não mascarado no repositório | achado em revisão de segurança |
{ "id": "tk-0912", "input": "oi, paguei o boleto mas o pedido 4521 continua como pendente e quero trocar o tamanho", "expected": "billing", "category": "billing", "source": "bug", "difficulty": "hard", "split": "holdout", "reason": "dois assuntos; roteado para pedidos em 2026-08" }
O dataset contém dados reais. Mascare dados pessoais antes de gravar, restrinja o acesso e registre a base legal do uso, em linha com a LGPD.
Mascarar só CPF e email não basta. Tickets trazem nome, endereço, telefone, número de cartão e, às vezes, dados de saúde. Rode uma detecção mais completa e revise uma amostra à mão a cada lote.
| Prós | Contras |
|---|---|
| O eval mede o mundo real | Rotular custa tempo de especialistas |
| Bugs corrigidos não voltam | Exige disciplina para crescer |
| Base comum para todas as decisões | Rótulos envelhecem quando a regra muda |
| Reserva evita sobreajuste | Dados reais exigem cuidado com privacidade |
As duas abas implementam a entrada de um caso a partir de um bug e o relatório de cobertura.
Python. Use quando o dataset vive num JSONL versionado no repositório.
import hashlib
import json
import re
from collections import Counter
from pathlib import Path
DATASET = Path("evals/golden/tickets.jsonl")
CPF = re.compile(r"\b\d{3}\.?\d{3}\.?\d{3}-?\d{2}\b")
EMAIL = re.compile(r"[\w.+-]+@[\w-]+\.[\w.]+")
def redact(text: str) -> str: # 2. mascarar antes de gravar
return EMAIL.sub("<email>", CPF.sub("<cpf>", text))
def assign_split(case_id: str, holdout_share: float = 0.25) -> str:
bucket = int(hashlib.sha256(case_id.encode()).hexdigest(), 16) % 100
return "holdout" if bucket < holdout_share * 100 else "dev" # estável por id
def add_from_bug(bug_id: str, text: str, expected: str, category: str, reason: str, author: str) -> dict:
case_id = f"bug-{bug_id}"
case = {
"id": case_id, "input": redact(text), "expected": expected, "category": category,
"source": "bug", "difficulty": "hard", "split": assign_split(case_id),
"added_by": author, "reason": reason,
}
with DATASET.open("a", encoding="utf-8") as f: # 4. versionado com git
f.write(json.dumps(case, ensure_ascii=False) + "\n")
return case
def coverage() -> Counter:
with DATASET.open(encoding="utf-8") as f:
cases = [json.loads(line) for line in f if line.strip()]
return Counter((c["category"], c["difficulty"], c["split"]) for c in cases)O que você ganhou: casos com origem, motivo e divisão estável, revisáveis em pull request. O que você perdeu: o mascaramento por regex pega só formatos conhecidos; use uma rotina de detecção mais completa antes de produção.
TypeScript. Use quando a fila de rotulagem roda num serviço Node.
import { createHash } from "node:crypto";
import { appendFileSync, readFileSync } from "node:fs";
const DATASET = "evals/golden/tickets.jsonl";
type GoldenCase = {
id: string; input: string; expected: string; category: string;
source: "production" | "bug" | "synthetic"; difficulty: "easy" | "hard";
split: "dev" | "holdout"; added_by: string; reason: string;
};
const redact = (t: string) =>
t.replace(/[\w.+-]+@[\w-]+\.[\w.]+/g, "<email>").replace(/\b\d{3}\.?\d{3}\.?\d{3}-?\d{2}\b/g, "<cpf>");
const split = (id: string): GoldenCase["split"] =>
parseInt(createHash("sha256").update(id).digest("hex").slice(0, 8), 16) % 100 < 25 ? "holdout" : "dev";
export function addFromBug(bug: { id: string; text: string; expected: string; category: string; reason: string; author: string }) {
const id = `bug-${bug.id}`;
const c: GoldenCase = {
id, input: redact(bug.text), expected: bug.expected, category: bug.category,
source: "bug", difficulty: "hard", split: split(id), added_by: bug.author, reason: bug.reason,
};
appendFileSync(DATASET, JSON.stringify(c) + "\n");
return c;
}
export function coverage(): Record<string, number> {
const cases: GoldenCase[] = readFileSync(DATASET, "utf8").split("\n").filter((l) => l.trim()).map((l) => JSON.parse(l));
return cases.reduce<Record<string, number>>((acc, c) => {
const key = `${c.category}/${c.difficulty}/${c.split}`;
acc[key] = (acc[key] ?? 0) + 1;
return acc;
}, {});
}O que você ganhou: o mesmo formato de caso que a bancada lê, com divisão estável. O que você perdeu: o mesmo que em Python; o mascaramento é só um começo.
Responda as 6 perguntas para concluir a aula. Só assistir não conclui: a checagem é o que conta para a trilha e para o certificado.
Dataset de Ouro (Golden Dataset) é um padrão de projeto de IA para apps com LLM e agentes, do grupo Operação e qualidade. Manter um conjunto curado de entradas e respostas esperadas, crescendo com bugs reais.
Acúmulo de Exemplos, Exemplos Chumbados, Prompt Sem Eval, Prompt Preso ao Modelo e Eval no Olhômetro.
Nos cenários reais Extração de NF-e e contratos, Perguntas em linguagem natural para SQL e Triagem automática de tickets.