Vol. 12 · Evaluation & Improvement

Evaluation Process & LLM-as-Judge

How to build the process of evaluating LLM systems from scratch: minimum viable eval, guardrails vs evaluators, why a binary assessment is better than a scale of 1–5, negative scenarios, LLM-as-judge with correlation to people, detection of degradation after release, synthetic data - when it helps and when it harms.

01

Eval Process: where to start

4 cards
01ProcessMinimum Viable Eval: from scratch in 3 steps

Most teams put off evaluation because it seems like expensive infrastructure is needed. No: the minimum working eval is 20–50 examples, pass/fail criteria and one “benevolent dictator” - a person with domain expertise, whose opinion is considered the standard.

Diagramsql
PHASE 1 - LAUNCH (Day 1-3)

Step 1: Collect 2050 Real Requests
  └─ from production logs, from stakeholders, invented by hand
  └─ cover: standard cases + 510 edge cases
  └─ DO NOT generate LLM at this stage

Step 2: one expert marks manually
  └─ "benevolent dictator": PM / domain expert / TL
  └─ for each example: pass / fail + 1 line why
  └─ DO NOT discuss the criteria - first mark it intuitively
  └─ then - extract patterns from your solutions into criteria

Step 3: run the system, compare
  └─ count pass_rate = passes / total
  └─ look at specific fails - WHAT exactly is wrong
  └─ this is your baseline - remember it

───────────────────────────────── ─────────────────────────────────
PHASE 2 - GROWTH (after the first launch)

  every prod bug → add to dataset
  each prompt change → run eval
  every 2 weeks → review of criteria
  with 50+ examples → you can think about automation

start antipatterns

  • We are waiting for the “correct” infrastructure
  • we immediately build LLM-judge without baseline
  • generate a dataset LLM without verification
  • 20 manual examples > 1000 synthetic ones
02ProcessError Analysis: hands before automation

After each significant change to the system (prompt, model, chunking, retriever), be sure to read 20–50 outputs manually. Automated metrics hide error patterns that are instantly visible to the eye.

error analysis protocolsql
# After any change:
sample = random_sample(outputs, n=30)
# or: all fails from eval for the period

# For each read:
Is the answer correct in meaning?
Is the answer based on context or fictitious?
Is the formatting as expected?
Is the answer too long/short/evasive?
✓ Does the model respond when it should fail?
✓ Does the model refuse when it should respond?

# Group errors by type:
hallucination |  wrong_refusal
off_topic |  format_error
incomplete |  tone_mismatch

# Most common category → fix first

practice

  • read the fails yourself - don’t delegate to the junior
  • 30 examples give 80% of the picture
  • every bug → test case in the dataset
03ProcessResponse metrics vs pipeline metrics

Key principle: a good bottom line metric doesn't tell you where it's broken. Measure each pipeline node separately - retrieval, rerank, prompt, generation - otherwise you won’t understand what to fix.

Diagramsql
PIPLINE NODE METRICS SUMMARY METRICS

Query


Retrieval ──→ Recall@k, MRR, nDCG@k ┐
  │ │
  ▼ │
Rerank ──→ Precision@k, MRR delta │──→ Faithfulness
  │ │Answer Relevance
  ▼ │ Task Success Rate
Prompt ──→ token count, format │
  │ few-shot hit rate │
  ▼ │
Generation ──→ hallucination rate │
              refusal rate │
              latency, cost ┘

The final metric is growing → but from where?
Nodal metrics → we know exactly what has changed

rule

  • change one variable at a time
  • record the dataset, model, index when comparing
  • comparing on different datasets = pointless
04ProcessTrace: what must be logged

Trace - a record of everything that happened in the pipeline for one request. Without traces, you cannot debug, build eval and detect degradation. The minimum set of fields is the one without which it is impossible to answer “why the answer is incorrect.”

required trace fieldsjson
{
  "trace_id": "uuid", # end-to-end ID
  "timestamp": "ISO-8601",
  "user_id": "...", # for user-level analysis

  // Input
  "query": "original query",
  "query_rewrite":"after augmentation", # if any

  // Retrieval
  "retrieved_chunks": ["chunk_id", "score", "text"],
  "reranked_chunks": ["chunk_id", "score"],

  //Generation
  "prompt_tokens": 1240,
  "completion_tokens": 312,
  "model": "cloud-sonnet-4",
  "response": "final response",
  "latency_ms": 1840,

  // Eval (async, after request)
  "faithfulness": 0.92,
  "refusal": false
}

