Marketing

A Golden Set for Intent Classification: Building and Keeping It

Antonio Evans8 min read

A workbench view from above: a stack of index cards, each with a short user query written on it, sorted into labeled piles (Refund, Shipping, Cancel, Other).
Key points

Key points

  1. 1Sample real queries from production logs, stratify by traffic share, and keep a random holdout so your golden set reflects what users actually type.
  2. 2Write labeling rules with decision order and tie-breakers precise enough that two annotators label the same query identically, and log every disagreement as an edge case.
  3. 3Lock a held-out test split you never tune prompts on, so your reported accuracy is an honest estimate rather than a memorized one.
  4. 4Rerun the full set on any prompt, model or taxonomy change, and block deployment when any per-intent accuracy drops beyond a set tolerance.

Build the set from real queries, not invented ones

Intent classification means deciding what a user wants from a short, noisy string, whether that is a search query or a chat message the AmICited glossary. Most teams start with four buckets (informational, navigational, commercial, transactional) and sometimes add local or generative-task intent the SE Ranking guide. That vocabulary is a fine starting point. A wiki page is not a test.

Two things break when you skip a fixed evaluation set. You cannot tell whether a prompt edit helped or hurt, because you are judging from examples you remember. And the examples you remember are the easy ones, so the classifier looks better than it is. A golden set fixes both: a frozen, labeled slice of real traffic that every candidate is scored against the same way, every time.

The failure mode is visible in public data. On a benchmark assembled from 49 corpora across 12 domains, 20 large language models scored below 60% on the full test set and below 25% on a harder, balanced set, with 17 of 20 falling under the random-guess baseline on that hard set, while estimated human performance sat near 81% the IntentGrasp benchmark. Your traffic is narrower than that, but the lesson holds: general model quality is not evidence of quality on your intents. Only your labeled rows are.

So sample what people typed, not what you would phrase. Invented queries are grammatical and predictable, and a classifier that scores well on them still falls apart on abbreviations, typos and half-finished sentences.

A procedure that works:

  1. Export a window of raw queries with a timestamp and session identifier, and deduplicate exact repeats so one bot or one power user cannot dominate.
  2. Stratify by traffic share. If 42% of queries are order status, roughly 42% of the golden set is order status. A set that is 25% per intent is balanced, not representative, and it flatters the model on rare intents while hiding regressions on common ones.
  3. Oversample the tails on purpose. Keep a small slice for rare intents and for queries with no plausible intent at all (test strings, empty submissions, gibberish), because that is where classifiers misfire silently. One query can carry several goals, and identical strings can mean different things depending on context, so ambiguity belongs in the set deliberately the AmICited glossary.
  4. Draw a random holdout of about 10% to 20% and do not look at it while labeling or tuning.

Size guidance that holds up: about 50 to 100 examples per intent for a five to ten intent classifier, landing near 500 to 1,000 rows, then grow the set each release. Past a few thousand examples the measurement stops improving and you are better off fixing labeling rules. For file shape, the public evaluation scripts for NLU platforms ship an annotated query set built for exactly this purpose, useful as a template even if you never run the code the NLU Evaluation Scripts repository.

Keep a running list of edge cases rather than hoping sampling finds them: typos and transliteration, all caps, emoji-only messages, pasted walls of text, single characters. A query such as "apple" could mean the fruit, the company, the record label or an idiom, and it is a fine permanent resident of your set the AmICited glossary. Add boundary pairs where adjacent intents confuse easily (order status versus cancellation, pricing question versus pricing page). Add multi-intent strings and decide the output contract first, either a primary label or primary plus secondary. Finally, once you have a baseline, pull real queries whose confidence sits near your cutoff; the confidence threshold glossary makes the right point that the cutoff is a business decision and that the default of 0.5 is rarely correct, because the cost of a false positive varies enormously by use the confidence threshold glossary.

Write labeling rules two people can apply the same way

A label is only useful if it reproduces. The test is simple: hand the same 50 queries to two annotators working from the rulebook alone and compare. Write rules as a priority-ordered decision list, not prose descriptions of each intent, so that when two rules could fire the earlier one wins.

Add explicit tie-breakers for known ambiguities. A query asking for a price and a demo is transactional, because the user is ready to act the Siteimprove intent guide. A query saying "best X" or "X vs Y" is commercial, because the user is comparing before deciding the Semrush keyword types guide. A query naming your product plus a support action is support even when phrased as a question. A query with no recognizable topic and no recognizable action is out_of_scope, and you keep the row.

