LLMOps: versioning, eval gates, LLM judges and guardrails
Before this lesson: lesson 02 (behavioural and regression tests, McNemar's test: an eval suite is the same idea); lesson 06 (release gates, canaries, rollback); how LLMs are evaluated and retrieval, tools and agents; hypothesis testing and Bayes' rule (base rates). Time: about 55 minutes, plus the notebook.
Why this matters #
A one-sentence prompt edit can break the output format for 3% of requests. A provider can change the model behind an alias such as "latest". A new embedding model can make retrieval worse while every answer still looks fluent. Evals are the tests of an LLM application, and the gate in CI is where they protect users. "How do you evaluate and ship a change to an LLM feature?" is a standard question for ML engineer roles as of 2026. The same holds in science: an assistant that answers questions about a climate model's configuration, or a pipeline that turns field reports into database rows, needs the same gate.
1. Predict first #
2. The picture #
The application is a function of its configuration. Write
The configuration holds the prompt template, the model snapshot, the decoding parameters, the
tool definitions and the retrieval setup. A simple example: an app reads a weather station's
maintenance report and returns JSON with the fields station_id, rain_mm and sensor_ok. Any
change to can change the output for any input.
Three consequences give the whole lesson:
- Version as one unit, so that every output can be traced back to the configuration that made it.
- Test each new on a fixed set of inputs before it ships (the eval gate), and compare it with the current on the same inputs.
- Check inputs and outputs at run time (guardrails), for the cases that no test set covers.
Three levels of evals (Hamel Husain). Level 1: assertion checks in code; cheap, run on every change. Level 2: human and LLM-judge review of logged traces (the input, the retrieved text, the output, latency and cost of one request), on a regular schedule. Level 3: A/B tests with users, after large product changes. The cost grows from level 1 to level 3.
Why compare case by case. Run the baseline and the candidate on the same golden cases. Most cases give the same result for both: easy cases pass for both, hard cases fail for both. They cancel in the difference. Only the cases where the two versions disagree say which one is better.
Move and and watch the two gates. Each cell is one golden case: blue = only the baseline passes (), orange = only the candidate passes (), light = both pass, grey = both fail. The number line shows the drop in pass rate (orange dot) and its one-sided 95% upper bound; the aqua line is the margin. With , the significance gate passes (), but the upper bound (6.85 points) is above the 5-point margin. Now set : the same rates give a tighter bound.
On 200 golden cases, 170 pass for both versions, 20 fail for both, 6 pass only for the baseline and 4 pass only for the candidate. How many cases carry information about which version is better?
Only the discordant cases, , differ between the versions. The 190 concordant cases add the same amount to both pass rates and cancel in the drop point. The paired test uses exactly these 10 cases.
3. Definitions and main results #
One app version #
Pin and record, in one configuration file:
- prompt: the template in the repository (or a prompt registry), with an id or a content hash;
- model: a dated snapshot id, not a moving alias such as "latest";
- decoding: temperature, top-p, maximum tokens, stop sequences, and a seed if the provider supports one;
- tools: names and JSON schemas;
- retrieval: the embedding model and its version, the chunking (size, overlap), the corpus snapshot, the index build id, top-, the reranker;
- the judge (its model and prompt), because a new judge changes the scores.
The hash of this file is the app version. Log it with every trace. Then a regression points to a version, and rollback works as in lesson 06.
A new embedding model needs a new index. An embedding model maps text to vectors. Vectors from two different embedding models live in different spaces, so a query vector from the new model cannot be compared with document vectors from the old one. Re-embed the whole corpus, build a new index, switch to it in one step (keep the old one for rollback), and evaluate retrieval itself: recall@ (the share of labelled relevant documents that appear in the top results), not only the final answers.
The eval suite as a regression gate #
- Golden set. Curated inputs with an expected output or a grading rubric. Build it from real traces, above all from failures, with slices (topics, languages, hard and adversarial cases). Keep a held-out part that is never used to tune prompts. Every production failure becomes a new case, so the set does not go stale.
- Assertion checks. Deterministic code: the JSON matches the schema, required fields exist, cited ids are among the retrieved chunks, numbers are in range, out-of-scope questions are refused, no personal data appears. Exact, cheap, and run on every change.
- LLM judge. For qualities that code cannot check: faithfulness to the retrieved text, helpfulness, tone. A binary pass/fail with a written rubric is easier to calibrate than a 1–5 score.
- Noise. With temperature above 0 (and often even at 0, because serving is not always deterministic), the same case can pass on one run and fail on another. Run each case several times (the eval framework Inspect calls these epochs) and use the mean.
- The gate rule, fixed before the run: block if any critical assertion fails; block if the drop in pass rate may exceed a margin, overall or on an important slice (the non-inferiority test below); block if cost or latency exceeds its budget.
Result 1: the paired comparison and non-inferiority #
Run the baseline and the candidate on the same cases. Let be the number of cases that only the baseline passes, and the number that only the candidate passes. (These are the discordant pairs of lesson 02.)
- The drop. The drop in pass rate is : cases that both pass or both fail cancel.
- McNemar's test. Under (no difference), each discordant case goes either way with probability . So, given , .
- The p-value for a regression is one-sided: with . For large , is approximately standard normal, and is McNemar's statistic.
- The paired standard error. Write for the per-case drop (baseline minus candidate). Its sample standard deviation divided by is the standard error of the drop:
- Non-inferiority. Fix a margin in advance: the largest drop you accept. The candidate passes the gate only if the one-sided 95% upper bound of the drop is below the margin:
Why the two tests differ. McNemar's test asks "is there any evidence of a drop?". A large p can just mean that there were too few discordant cases to see anything. The non-inferiority gate puts the burden of proof on the candidate: it must show that the drop is small.
Why pairing helps. An unpaired test treats the two pass rates as independent and adds their variances. But both rates move together with the difficulty of the cases. The paired test uses only the discordant cases, so it has a smaller standard error (worked example A: 2.34 points instead of 3.62).
On cases, and . The margin is 3 points. McNemar's one-sided p is 0.11 and the paired standard error is 1.41 points. What does the non-inferiority gate say?
The drop is points, but it is uncertain. Its one-sided 95% upper bound is points, above 3. The data cannot rule out a drop larger than the margin, so the gate blocks. A large p-value only says that the test did not see a clear drop; it is not evidence that the drop is small.
LLM-as-judge and its biases #
A judge model can compare two answers (pairwise comparison), grade one answer (single-answer grading), or grade against a reference answer (reference-guided grading). Zheng et al. (2023) found that GPT-4 as a judge agreed with human experts on 85% of pairwise votes (without ties), while two humans agreed on 81%. They also measured these biases:
| Bias | What it is | Numbers (Zheng et al., 2023) | Mitigation |
|---|---|---|---|
| position | prefers an answer because of its place | after swapping the order, GPT-4 gave a consistent verdict in 65% of cases, Claude-v1 in 24% | judge both orders; count a win only if both agree, else a tie |
| verbosity | prefers longer answers | a padded "repetitive list" answer fooled Claude-v1 and GPT-3.5 in 91% of cases, GPT-4 in 9% | a rubric that penalises padding; length-controlled comparisons |
| self-preference (self-enhancement) | prefers answers from its own model | GPT-4 gave itself a 10% higher win rate, Claude-v1 25%; the authors could not confirm the bias with their limited data | a judge from another model family, or several judges |
Judges are also weak at grading maths: they are misled by the answer they grade. On 20 judgements of maths answers, a default GPT-4 judge failed 14 times; a reference-guided judge (it first solves the problem itself, then uses its own answer as the reference) failed 3 times.
A judge prefers answer A when A is shown first, and answer B when B is shown first. What do you record?
The two verdicts disagree, so the preference comes from the position, not from the answers. The standard mitigation is to judge both orders and count a win only when both agree. Dropping such cases would bias the set toward easy comparisons.
Calibrating the judge against an expert #
A domain expert labels 100–200 traces as pass or fail, each with a one-line critique. Split them into a training part (examples for the judge prompt), a development part (to iterate on the prompt), and a test part (measured once, at the end). With the expert as the truth and "pass" as the positive class:
- TPR (true positive rate) ;
- TNR (true negative rate) ;
- Cohen's kappa . Here is the observed agreement and is the agreement expected by chance, from the judge's pass rate and the expert's pass rate . means chance-level agreement; means perfect agreement.
Raw agreement misleads when failures are rare. A judge that always says "pass" agrees with the expert on every good trace and catches no failure. On traces where 75% pass, it has 75% agreement and .
Try the presets, then move the sliders. The table shows the expected counts on 200 expert-labelled traces (errors in magenta). The bars compare the true pass rate , the pass rate the judge reports, and the corrected rate from Result 2. "Always pass" has 75% agreement but , and the correction is undefined. Press "Draw a labelled set" several times: TPR and TNR estimated from 200 labels are noisy, and so is the corrected rate.
Result 2: correcting the judge's pass rate (Rogan–Gladen) #
Let be the true pass rate on new traces, and the pass rate that the judge reports.
- The judge says "pass" for a good trace with probability TPR, and for a bad trace with probability .
- By the law of total probability, .
- Solve for :
valid if . A judge at chance has : its verdict is independent of the truth and carries no information.
This assumes that the judge's TPR and TNR on new traces equal those on the labelled set. So re-check them when the app, the traffic or the judge changes.
An interval for the corrected pass rate
TPR and TNR come from few labels, so is noisy. A bootstrap gives an interval: resample the labelled traces with replacement (and the new traces, if they are few), recompute , , and , repeat 1,000–10,000 times, and take the 2.5% and 97.5% quantiles. Clip to : with noisy estimates the formula can leave this range. The denominator is Youden's ; when it is small, the interval is wide. The notebook implements this.
A judge has TPR = 0.90 and TNR = 0.60. On new traces it reports a pass rate of 0.85. What is the corrected pass rate?
. Check: ✓. Here the judge is harsh on good traces (TPR 0.90) more than it is lenient on bad ones, so the true pass rate is higher than reported. 0.765 is , a common wrong shortcut.
Guardrails #
Guardrails are run-time checks around the model:
- input checks: prompt-injection and jailbreak detection, detection or removal of personal data, a topic or scope filter, size limits;
- output checks: schema validation (a JSON schema, or a structured-output mode that constrains decoding), policy and toxicity classifiers, personal-data leaks, grounding checks (claims and citations supported by the retrieved text), an allow-list for tool calls;
- on failure: block with a safe message, repair or retry (one more model call), fall back to a simpler path, or send to a human.
Prompt injection is an input that makes the model follow the attacker's instructions instead of the app's, either directly or hidden in a retrieved document or web page. Guardrails reduce this risk; they do not remove it. As of 2026, the OWASP Top 10 for LLM applications lists prompt injection first (LLM01:2025) and says that it is unclear whether fool-proof prevention exists. So also limit what the model can do: tools with narrowly scoped credentials, and human approval for high-risk actions.
Costs.
- Latency. Each check is a classifier or an LLM call. Input checks can run in parallel with generation. Output checks need the output, which conflicts with streaming (buffer it, or check chunks).
- Tokens and money. An LLM-based check can cost as much as the main call; a retry after a schema failure is a second call.
- False positives. Valid requests are blocked. When attacks are rare, most blocks hit valid users (base-rate effect, worked example C).
- False negatives and maintenance. No check is complete. The thresholds and prompts of the checks are versioned and evaluated like the app itself.
Lower the attack rate and watch the right panel. Left: 1,000 requests; orange squares are attacks (solid = blocked, outlined = missed), magenta squares are valid users who are blocked. Right: 100 blocked requests. At 0.5% attacks, a detector that catches 95% of attacks and flags 2% of valid requests has a precision of 19%: about four of five blocks hit a valid user. With "common attacks" the same detector is precise.
0.1% of requests are attacks. A detector catches 99% of them and flags 1% of valid requests. What share of the blocked requests are attacks?
Per 100,000 requests: 100 attacks, of which 99 are blocked; 99,900 valid requests, of which 999 are blocked. Precision . This is Bayes' rule with a small prior: the 1% false-positive rate applies to almost all requests, the 99% detection rate only to a few.
Answers to section 1. (1) No, not on this evidence. With and , McNemar's one-sided p-value is 0.14, so a significance test does not block. But "not significant" is not "safe": 22 discordant cases give little power. The paired standard error of the 3-point drop is 2.34 points, so the one-sided 95% upper bound of the drop is points. A gate with a 5-point margin blocks it. Read the 14 failures, check the slices, and add cases or epochs. (2) Not necessarily. In worked example B, a judge with 88% agreement misses 30% of the real failures (TNR = 0.70), and an always-pass judge reaches 75% agreement on the same traces. Look at TNR and , not at raw agreement.
4. Worked example #
A. The paired gate. , the baseline passes 172, , , so the candidate passes .
- Drop: , 3 points.
- Exact one-sided p-value: for , which is . The normal approximation: .
- Paired standard error: .
- Unpaired standard error: . Pairing removes about a third of the noise.
- Gate with a 5-point margin: upper bound . Block and investigate.
B. Calibrating a judge. An expert labels 200 traces: 150 pass, 50 fail. The judge says "pass" on 141 of the 150 and "fail" on 35 of the 50.
| judge pass | judge fail | total | |
|---|---|---|---|
| expert pass | 141 | 9 | 150 |
| expert fail | 15 | 35 | 50 |
| total | 156 | 44 | 200 |
- , .
- Agreement .
- The judge says "pass" on of the traces, the expert on . So and .
- An always-pass judge: , , .
- On new traces the judge reports . Corrected: . Check: ✓.
The judge is lenient (it passes 30% of the failures), so the true pass rate is lower than reported. Improve its TNR first: add the expert's critiques of failures to its prompt.
C. A guardrail and the base rate. 0.5% of requests are injection attempts. A detector catches 95% of them and flags 2% of valid requests. Per requests:
- Attacks: . Blocked: . Missed: 250.
- Valid requests: . Blocked: .
- Precision of a block: .
About four of five blocks hit a valid user. Use a higher threshold, or a softer action (log, warn, send to human review) instead of a block.
5. In code #
NumPy, SciPy and scikit-learn check the three worked examples; the JSON check uses only the standard library.
import json
import numpy as np
from scipy import stats
from sklearn.metrics import cohen_kappa_score
# A. Paired gate: McNemar's exact test on the discordant cases
b, c, N = 14, 8, 200
r = stats.binomtest(b, b + c, 0.5, alternative="greater")
d = np.r_[np.ones(b), -np.ones(c), np.zeros(N - b - c)] # per-case drop: +1, -1 or 0
se = d.std(ddof=1) / np.sqrt(N)
print(f"p = {r.pvalue:.3f}, drop = {d.mean():.3f}, SE = {se:.4f}, upper = {d.mean() + 1.645 * se:.4f}")
# p = 0.143, drop = 0.030, SE = 0.0234, upper = 0.0685
# B. Judge vs expert: TPR, TNR, kappa, and the corrected pass rate
expert = np.r_[np.ones(150), np.zeros(50)]
judge = np.r_[np.ones(141), np.zeros(9), np.zeros(35), np.ones(15)]
tpr, tnr = judge[expert == 1].mean(), 1 - judge[expert == 0].mean()
print(f"agreement = {(judge == expert).mean():.2f}, kappa = {cohen_kappa_score(expert, judge):.3f}, "
f"TPR = {tpr:.2f}, TNR = {tnr:.2f}")
# agreement = 0.88, kappa = 0.667, TPR = 0.94, TNR = 0.70
print(f"always-pass kappa = {cohen_kappa_score(expert, np.ones(N)):.1f}") # 0.0
print(f"corrected pass rate = {(0.80 + tnr - 1) / (tpr + tnr - 1):.3f}") # 0.781
# C. An output guardrail: check the model's JSON against a small schema
SCHEMA = {"station_id": str, "rain_mm": float, "sensor_ok": bool}
def valid(raw):
try:
obj = json.loads(raw)
except json.JSONDecodeError:
return False
return isinstance(obj, dict) and all(isinstance(obj.get(k), t) for k, t in SCHEMA.items())
print(valid('{"station_id": "S1", "rain_mm": 12.5, "sensor_ok": true}')) # True
print(valid('{"station_id": "S1", "rain_mm": "a lot", "sensor_ok": true}')) # False: retry, repair or fall back
In a real service, a schema library (for example Pydantic or jsonschema in Python) does the
same check with better error messages, and structured-output modes of many model APIs constrain
decoding so that the JSON is valid by construction.
6. Where it shows up #
- LLM products. RAG assistants, coding agents, extraction pipelines, support chat. Providers retire model snapshots, so a forced migration is a change like any other: run the gate.
- Science. An assistant over the documentation of an Earth-system model (namelists,
parameters); a literature assistant; an agent that writes job scripts for an HPC scheduler such as
Slurm (an assertion check can run
sbatch --test-only, which validates a script without submitting it); extraction of field reports into a database (schema validation plus a golden set). - Tools (as of 2026). Inspect (UK AI Security Institute): a task is a dataset, a solver and a
scorer; built-in scorers include
includes(),match(),model_graded_qa()andmodel_graded_fact();epochsrepeats each sample. Trace stores such as Langfuse or LangSmith keep the logged traces that feed level-2 review and the golden set. - Same ideas, other lessons. The eval gate is the release gate of lesson 06 with an LLM in the loop; the paired test is the slice test of lesson 02; watching judge scores over time is the monitoring of lesson 08.
7. Common mistakes #
- A model alias instead of a pinned snapshot. The app changes without a commit.
- A new embedding model on the old index. Re-embed the corpus and rebuild the index.
- One run per case at temperature above 0, compared unpaired. Use epochs and a paired test.
- "Not significant" read as "safe". Use a non-inferiority margin fixed in advance.
- The judge as ground truth. Measure TPR and TNR against an expert; report kappa, not raw agreement.
- Tuning the judge prompt (or the app prompt) on the test cases. Keep a held-out part.
- A golden set that never changes. Add production failures to it.
- Guardrails without numbers. Measure their false-positive rate and added latency like any other model.
8. Check yourself #
- List what goes into the app version of a RAG assistant.
Answer
The prompt template id or hash; the model snapshot id; the decoding parameters; the tool schemas; the embedding model and version, chunking, corpus snapshot, index build id, top- and reranker; the judge model and prompt. Its hash is logged with every trace. - On a 50-case safety slice, the baseline passes 3 cases that the candidate fails, and the reverse
never happens. What is the one-sided exact p-value, and what does it teach?
Answer
for is . A small slice cannot reach significance, so critical behaviour needs hard assertion gates (any failure blocks), not a significance test. - Why does a new embedding model need a full re-index, and what do you evaluate before switching?
Answer
The new model maps text into a different vector space, so query vectors cannot be compared with old document vectors. Re-embed the corpus, build a new index, and compare retrieval (recall@ on labelled query–document pairs) and end-to-end answers with the old version. - A judge has TPR = 0.80 and TNR = 0.20. What is its Youden's ,
and can you correct its pass rate?
Answer
. The judge says "pass" with probability 0.80 whatever the truth, so its verdict is independent of the truth. The correction divides by 0: the judge is useless. - Why should the temperature of the app not be set to 0 just to make evals reproducible?
Answer
The eval must test the configuration that users get. If production samples at temperature 0.7, an eval at 0 measures another app. Keep the production settings and run several epochs per case, then use the mean pass rate. - A judge-based metric on production traces drops from 0.90 to 0.84 after a provider changes the
judge model behind an alias. Did the app get worse?
Answer
Not necessarily: the judge is part of the measurement, and it changed. Pin the judge's snapshot, re-measure its TPR and TNR on the expert-labelled set, and compare the corrected pass rates. Otherwise a change of the ruler looks like a change of the app.
What goes into one version of an LLM application?
The prompt template, the model snapshot, the decoding parameters, the tool schemas, the retrieval setup (embedding model, chunking, index build, top-), and the judge. Its hash is logged with every trace.
Why does a new embedding model need a full re-index?
Vectors from two embedding models live in different spaces, so new query vectors cannot be compared with old document vectors.
Name the three levels of LLM evals.
Level 1: assertion checks in code. Level 2: human and LLM-judge review of traces. Level 3: A/B tests with users.
In a paired eval of baseline and candidate, which cases does McNemar's test use, and what is its null distribution?
Only the discordant cases: (only baseline passes) and (only candidate passes). Under no difference, .
Why is "not significant" not enough to ship a candidate?
With few discordant cases the test has little power. A non-inferiority gate asks that the upper bound of the drop is below a margin fixed in advance.
How do you reduce position bias in a pairwise LLM judge?
Judge both orders; count a win only when both verdicts agree, otherwise record a tie.
Write Cohen's kappa.
, with the observed agreement and the chance agreement from the two pass rates.
Write the Rogan–Gladen correction of a judge's pass rate.
, valid when .
Compute the precision of a block: 0.1% attacks, detection rate 99%, false-positive rate 1%.
.
Why run several epochs per eval case when the temperature is above 0?
The same case can pass on one run and fail on another; the mean over epochs reduces this noise.
9. Interview angle #
- "How do you ship a prompt change safely?" A versioned app configuration; a golden set with assertion checks and a calibrated judge in CI; a paired comparison with the baseline, overall and per slice, with a margin fixed in advance; then a canary or an A/B test.
- "How do you know your LLM judge is right?" Expert labels with critiques; TPR, TNR and kappa on a held-out part; correct the pass rate; re-check after changes of the app, traffic or judge.
- "Which biases do LLM judges have?" Position, verbosity, self-preference; weak grading of maths. Name one mitigation for each.
- "What do guardrails cost?" Latency, tokens, false positives (base rates), maintenance; they do not stop prompt injection completely, so also limit tool permissions.
- "How would you evaluate a RAG system?" Retrieval (recall@, MRR) separately from generation (faithfulness, correctness); a calibrated judge; a paired comparison with the current version.
10. Practice #
The notebook for this lesson runs in Colab. It has four exercises, each with a check cell and a folded solution:
- Write
paired_gate(base_pass, cand_pass, margin): drop, exact McNemar p, paired and unpaired standard errors, upper bound and verdict; reproduce worked example A. - Write
judge_report(expert, judge)(TPR, TNR, agreement, kappa) andcorrected_pass_rate(p_obs, tpr, tnr), then a bootstrap interval for the corrected rate. - Write
validate_output(text, schema)with thejsonmodule and count the failures in a list of fake model outputs. - Simulate a pairwise judge with position bias, and show that judging both orders and recording ties removes the bias.
11. Watch and read #
- Hamel Husain, Your AI product needs evals: the three levels (unit tests, human and model eval, A/B tests) and logging traces.
- Hamel Husain, Using LLM-as-a-Judge for evaluation: expert pass/fail labels with critiques, and measuring the judge against the expert.
- Zheng et al., Judging LLM-as-a-Judge with MT-Bench and Chatbot Arena (NeurIPS 2023): §3.3 (position, verbosity, self-enhancement, maths), §3.4 (mitigations), §4.2 (agreement with humans).
- OWASP, LLM01:2025 Prompt Injection: types of injection and mitigation strategies.
- Inspect documentation (UK AI Security Institute): the pages on tasks (epochs), scorers and model grading.
- Wikipedia, Cohen's kappa.
Runs in your browser with nothing to install. The notebook repeats the key demos and has exercises with checks; solutions are inside, folded.
No Google account needed: Python runs inside your browser. The first start downloads about 40 MB; your changes are saved in this browser only.