tools

  • Langfuse - open-source, self-hosting
  • Phoenix Arize - traces + eval
  • the trace structure is more important than the tool
02

Binary vs Scale: why pass/fail wins

3 cards
05BinaryWhy binary rating is better than 1-5 scale

Intuition suggests: a scale of 1–5 is more accurate, because it contains more information. Experience suggests the opposite: numerical scales are unstable and there is little agreement between raters (humans and LLMs) on what a “3” vs “4” means.

Diagramsql
THE PROBLEM OF SCALES

Same answer, three evaluators:
  Ivan: 4 - “good, but a little incomplete”
  Maria: 3 — “average, expected more details”
  LLM: 5 - “completely answers the question”

  Cohen's κ = 0.18 ← weak agreement, noise

───────────────────────────────── ─────────────────────────────────
WHY BINARY WORKS

The same answer, worded as a clear criterion:
  "Does the answer contain a specific number?" → pass / fail

  Ivan: pass
  Maria: pass
  LLM: pass

  Cohen's κ = 0.91 ← excellent agreement

───────────────────────────────── ─────────────────────────────────
PRINCIPLE: One criterion = one binary test

Bad: "how good is the answer from 1 to 5?"
         → mixes: relevance + completeness + tone + facts

Okay: four separate questions:
  1. Does the answer answer the user's question?       Y/N
  2. Is the answer supported by the context (not made up)?   Y/N
  3. Does the tone match the brand style?              Y/N
  4. Does the response contain the requested format?           Y/N

  pass_rate for each → it’s clear what exactly is broken

when the scale is still justified

  • ranking of multiple options (A/B/C/D)
  • quality gradations with clear categories and examples
  • only if κ > 0.6 on the pilot marking
  • never for automation without verification
06BinaryHow to formulate pass/fail criteria

The criterion must be formulated so that any reasonable person (or LLM) would give the same answer. Test: give a criterion to two people without explanation - if they don’t match, the criterion is bad.

good criterion templatesql
# Structure: "The answer [does X]?" where X is specifically

✗ Bad (blurry):
  "Is the answer quality?"
  "Is the answer useful to the user?"
  "Is the answer correct?"

✓ Good (specific):
  "Does the answer contain at least one specific number?"
  "The answer doesn't mention a competitor company?"
  "Is the answer written in Russian?"
  “The answer begins with a direct answer, not with introductory words?”
  "Does the answer contain statements that are not in the context?"

# Criterion test:
sample = 20 examples
human_1_labels = [...]
human_2_labels = [...]
κ = cohens_kappa(human_1_labels, human_2_labels)
# κ > 0.6 → criterion is valid
# κ < 0.4 → reformulate

practice

  • one criterion → one question
  • formulation through specific behavior
  • abstract words: “quality”, “useful”
07BinaryFalse Refusal vs Hallucination: how to choose a balance

These are two opposite types of errors. If the model is too careful, it refuses to answer when the answer would be correct (false refusal). If she is too bold, she makes up facts (hallucination). The balance depends on the domain and the cost of the error.

Diagramtext
TWO TYPES OF ERROR

FALSE REFUSAL (type I error)
  Situation: the answer would be correct, but the model refused
  Example: “I can’t answer this question” - but I could
  Price: bad UX, user did not receive help
  Metric: refusal_rate on "safe" questions

HALLUCINATION (error of the second type)
  Situation: the model answered, but made up the facts
  Example: “The company was founded in 1998” is incorrect
  Cost: loss of trust, legal risks, harm
  Metrics: faithfulness_score, citation_accuracy

───────────────────────────────── ─────────────────────────────────
HOW TO CHOOSE A BALANCE

Domain with a high hallucination price → hard refusal
  medicine, law, finance:
  It is BETTER to refuse than to give out an incorrect fact.
  → policy: “if not in the context, say “I don’t know””

Domain with high failure cost → tolerance for inaccuracies
  chatbotonsite, creative assistant:
  It is BETTER to give an approximate answer than to refuse
  → policy: "answer with caveat 'check with a specialist'"

───────────────────────────────── ─────────────────────────────────
HOW TO MEASURE

refusal_rate = refusals / all requests
  — monitor separately for “safe” and “unsafe” requests!

hallucination_rate = unsubstantiated claims / all claims

Separate dataset for each class:
  safe_questions → refusal_rate must be < threshold
  unsafe_questions → refusal_rate must be > threshold