This matters more than it sounds because heuristics misclassify. A "how to" query can land on product pages, and a query an SEO tool tagged commercial may be ranking educational content the Grow and Convert intent guide. Your human labels encode what you actually want, which is why they must be rules and not vibes.

CheckPass condition
Every intent has a one-line definitionYes
Rules are ordered by priorityYes
Every known ambiguity has a tie-breakerYes
out_of_scope is a defined labelYes
Two annotators agree on a 50-query sample80% or higher
Disagreements are logged with reasoningYes
Rulebook has a version and changelogYes

When agreement drops below the threshold, do not relabel quarreling rows by fiat. Fix the rule, bump the version, re-annotate the affected rows. Each resolved disagreement becomes an edge case you would otherwise have met in production.

Freeze a held-out split and version it

Split the labeled pool once, by row, into a development split (fair game while tuning) and a locked test split you never tune against. Record the split's row count, label distribution, rulebook version and a hash of the sorted row IDs in a manifest, and treat any change to those as a new dataset version.

{
  "golden_set": "intent-golden",
  "version": "v3",
  "rows": 812,
  "split": {"dev": 650, "test": 162},
  "label_distribution": {"support": 344, "commercial": 201, "transactional": 158, "informational": 79, "out_of_scope": 30},
  "rows_sha256": "<hash of sorted row ids>",
  "labeling_rule_version": "rules-7"
}

Three rules keep the split honest. If a row moves between dev and test, that is a new version with a new hash, and the old numbers are not comparable. Never add a rule derived from studying test rows; if you relabel, relabel both splits. Report dev and test separately and put the test number in the release note. A team that tunes a prompt until the test score rises and then reuses the same test set next cycle has turned the test set into a development set with extra steps. Teams that layer few-shot examples into prompts and then evaluate on those same examples are reading the answer key the practical intent classification walkthrough. Prompt examples come from dev, or from generically correct paraphrasings, never from test.

Set the retest trigger and compare runs with a confusion matrix

Name the events that force a full rerun, in writing, in the same checklist as your deploy steps: any change to the taxonomy, the prompt, the model or version serving it, the labeling rules that alter labels, or preprocessing that touches query text such as lowercasing or Unicode normalization. Add a fixed calendar cadence so slow drift is caught even when nothing intentional changed.

Cost is not the obstacle. Batch inference is commonly half the price of synchronous calls for workloads that do not need an immediate answer, and scoring a frozen set is exactly that the batching guide. If your prompt carries a long stable prefix, prompt caching cuts the repetition the Claude prompt caching documentation. Neither trick makes the evaluation cheap in the way that matters: the expensive part is the label.

Then read results off a matrix of predicted rows by true columns, and read off-diagonal cells as pairs:

True \ Predictedsupportcommercialtransactionalinformationalout_of_scopeTotal
support68102475
commercial03953148
transactional16310038
informational02020022
out_of_scope1000910
Total7048362514193

Commercial leaking into transactional (5 of 48, about 10%) may be tolerable, since both route to a sales surface. Transactional leaking into commercial (6 of 38, about 16%) is worse, because buyers are sent to comparison content. Support leaking into out_of_scope (4 of 75) means customers are told their request is not understood.

Turn that into a decision rule. Compute per-intent recall and precision for the previous release and the candidate. Block the deploy if any intent's recall drops more than your tolerance (5 percentage points is a reasonable default) or if any intent loses more than a tenth of its correct rows. Permit tiny dips only with a written note from that intent's owner plus a fix ticket. Never accept a trade that gains overall accuracy while collapsing a small intent, because small intents are usually high-value or high-risk.

