Skip to main content
Back to Blog
AI Testing
2026-07-13

Set Golden-Set Difference Thresholds for Prompt Regression

Set golden-set difference thresholds for prompt regression using labeled change severity, slice budgets, deterministic checks, and reviewable release gates.

Set Golden-Set Difference Thresholds for Prompt Regression

Prompt B changes 41 of 200 golden answers. That number sounds alarming until the changes are opened: 29 are punctuation or formatting, seven are clearer paraphrases, three omit required facts, and two violate a refusal rule. A raw changed-output percentage collapses all of those outcomes into one bit. The useful threshold is not “how much text moved,” but “how much unacceptable behavior entered each risk slice.”

A golden set gives prompt versions a shared examination. A difference threshold turns the comparison into a release decision. Setting it well requires several measures, because exact text equality is suitable for JSON keys and fixed labels but hostile to legitimate natural-language variation. This guide develops a severity-aware diff, calibrates budgets from human judgments, and shows how to keep the gate honest as prompts and models evolve.

Define what “different” means before choosing a number

Every golden item should state which parts of the response are invariant and which may vary. For an extraction prompt, field names, types, and required values may be strict while object key order is irrelevant. For a support answer, cited policy IDs and the recommended action may be invariant while wording is flexible. For a safety refusal, the refusal decision may be strict while the explanation can change.

Separate checks by contract:

Difference channelExampleSuitable comparisonTypical severity
SchemaRequired risk_level missingParse and validate structureBlocking
Decision“Approve” changes to “Reject”Exact normalized labelBlocking or high
Grounded factsWarranty changes from 24 to 12 monthsClaim or field comparisonHigh
Citation setPolicy P-14 disappearsSet inclusion or precision/recallHigh
Instruction coverageTroubleshooting step omittedRubric labelsMedium to high
StyleBullets become proseStyle classifier or reviewerUsually low
Surface textPunctuation and synonym changesNormalized or semantic similarityInformational

This decomposition prevents semantic similarity from excusing a wrong number and prevents exact-match checks from rejecting a harmless rewrite. It also produces failure messages an engineer can act on. “Citation recall fell below the billing slice budget” is more useful than “42 percent different.”

The golden dataset guide covers construction and curation. Threshold work begins after each item has a stable input, expected contract, risk label, and enough metadata to form meaningful slices.

Store judgments, not a single canonical paragraph

A canonical response is useful evidence, but it is rarely the complete oracle. Add fields that express allowed variation and required behavior. A JSONL item for an account-policy assistant might look like this:

{"id":"refund-017","slice":"refunds","risk":"high","input":"Can I refund an annual plan after 45 days?","required_facts":["30-day refund window","contact support for exceptions"],"forbidden_claims":["automatic refund is guaranteed"],"required_citations":["POL-REFUND-2"],"reference":"The standard refund window is 30 days. Contact support to ask whether an exception applies. [POL-REFUND-2]"}

The generated output belongs in a separate run record keyed by item ID, prompt version, model identifier, decoding settings, retrieval snapshot, and timestamp. Do not overwrite the golden data with the latest answer. The point is to compare a candidate against an agreed contract and a baseline, not to make the oracle follow every candidate.

For genuinely open-ended tasks, keep multiple acceptable examples or a rubric. Human-reviewed “acceptable but different” outputs are particularly valuable because they reveal where an exact or lexical threshold would create noise.

Build a severity-aware diff locally

Start with deterministic checks that are cheap, reproducible, and easy to audit. The following Python program compares baseline and candidate JSONL files. Each run row contains id, decision, facts, citations, and text. It treats decision changes and lost facts as severe, citation loss as major, and normalized text-only movement as cosmetic.

from __future__ import annotations

import json
import re
import sys
from collections import Counter
from pathlib import Path


def load_jsonl(path: str) -> dict[str, dict]:
    rows = {}
    for line in Path(path).read_text(encoding="utf-8").splitlines():
        if line.strip():
            row = json.loads(line)
            rows[row["id"]] = row
    return rows


def normalized(text: str) -> str:
    return re.sub(r"\s+", " ", text.strip().lower())


def classify(before: dict, after: dict) -> tuple[str, list[str]]:
    reasons: list[str] = []
    if before["decision"] != after["decision"]:
        reasons.append("decision changed")
    missing_facts = set(before["facts"]) - set(after["facts"])
    if missing_facts:
        reasons.append(f"facts lost: {sorted(missing_facts)}")
    if reasons:
        return "severe", reasons

    missing_citations = set(before["citations"]) - set(after["citations"])
    if missing_citations:
        return "major", [f"citations lost: {sorted(missing_citations)}"]

    if normalized(before["text"]) != normalized(after["text"]):
        return "cosmetic", ["normalized text changed"]
    return "same", []


baseline = load_jsonl(sys.argv[1])
candidate = load_jsonl(sys.argv[2])
if baseline.keys() != candidate.keys():
    raise SystemExit("Run files must contain exactly the same item IDs")