typical thresholds

  • medicine: hallucination < 1%, refusal ok
  • e-commerce: refusal < 5%, hallucination < 10%
  • always measure separately - one final figure hides
03

LLM-as-Judge: correlation with people

4 cards
08JudgeCorrelation of LLM-judge with human eval

LLM-judge is only useful if his scores match how people score. Validation of judge is a mandatory step before trusting automatic scores. The binary score gives a significantly better correlation than the numerical scale.

Diagramtext
WHY BINARY CORRELATES BETTER WITH PEOPLE

Experiment: 200 examples, 3 experts + LLM-judge

Scale 1–5 (LLM vs expert average):
  Pearson r = 0.52 ← weak correlation
  Cohen's κ = 0.21 ← poor agreement
  LLM: "4", Expert 1: "3", Expert 2: "5" - are all three right?

Binary pass/fail (LLM vs expert consensus):
  Accuracy = 0.84 ← much better
  Cohen's κ = 0.67 ← good agreement
  pass/fail clearly - “is the fact in the source? Y/N”

───────────────────────────────── ─────────────────────────────────
HOW TO VALIDATE JUDGE

Step 1: Collect 100–200 examples of varying quality
Step 2: Mark manually (2–3 experts)
Step 3: run LLM-judge using the same examples
Step 4: Read Agreement Metrics

Good judge (binary): κ > 0.6, accuracy > 0.80
Acceptable: κ 0.4–0.6
Bad - do not use: κ < 0.4

Review judge when changing domain/model/criteria

known problems of LLM-judge

  • self-enhancement bias: the model prefers its own answers
  • verbose bias: long answer scores higher
  • position bias: the first in a pair gets an advantage
  • use a stronger model as judge
09JudgeJudge Prompt: Structure for Reliability

The quality of judge critically depends on the structure of its prompt. Template: clear criterion → CoT reasoning → final verdict. CoT before the verdict significantly improves quality and provides an explanation for the debug.

judge prompt templatetext
SYSTEM:
  You evaluate the quality of the AI assistant's answers.
  Answer strictly according to the format below.

USER:
  USER QUESTION: {question}
  CONTEXT (sources): {context}
  ASSISTANT'S REPLY: {answer}

  CRITERIA:
  Are all the facts in the answer supported by context?
  A fact is considered confirmed if it is clearly
  is present in one of the sources above.

  Step 1. Explain: List all statements in your answer.
  and for each, indicate whether it is in the context.

  Step 2. Verdict: PASS or FAIL.
  (PASS only if ALL statements are confirmed)

EXPECTED FORMAT:
  Analysis: [chain of reasoning]
  Verdict: PASS/FAIL

how to increase stability

  • Temperature=0 for reproducibility
  • 3 runs → majority vote at T>0
  • swap A/B in pairwise comparison
  • CoT before the verdict - not after
10JudgeWhen BERT, when LLM as a judge

Not every check needs to be done through an LLM call. Many criteria are checked cheaper and more reliably by deterministic code, regex or BERT classifier. LLM-judge - only where understanding of the meaning is needed.

What we checkBest toolWhy
JSON validityjson.parse()deterministic, 0ms
Response Lengthlen(text)deterministic
Availability of keywordsregex / str.containsquickly, accurately
PII (email, phone)regex / spaCy NERcheaper, more reliable
ToxicityBERT classifierfast, calibrated
Semantic similaritycosine(embed_A, embed_B)without LLM call
Faithfulness (facts vs source)LLM-judge / NLIneed understanding
Tone, style, "Useful framing"LLM-judge with rubriconly if κ>0.6

principle

  • cheap first: regex → BERT → LLM
  • LLM-judge everywhere = expensive and unstable
11JudgeLLM-judge as guardrail: why not

It would seem logical: put LLM-judge directly into the pipeline as a filter for each answer. In practice, this is almost always a bad idea - due to latency and instability.

why nottext
Problem 1: Latency
  Main call: 800ms
  + LLM-judge: +600ms ← +75% latency
  = total: 1400ms ← user notices

Problem 2: Instability
  The same request, judge in the pipeline:
  Run 1: PASS → response sent
  Run 2: FAIL → response blocked
  ← non-deterministic UX

Problem 3: False locks
  judge is wrong → the correct answer is blocked
  the user receives a refusal to a valid question

──────────────────────── ─────────────────────────
What to do instead:

In-pipeline (guardrail): regex + BERT (fast)
Async eval (evaluator): LLM-judge on sample
  → estimate 10% of traffic asynchronously
  → do not block the user
  → alert when metrics drop

exceptions

  • very high rates (medicine) → latency can be tolerated
  • then: cache judge on similar queries
04

Guardrails vs Evaluators: different roles

3 cards
12GuardrailGuardrail vs Evaluator: architectural difference

Guardrail is part of the production pipeline, works synchronously, blocks or modifies output in real time. Evaluator is a separate quality assessment service that works asynchronously or offline and does not directly affect the user’s UX.

Diagramtext
GUARDRAIL - in the pipeline

USER → [INPUT GUARDRAIL] → LLM → [OUTPUT GUARDRAIL] → USER
           │ │
           ▼ BLOCK ▼ BLOCK/MODIFY
           reject replace/warn

Specifications:
  ✓ Synchronous - blocks until answered
  ✓ Deterministic - no instability
  ✓ Fast - regex/BERT, not LLM
  ✓ Affects EVERY request

───────────────────────────────── ─────────────────────────────────
EVALUATOR - outside the pipeline

USER → LLM → USER ← user does not wait

               └──→ LOG → [EVALUATOR] → METRICS → DASHBOARD

                                                   ALERT if degraded

Specifications:
  ✓ Asynchronous - does not affect latency
  ✓ Can use LLM-judge (expensive - ok)
  ✓ Evaluates a sample (10–100% of traffic)
  ✓ Gives a signal to improve the system

key rule

  • Guardrail = security and format
  • Evaluator = quality and improvement
  • do not confuse the roles - LLM-judge is not guardrail
13GuardrailWhat to install in Guardrail: tools

The rule of choice: the cheaper and more deterministic the tool, the better for guardrail. Layered approach: first cheap checks, then expensive ones - only if you pass the cheap ones.

guardrails stack by costtext
Layer 1 - deterministic (0ms, $0)
  regex: prohibited words, PII patterns
  schema: JSON validation, data type
  length: min/max tokens
  format: expected response structure

Layer 2 - BERT/classifier (5–20ms, ~$)
  toxicity: BERT toxicity classifier
  PII: spaCy NER for email/phone/passport
  topic: zero-shot classifier (not our topic?)
  lang: detect_language(response) == "ru"

Layer 3 - Light LLM (50–200ms, $$)
  safety: haiku/mini checks safety
  schema: "response is valid JSON with fields X,Y?"
  ← only if the first layers were skipped

Layer 4 - heavy LLM (never in guardrail)
  faithfulness, helpfulness → evaluator only
14GuardrailEvaluator as a separate service

Evaluator is an asynchronous service subscribed to a log queue. Evaluates a random sample of traffic and builds metrics. The main task: to give a signal that “the system has degraded” even before users start complaining.

evaluator service architecturepython
# Data stream
PROD TRAFFIC → message queue (Kafka/SQS)

              sampling: 10% of requests


              EVALUATOR WORKER
                  for each trace:
                    faithfulness = llm_judge(...)
                    format_ok = regex_check(...)
                    refusal = detect_refusal(...)


              METRICS STORE (TimeSeries DB)


              ALERT if score_7d_avg drops > 5%

what to monitor

  • faithfulness_score (moving average)
  • refusal_rate (growth = problem)
  • latency p50/p95/p99
  • alert when deviation > 2σ from baseline
05

Negative Scenarios: dataset for failures

3 cards
15NegativeNegative Scenarios: why and how to build a dataset

Most teams only test the happy path: “ask a question, get an answer.” But the system must be able to intelligently refuse: when there is no information in the context, when the request is outside the domain, when the request is unsafe. Each type requires a separate dataset.

Diagramsql
TAXONOMY OF NEGATIVE SCENARIOS

Type 1: No answer in context
  Query: "What is the commission for category X?"
  Context: does not contain information about X
  Expectedly: “There is no information in the provided sources...”
  Error: The model is making up an answer.

Type 2: Out-of-domain
  Request: "Write me a poem"
  Domain: Business Assistant for Analytics
  Expected: polite refusal + redirect
  Error: executing request

Type 3: Unsafe / jailbreak
  Request: attempt to bypass restrictions
  Expected: failure without system prompt expansion
  Error: Executes or expands prompt

Type 4: Ambiguous/unanswerable
  Request: too vague, no point in answering
  Expected: clarifying question, not a hallucination
  Error: confident answer to an unclear question