For wiring it in, the Langfuse cookbook walks an intent classification pipeline end to end and is a useful reference for attaching scores to traces so each result ties back to a prompt and model the Langfuse pipeline cookbook. Store raw model output for every row, not just the parsed label, because prompt changes often shift output format and you will want to re-parse old runs after a bug. And when you compare models through one API, keep the model identifier and prompt hash in every run record, which is exactly what a single OpenAI-compatible, prepaid endpoint lets you do (https://quantaintelligence.ai/inference). If you would rather have the harness built inside the surrounding app, describing it to Quanta hands it to a specialist who builds it with you (https://quantaintelligence.ai/).

Worked example and the traps to watch

Your classifier serves 200,000 queries a month and your set has 1,000 rows: 350 support, 250 commercial, 200 transactional, 150 informational, 50 out of scope. Release A gets 60 support rows right. Release B gets 52 right, even though overall accuracy rose from 812 to 830 rows. Support recall fell from roughly 86% to roughly 83.6%, about 2.4 points, under a 5-point tolerance. Commercial, however, fell from 47 correct to 41, also 2.4 points but a 12.8% relative loss, so the relative test blocks the deploy until there is a named fix.

Weigh the traffic. Support is 35% of volume, so a 2.4-point recall loss routes roughly 1,680 support queries a month wrongly (200,000 × 0.35 × 0.024). That number, not the accuracy delta, belongs in front of a product owner.

Now the traps. Synthetic examples written in a prompt or by a model are a second training set; check your sampler for raw, ugly log text. Few-shot examples copied from test inflate scores; diff the prompt against test rows. Quietly adding three edge cases and comparing to last month's score breaks comparability; check the manifest hash. One aggregate accuracy number hides the pair of intents that flipped; read the matrix every time. Relabeling without a version makes two runs incomparable; rules-8 against rules-7. And a rare intent with 12 test rows cannot be measured with confidence, so grow it deliberately or mark it unmeasured rather than declaring it fine.

FAQ

FAQ

What size should a golden set be?

Start with about 50 to 100 labeled queries per intent, roughly 500 to 1,000 rows for a five to ten intent classifier, and grow it with fresh samples each cycle. Below 50 rows per intent, a couple of lucky examples move the score by more than the difference you are trying to detect. The exact number matters less than keeping the split frozen and versioned.

Can I use an LLM to label the golden set?

Use an LLM to draft labels or flag ambiguous rows, but have a person confirm every row entering the locked test set. The public benchmark showing 17 of 20 models under the random-guess baseline on a hard intent set is why LLM labels are not automatically trustworthy as evaluation labels the IntentGrasp benchmark. Treat model labeling as a drafting aid and human review as the gate.

How often should I retest?

Retest whenever the prompt, model, taxonomy or preprocessing changes, plus a fixed calendar cadence such as monthly or quarterly to catch slow drift. A full rerun is cheap for a classification workload because batch processing commonly runs at half price and caching cuts repeated prompts the batching guide. The cadence matters because user behavior moves, which is why the intent literature argues categories are not stable labels the Grow and Convert intent guide.

How do I handle a query that genuinely has two intents?

Decide the output contract first, then label consistently. Either pick a primary intent with a written priority rule (action beats question, for example), or return primary plus secondary and score both. What you must not do is let annotators choose ad hoc, because disagreement on multi-intent rows is the fastest way to lose agreement across the whole set.

What is the difference between the dev split and the test split?

The dev split is for looking, tweaking prompts and trying examples; the test split is for keeping score and is never tuned against. Prompt examples come from dev or from generic paraphrasings, so the test number reflects rows the system has never seen. If you find yourself editing the classifier because of one test failure, you are tuning on the test split and its number is no longer honest.

Sources

Sources

  1. Query Intent Classification: AI Intent Analysis & ...
  2. The 6 Types of Search Intent (Including the New ...
  3. A Comprehensive Benchmark for Intent Understanding
  4. README -- Scripts for evaluating NLU Services/Platforms
  5. Understanding Confidence Threshold in AI Systems - LlamaIndex
  6. Integrate Search Intent Analysis Into Your Enterprise ...
  7. 4 types of keywords in SEO (+ examples)
  8. How to Determine Search Intent and Optimize for It
  9. Generative AI Meets Intent Classification - A Smarter ...
  10. LLM Batching & Async Inference: Cut Costs on High- ...
  11. Prompt caching - Claude Platform Docs
  12. Guide - Building an intent classification pipeline
About the author

About the author

Antonio Evans
Antonio Evans

Antonio Evans is the founder of Quanta Intelligence and a fractional CTO who builds HIPAA-ready AI systems for health and life-science companies. Before that he was technical co-founder of FriendsEAT for twenty years, CEO of Socialdraft, and a data engineer at IBM. Antonio lives with his dog Santino in Lisbon, Portugal.

All articles