What is Golden Dataset?
Golden Dataset is an AI design pattern for LLM apps and agents, in the Operations and quality group. Keep a curated set of inputs and expected answers that grows with real bugs.
Marcos built the ticket classifier's first eval in one afternoon. He asked the model itself to generate a hundred sample tickets, each with the right queue.
The eval scored ninety-six percent. And in production, the support team complained about misrouted tickets every single day.
When he looked at real tickets, he understood. Real customers write with typos everywhere. They mix two topics. They paste screenshots, and reply to old emails. The hundred generated tickets were clean, short, and always about one thing. The eval measured a world that didn't exist.
And there was a subtler problem. The same model that wrote the tickets also classified them. It wrote them the way it understood them, so it got almost all of them right. The eval was a mirror.
Worse still. When a real ticket landed in the wrong queue, someone fixed the prompt, but the case never made it into the eval. Three months later, the same error came back with another prompt change. In this lesson, you'll learn where the cases for a trustworthy eval come from, and how to keep them trustworthy over time.
The answer is to treat the dataset as a product asset. With an owner, versions, and rules for what gets in.
Real origin. Cases come from production, anonymized. Synthetic cases only come in to cover known gaps, and they stay labeled as synthetic.
Reviewed labels. The expected answer comes from someone who knows the domain. And hard cases get a second reviewer.
Coverage per category. Every category has cases, including the rare ones, and hard cases on purpose.
Growth by bug. Every production error becomes a case, before it's fixed.
And separation. Part of the dataset is for development. Another part is held out, only for the final decision, so you don't overfit.
Of the five, growth by bug pays off the most over time. A dataset that only gets cases on the day it was created ages along with the product. One that receives every production error becomes the team's memory. Everything that broke once stays watched forever.
And the order matters. Record the case, run the eval, and watch the case fail. Only then fix it. If the case doesn't fail before the fix, it doesn't reproduce the bug. And the test protects nothing.
Here's the flow. New cases come from two places. Samples of production traffic, and reported bugs.
Personal data is removed or masked, before anything else.
A domain expert labels the expected answer. Ambiguous cases get a second review.
The dataset is versioned like code, with a history of who added what, and why.
One split is for iterating on the prompt. The other is used only for the deploy decision.
And the eval harness runs the system against all of it.
Let's follow a bug all the way into the dataset. On a Monday, support flags a ticket. I paid the bill, but order forty-five twenty-one still shows pending, and I want to change the size. It went to the orders queue. Billing only saw it two days later.
The engineer on call finds the ticket's trace, and sends it to the dataset with one click. It lands in the labeling queue, already masked.
The labeler applies the guide. Two topics, and the payment blocks the order, so it's billing. Since it's a hard case, a second person confirms.
The case goes in, marked as coming from a bug, with the reason. The eval runs on the current prompt, and the new case fails. Expected billing, got orders. Good. Now it reproduces the bug.
Then the prompt gets the blocking topic rule, with an example that is not this ticket.
The eval runs again. The case passes, and you check that nothing that used to pass now fails. Only then is the bug closed. And six months from now, when someone rewrites the prompt for something else, that case is still there, and it speaks up if the error returns.
And that labeling guide is what makes two labelers agree. It's a short document that answers the questions that will come up. Two topics? Use the queue of the one that blocks the customer. Only a screenshot? Label by its content. Unsure? List both, and send it to a second review. Every new tie found in review becomes a rule in the guide. And the guide is versioned with the dataset, because labels and rules travel together.
Here's one for you. The legal queue is two percent of tickets. Your random sample of two hundred cases brought four of them, and the eval shows seventy-five percent on that queue. What do you conclude?
That with four cases, the number says almost nothing. One error is worth twenty-five points. A single odd ticket explains the seventy-five percent. Pull more cases from that queue.
And don't ignore it because two percent barely moves the average. Weighing little in the average doesn't mean costing little. A legal ticket in the wrong queue can become a lawsuit. Rare, expensive categories deserve their own coverage.
That's why the sample is stratified. You deliberately pull more cases from rare categories, until each one has a few dozen. In a category with twenty cases, each error moves the score by five points. In one with four, by twenty-five.
The side effect is that the plain average no longer reflects production. So report each category on its own. And compute the overall score weighted by each category's real frequency.
And before celebrating a two point gain, look at which cases changed. In a hundred cases, two points are two tickets.
So is synthetic data forbidden? No. Say your team wants to double the eval this week, and someone suggests asking the production model to generate three hundred new tickets, already labeled. Volume doesn't make up for origin. Cases generated by the model you're evaluating tend to be easy for it. The eval ends up measuring itself.
But a total ban goes too far. Without synthetic cases, a rare category may have none at all. Use them to fill known gaps. Take hard real cases and generate variations, with typos, two topics, another language. Mark them as synthetic, and review the labels.
Now the traps. The prompt's author labels alone. They tend to label the way the prompt already answers. Bring in someone from support or billing.
Deleting the case that always fails. If a case fails on every version, review the label. If the label is right, that case is exactly what the dataset needs.
Peeking at the holdout. Checking it on every iteration, and keeping the version that did best on it, turns it into development data.
And copying eval cases into the prompt as examples. The eval passes from memory, and the score rises without production improving.
In production, make it easy. A button or a command that sends a trace straight to the labeling queue.
A dashboard with the dataset's size per category, and the date of each category's latest case.
An alert when a new category shows up in production with no cases in the dataset.
And a labeling queue with an owner and a deadline. A queue with no owner becomes a graveyard of traces.
And remember the dataset holds real data. Masking only ID numbers and emails isn't enough. Tickets carry names, addresses, phone numbers, card numbers, and sometimes health data. Run a fuller detection pass, review a sample by hand on every batch, and restrict who can access it.
So here's the idea to take with you. An eval is only as honest as its cases. Take them from production, and label them with people who know the domain. Cover the rare categories on purpose, keep a slice you never peek at, and turn every bug into a case before you fix it. That file becomes your team's memory.
In the next lesson, we put these evals to work on a smell almost every team has, and almost nobody notices until month-end: text passed between two machines. Free-text handoff. See you there.
Dataset de Ouro
Golden Dataset is an AI design pattern for LLM apps and agents, in the Operations and quality group. Keep a curated set of inputs and expected answers that grows with real bugs.
Keep a curated set of inputs with expected answers that grows with every real bug and serves as the reference for every change. It is for any AI system that needs a trustworthy eval over time.
The official answer key for an exam, revised every time someone finds a question graded wrong.
Marcos built the ticket classifier’s first eval in an afternoon: he asked the model itself to generate 100 sample tickets with the right queue. The eval scored 96%. In production, the support team complained about misrouted tickets every day.
When he looked at real tickets, he understood. Customers write with typos, mix two topics, paste screenshots, reply to old emails. The 100 generated tickets were clean, short, and always about one thing. The eval measured a world that did not exist.
There was a second, subtler problem. The same model that generated the tickets also classified them. It wrote cases the way it understood them, so it got almost all of them right. The eval was a mirror.
Worse: when a real ticket landed in the wrong queue, someone fixed the prompt, but the case never made it into the eval. Three months later, the same error came back with another prompt change, and nobody noticed until support complained again.
The question this pattern answers: where do the cases for an eval that truly represents production come from, and how does it keep representing it over time?
Treat the dataset as a product asset, with an owner, versions, and rules for what gets in:
Of the five rules, growth by bug pays off the most over time. A dataset that only gets cases on the day it was created ages along with the product. One that receives every production error becomes the team’s memory: everything that broke once stays watched forever.
The order “case before fix” matters too. You record the case, run the eval, and watch the case fail. Only then do you fix it. If the case does not fail before the fix, it does not reproduce the bug, and the test protects nothing.
flowchart LR
P[("1. Production: traces and feedback")]:::store --> S["2. Sample and anonymize"]:::tool
B(["1. Reported bug"]):::io --> S
S --> L((("3. Label and review"))):::human
L --> D[("4. Versioned dataset")]:::store
D --> DEV["5a. Development split"]:::tool
D --> HO["5b. Holdout split"]:::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 and 2
queue_for_labeling(redact_pii(raw.input))
function coverage_report():
return dataset.count(by=["category", "difficulty", "source"])
Two labelers only agree if the rule is written down. The guide is a short document that answers the questions that will come up. An excerpt from the triage guide:
Queue for a ticket with two topics:
use the queue of the topic that blocks the customer from using the product.
"I paid and the order is still pending, and I want to change the size"
-> billing (the unconfirmed payment blocks the order)
Ticket with only a screenshot, no text:
label by the screenshot's content; if unreadable, mark "unclear" and keep it out of the eval.
Unsure between two queues:
list both under "alternatives" and send it to second review.
Every time a review finds a new tie, the rule goes into the guide. The guide is versioned with the dataset, because labels and rules travel together.
The math is simple and scary. In a category with 20 cases, each error moves the score by 5 points. In one with 4 cases, by 25.
Suppose the “legal” queue is 2% of tickets. A random sample of 200 cases brings about 4 of them. One error and the queue shows 75%; no errors and it shows 100%. Neither number says much.
That is why the sample is stratified: you deliberately pull more cases from rare queues, until each has a few dozen. The side effect is that the dataset’s plain average no longer reflects production. Handle it like this:
On a Monday, support flags it: a customer wrote “hi, I paid the bill but order 4521 still shows pending and I want to change the size”. The ticket went to the orders queue. Billing only saw it two days later.
billing. Since it is a hard case, a second person confirms.tk-0912, with source: "bug" and the reason. The eval runs on the current prompt:golden/tickets v41 (212 cases: 158 dev, 54 holdout) illustrative example
prompt triage@v17
dev 91.1% holdout 88.9%
FAIL tk-0912 expected=billing got=orders reason: two topics
tk-0912.tk-0912 passes, and you check whether any case that used to pass now fails. Only then is the bug closed.Step 5 is what protects the future. Six months from now, when someone rewrites the prompt for something else, tk-0912 is still there and speaks up if the error returns.
| Aspect | What happens | How it shows up |
|---|---|---|
| Cost | Domain experts’ time spent labeling | labeling hours per month |
| Execution | Dataset size sets the cost and time of every eval | eval job cost |
| Failure: wrong label | The eval fails the correct answer | cases that fail on every version |
| Failure: biased dataset | Only easy cases, or only one category | high eval score and complaints in production |
| Failure: leakage | Eval cases become prompt examples | eval accuracy far above production |
| Failure: spent holdout | The holdout was used to choose between versions | holdout and dev always scoring the same |
| Failure: personal data | Unmasked real data in the repository | finding in a security review |
{ "id": "tk-0912", "input": "hi, I paid the bill but order 4521 still shows pending and I want to change the size", "expected": "billing", "category": "billing", "source": "bug", "difficulty": "hard", "split": "holdout", "reason": "two topics; routed to orders in 2026-08" }
The dataset holds real data. Mask personal data before storing it, restrict access, and record the legal basis for using it, in line with privacy laws such as GDPR or Brazil’s LGPD.
Masking only ID numbers and emails is not enough. Tickets carry names, addresses, phone numbers, card numbers and, sometimes, health data. Run a fuller detection pass and review a sample by hand on every batch.
| Pros | Cons |
|---|---|
| The eval measures the real world | Labeling costs expert time |
| Fixed bugs do not return | It takes discipline to keep growing it |
| A shared basis for every decision | Labels age when the rule changes |
| The holdout prevents overfitting | Real data requires privacy care |
Both tabs implement adding a case from a bug and the coverage report.
Python. Use this when the dataset lives in a versioned JSONL file in the repository.
import hashlib
import json
import re
from collections import Counter
from pathlib import Path
DATASET = Path("evals/golden/tickets.jsonl")
SSN = re.compile(r"\b\d{3}-?\d{2}-?\d{4}\b")
EMAIL = re.compile(r"[\w.+-]+@[\w-]+\.[\w.]+")
def redact(text: str) -> str: # 2. mask before storing
return EMAIL.sub("<email>", SSN.sub("<ssn>", 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" # stable per 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. versioned with 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)What you gained: cases with source, reason, and a stable split, reviewable in a pull request. What you lost: regex masking only catches known formats; use a fuller detection routine before production.
TypeScript. Use this when the labeling queue runs in a Node service.
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{2}-?\d{4}\b/g, "<ssn>");
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;
}, {});
}What you gained: the same case format the eval harness reads, with a stable split. What you lost: same as Python; masking is only a start.
Answer the 6 questions to complete this lesson. Watching alone does not complete it: the check is what counts toward the trail and the certificate.
Golden Dataset is an AI design pattern for LLM apps and agents, in the Operations and quality group. Keep a curated set of inputs and expected answers that grows with real bugs.
Few-Shot Hoarding, Hardcoded Few-Shots, Untested Prompt, Model-Pinned Prompt, and Vibe-Check Eval.
In the real-world scenarios Invoice and contract extraction, Natural language to SQL, and Automatic ticket triage.