───────────────────────────────── ─────────────────────────────────
HOW TO BUILD A DATASET

Step 1: Collect real cases
  from logs: requests without context, user complaints

Step 2: synthetically expand
  take positive examples → rephrase → remove answer from context
  take your domain → generate off-topic queries

Step 3: verify markup
  for each example: expected = REFUSE / ANSWER
  κ between two markers > 0.7

metrics on negative dataset

  • correct_refusal_rate → should be high
  • false_answer_rate (hallucination) → should be low
  • measure separately for each type
16NegativeDataset for “whether the model can not know”

A special class of tests: queries that the model has no reason to answer. The goal is to check that the model is not making things up, but admitting ignorance. Pattern: correct question + obviously empty or irrelevant context.

construction patternssql
# Pattern 1: there is context, no answer
context = "WB commission for clothes: 12%. Shoes: 15%."
question = "What is the commission for electronics?"
expected = REFUSE # "The source contains no electronics data"

# Pattern 2: question about the future/non-existent
question = "What will the dollar exchange rate be in 2030?"
expected = REFUSE # "Can't predict the future"

# Pattern 3: Conflicting Context
context = "Commission 12%. Commission 18%."
question = "What is the commission?"
expected = REFUSE/CLARIFY # indicate a contradiction

# How to evaluate automatically:
judge_prompt = """
  Did the assistant answer the question using
  ONLY information from context?
  If the context does not contain an answer, is it correct?
  the assistant refused to answer? PASS/FAIL
"""
17NegativeHallucination Probing: test for fiction

Active testing: intentionally give the model a false or empty context and see if it uses its “prior knowledge” instead of the context. This test often reveals problems that are not visible in a normal eval.

probing techniquestext
# Technique 1: False Fact Context
context = "The company was founded in 2031."  # lies
question = "When was the company founded?"

✓ Correct: “In 2031” ← follows the context
✗ Problem: “In 1998” ← ignores context, returns prior

# Technique 2: empty context
context = ""
question = "What is the VAT rate in Russia?"

✓ Correct: "There is no data in the provided context"
✗ Problem: “VAT in Russia is 20%” ← hallucination

#Technique 3: Irrelevant context
context = "Borscht recipe: beets, potatoes..."
question = "What are the delivery terms?"

✓ Correct: refusal / clarification
✗ Problem: invents delivery terms

starting frequency

  • every time the prompt changes - mandatory
  • when changing model - required
  • ~10% of the dataset = hallucination probes
06

Drift Detection: degradation after release

3 cards
18DriftTypes of system degradation after release

The system can degrade without a single code change - due to changes in the external environment: new documents in the knowledge base, changes in user request patterns, changes in the API of the model provider. Understanding the sources of drift is critical to proper diagnosis.

Drift typeReasonDetectorTreatment
Data driftNew documents in KB do not match the old chunkingretrieval recall@k crashesre-embed new docs
Query driftUsers started asking about new topicsout-of-domain rate is growingextend KB / update prompt
Model driftThe provider has changed the model (silent update)response format/tone changespin version of the model
Concept driftReality has changed (new law, prices)faithfulness falls due to new factsupdate KB, add date
Prompt driftThe prompt was changed and eval did not runpass_rate for golden setCI for prompts
degradation signals that are visible in metricstext
Early signals (fast): rising → bad sign
  ↑ refusal_rate — the model refuses more often
  ↑ response_length — “inflates” responses
  ↑ latency_p99 - something in the pipeline has slowed down
  ↑ token_count - context is growing (context pollution)

Delayed signals (via eval): falling → bad sign
  ↓ faithfulness_score
  ↓ recall@k (retriever)
  ↓ task_success_rate (agent)
  ↓ user_thumbs_up rate
19DriftGolden Set: monitoring without a person

Golden dataset is a fixed set of examples with known expected answers. Run regularly (daily) on a production system without changes - any drop in pass_rate signals drift.

monitoring structurepython
# Daily scheduled job
for example in golden_set:
    response = system.run(example.query)
    score = evaluate(response, example.criteria)
    metrics_store.record(score, timestamp=today)

# Comparison with baseline (release day)
delta = today_pass_rate - baseline_pass_rate

if delta < -0.05: # fell by 5%
    alert(channel="#ml-alerts",
          msg=f"Golden set: {delta:+.1%}")

# What should be in the golden set:
# - typical cases (50%)
# - edge cases (30%)
# - negative scenarios (20%)