counts: Counter[str] = Counter()
for item_id in sorted(baseline):
    severity, reasons = classify(baseline[item_id], candidate[item_id])
    counts[severity] += 1
    if severity in {"severe", "major"}:
        print(item_id, severity, "; ".join(reasons))

total = len(baseline)
print({name: round(count / total, 4) for name, count in counts.items()})
if counts["severe"] > 0 or counts["major"] / total > 0.01:
    raise SystemExit(1)

The one-percent number is only an example policy, not a universal recommendation. On a 50-item set it is effectively zero because one item equals two percent. That illustrates why count budgets and rate budgets both matter. A team might allow at most two major regressions overall but zero in a high-risk slice.

The program assumes baseline facts are expected in the candidate run record. In production, derive candidate facts with a tested extractor or compare generated text directly against golden required facts. Do not let the same unreviewed LLM generate the answer and certify its own facts without calibration.

Calibrate against human acceptability

Threshold selection is a classification problem. For each historical comparison, reviewers label the candidate acceptable or unacceptable under a written rubric. The automated metric then predicts that label. Test several threshold values and calculate false-accept and false-reject counts.

Suppose a composite severity score ranges from 0 (no harmful difference) to 1 (maximum harm), and the gate fails at or above threshold t. The following script reads reviewed examples and prints confusion data for candidate thresholds:

from dataclasses import dataclass


@dataclass(frozen=True)
class Review:
    score: float
    unacceptable: bool


reviews = [
    Review(0.02, False),
    Review(0.08, False),
    Review(0.17, False),
    Review(0.24, True),
    Review(0.31, False),
    Review(0.44, True),
    Review(0.68, True),
    Review(0.91, True),
]

for threshold in (0.15, 0.20, 0.25, 0.30, 0.40):
    tp = fp = tn = fn = 0
    for review in reviews:
        predicted_failure = review.score >= threshold
        if predicted_failure and review.unacceptable:
            tp += 1
        elif predicted_failure and not review.unacceptable:
            fp += 1
        elif not predicted_failure and review.unacceptable:
            fn += 1
        else:
            tn += 1
    print(threshold, {"tp": tp, "fp": fp, "tn": tn, "fn": fn})

The tiny list is pedagogical. Real calibration needs enough reviewed cases across each important slice. The decision should reflect cost: a false accept in medical instructions may be far more expensive than a false reject that sends a candidate to manual review. In a low-risk copywriting prompt, the balance can reverse.

Do not select the threshold on the same examples used to design the metric and report it as validated. Hold out reviewed comparisons or use time-based validation. Otherwise the team tunes to known cases and discovers poor generalization during the next prompt change.

Give risky slices their own budgets

Aggregate gates can hide concentrated damage. If a golden set contains 900 marketing items and 100 billing items, nine billing regressions produce a one-percent overall rate but a nine-percent billing rate. Release decisions should see both.

Define slices from product risk, language, intent, tool usage, answer length, customer tier, and known failure modes. Keep the number manageable. A slice with three items cannot support a stable percentage threshold, although it can support a zero-tolerance rule for explicit invariants.

SliceExample invariantRecommended gate shapeWhy aggregate is insufficient
Safety refusalAllowed request must not be refusedZero severe label flipsOverall quality can hide one harmful refusal
Billing policyAmount and eligibility remain correctZero fact loss, citation budget near zeroWrong values carry direct customer impact
Tool callingFunction and required arguments validSchema pass plus exact tool-name checkFluent text cannot compensate for invalid calls
MultilingualMeaning preserved per supported localePer-locale reviewed metricEnglish volume can dominate the mean
Creative copyBrand constraints honoredRubric score with review bandExact wording should vary

Thresholds can have three states rather than pass and fail. A clean region auto-passes, a clearly harmful region blocks, and a narrow gray band requires review. This is often better than pretending evaluator noise disappears at one decimal place.

Keep baseline drift separate from candidate drift

There are two comparisons in prompt regression. Candidate versus golden contract asks whether the new system is acceptable. Candidate versus current production asks what changed operationally. A candidate can differ greatly from production and still be a major improvement. It can also resemble production while both violate a newly added golden rule.

Store both results:

  1. Contract evaluation against required fields, facts, citations, and rubrics.
  2. Paired change analysis against the currently deployed prompt and model.
  3. Human review for disagreements, novel behavior, and threshold-border cases.

Never silently replace the baseline after deployment. Baseline promotion should be a reviewed event with prompt version, model version, dataset version, evaluator version, and approver. Otherwise next week’s diff cannot explain whether behavior moved because of the candidate or because the reference was edited.

The broader prompt regression testing guide shows how these comparisons fit into CI and release workflows.

Account for evaluator and sampling variance

Natural-language outputs can vary even with stable prompts. Model graders can vary too. A threshold built from a single generation and a single grading pass may flap near its boundary. Reduce avoidable variance by pinning the evaluated model version where possible, recording decoding parameters, freezing retrieval inputs, and making deterministic checks first.