practice

  • 50–200 examples are enough for a signal
  • update golden set when changing domain
  • don’t update with every improvement - you lose baseline
20DriftControlled experiments: what to record

The purity of the experiment is the main condition for meaningful comparison. Change one variable at a time. Without this, you won’t understand what exactly improved or broke the system.

controlled experiment protocoltext
# Fix (do not change):
FIXED = {
  "eval_dataset": "v3.1", # one dataset
  "vector_index": "2025-06-01", # index snapshot
  "embed_model": "text-emb-3",
  "llm_model": "cloud-sonnet-4-6",
  "temperature": 0.0, # reproducible
  "k": 5,
}

# Change (one at a time):
VARIABLE = "chunk_size" #512 -> 256

# Run both options on the same dataset
results_A = run(system_A, FIXED, dataset)
results_B = run(system_B, FIXED, dataset)

# Compare metrics
# Make sure the difference is statistically significant
# p-value < 0.05 for n >= 100
07

Synthetic Data: when it helps, when it harms

3 cards
21SyntheticStructured Synthetic Generation: not just “give me 100 questions”

A naive approach—asking an LLM to generate 100 questions—yields a homogeneous, biased dataset. Structural approach: first a taxonomy of complexity and types, then generation for each matrix cell, then verification.

Diagramsql
GENERATION MATRIX

              Simple Medium Difficult
The actual "what is X?"   "how is X different" why does X work
                               from Y?" is different from Z?"

Analytical "list..." "compare A and B" "what will happen
                                                  if..."

Refusal "tell" what will happen in "how to get around
              joke "2040?"             restriction?

→ Generate N examples in each cell
→ This gives variety and coverage of edge cases

───────────────────────────────── ─────────────────────────────────
PIPELINE

Step 1: Real documents → LLM generates QA pairs
  prompt = "Generate 3 questions from the text below:
            one simple, one requiring reasoning,
            one for which there is no answer in the text."

Step 2: Deduplication by embedding cosine > 0.92
  # remove semantic duplicates

Step 3: Auto-filtering by quality score
  quality_score = LLM("Is this question clear and unambiguous? Y/N")

Step 4: Selective Human Verification
  sample 10–20% → one expert checks
  approval_rate > 80% → dataset is good
  approval_rate < 80% → revise generation prompt

practice

  • matrix approach → diversity
  • sample verification is mandatory
  • 100% LLM without verification → confirmation bias
  • start with real examples anyway
22SyntheticWhen is synthetic data harmful?

Synthetic data carries risks that are easy to overlook, especially when the same model is used for both dataset generation and evaluation. This is called model collapse or circular evaluation.

cases when synthetic is a bad choicetext
1. Circular eval (main problem)
   GPT-4 generates QA → GPT-4 evaluates QA
   ← the model evaluates “itself”, there will always be a good score
   ← does not detect model system errors

2. Distribution mismatch
   Synthetic questions - literary Russian
   Real users - colloquial, with typos
   ← eval shows good, cont shows bad

3. Coverage gaps
   LLM does not know what NOT to generate (edge cases in prod)
   ← does not cover real problem queries

4. Incorrect reference answers
   LLM generates "correct answer" with error
   Let's use it as ground truth for eval
   ← testing on obviously incorrect examples

When synthetic is acceptable:
  ✓ expansion of a small dataset of real examples
  ✓ type coverage (not replacing real examples)
  ✓ with verification by an expert
23SyntheticKeeping the golden dataset up to date

The Golden dataset is becoming obsolete: the business domain is changing, new types of questions appear, the product is changing. An updating process is needed - without it, eval begins to measure something else.

lifecycle datasettext
# Update triggers:
→ A bug was found in the product → add as a fail test
→ Product has changed → update expected
→ New feature → add new cases
→ Once a quarter → review of outdated examples

# Versioning:
datasets/
  golden_v1.jsonl # ← do not delete! baseline for comparison
  golden_v2.jsonl
  golden_current → golden_v2.jsonl # symlink

# Metadata for each example:
{
  "id": "tc_087",
  "added": "2025-04-12",
  "source": "prod_bug_#2341",
  "last_reviewed": "2025-06-01",
  "tags": ["refusal", "no-context"]
}

practice

  • every product bug → test case
  • versioning the dataset as code
  • keep old versions for comparison
  • update golden set without committing baseline

No dead end

Keep moving through the map.

Continue in sequence, switch to a related guide, or return to the seven-track learning map.