For stochastic use cases, run repeated samples on a targeted subset and summarize the distribution. Do not average away a rare catastrophic failure. Track worst case or exceedance probability for critical invariants. For lower-risk style scores, a mean and confidence interval may be appropriate.

If an LLM judge supplies a score, preserve its rubric label and rationale for review, but do not assume numeric distances are calibrated. A move from 0.79 to 0.81 is not necessarily a meaningful quality jump. Compare judge labels to human labels, inspect disagreements, and version the judge prompt like production code.

Write a gate that explains itself

A release gate should output item IDs, slice, failed invariant, baseline value, candidate value, severity, and links to full responses. A single percentage in a CI log forces engineers to rerun locally and encourages blind retries.

The policy can be explicit:

policy_version: 3
global:
  severe_count_max: 0
  major_rate_max: 0.01
  review_band:
    composite_difference: [0.18, 0.25]
slices:
  billing:
    lost_required_fact_max: 0
    lost_required_citation_max: 0
  creative-copy:
    severe_count_max: 0
    major_rate_max: 0.05
minimum_items:
  rate_gate: 100

This YAML is a proposed team-owned policy format, not a library API. Validate it with your own schema and keep it beside the evaluator. The minimum_items rule prevents a percentage from conveying false precision on tiny slices. When the sample is smaller, use count gates and manual review.

Report improvements as well as regressions. A prompt change that fixes eight known failures while introducing one low-risk style issue deserves a different discussion from a change that only introduces the issue. The release rule may still block the severe case, but the report should represent the whole paired result.

Recalibrate when the system changes shape

Revisit thresholds when the model family changes, retrieval is introduced, output schema changes, new locales launch, or human reviewers revise the rubric. Monitor the post-release false-alarm rate and escaped regression rate. Add escaped cases to the golden set only after deduplication and root-cause analysis.

Do not tighten a threshold simply because the current candidate happens to pass. Do not loosen it in the same pull request that it blocks without independent evidence. Either action can be valid, but it requires reviewed calibration data and a policy change separated from the prompt change where feasible.

A healthy gate is deliberately boring: stable item set, versioned measurements, explainable failures, and occasional evidence-based recalibration. Its purpose is not to freeze prose. It is to make behavioral change visible at the level customers and systems care about.

Use change budgets to control the review queue

Even acceptable output movement consumes reviewer attention. A prompt refactor that changes 80 percent of surface text may preserve every invariant, yet manually confirming that result is harder than reviewing a five-percent change. Track review workload as an operational budget separate from the defect budget.

Stratify changed items by deterministic outcome. Items with identical structured decisions, complete required facts, unchanged citations, and only normalized prose movement can receive sampled review. Items with evaluator disagreement, new claims, or scores near a boundary receive full review. Severe deterministic changes always block. This allocation makes a large benign rewrite inspectable without pretending it is risk-free.

Sampling must be reproducible and risk-aware. Seed selection from the candidate version, include every high-risk slice, and record the chosen item IDs. A random sample drawn only from the dominant intent can miss the one locale or tool path that changed. If review finds a defect, expand to the related cluster rather than treating the sampled error as an isolated percentage.

Maintain a ceiling on “unclassified change.” When a response differs but none of the known comparators can explain why, route it to review. Do not place it automatically in the cosmetic bucket. That bucket should mean measured surface variation after invariant checks, not absence of an implemented detector.

The review budget can influence rollout without redefining correctness. A candidate with no detected regressions but unusually broad movement might ship to a small canary while review continues. Record that as a rollout decision, not a lower test threshold. Keeping those concepts separate prevents delivery pressure from rewriting the definition of an acceptable answer.

Frequently Asked Questions

Is exact match ever appropriate for prompt regression?

Yes, for canonical labels, tool names, identifiers, schema fields, and other tokens whose variation is not acceptable. It is usually too strict for explanatory prose. Apply exact match to the invariant field rather than the entire response.

How large should a golden set be before using percentage thresholds?

There is no universal size. Percentages become misleading when one item moves the rate by several points. Define a minimum sample count for rate-based gates, then use absolute counts or mandatory review for smaller high-risk slices.

Should cosmetic differences count toward the release limit?

Track them, but give them a separate budget from factual, decision, citation, or safety regressions. A surge in cosmetic movement can reveal prompt instability, yet it should not carry the same severity as a wrong policy answer.

Can an LLM judge choose the threshold automatically?

It can provide a signal, not an accountable policy. Calibrate judge outputs against human decisions, inspect slice-specific errors, and choose the gate based on false-accept and false-reject costs. Version the judge and rubric so the measurement itself does not drift silently.

When may the production baseline be updated?

Promote it after the candidate is approved and deployed, with the prompt, model, dataset, evaluator, and review decision recorded. Keep the immutable golden contract separate, because production behavior is a comparison point, not automatically the correct answer.

Set Golden-Set Difference Thresholds for Prompt Regression | QASkills.sh