Quality and safety
Offline and online evals
Offline evals run on a fixed dataset before you ship. Online evals score live traffic. You need both, and every run should record its versions.
An offline eval scores the system on a fixed dataset before it ships. An online eval scores real traffic after it ships. Offline evals catch regressions you can predict. Online evals catch failures you never put in the set. You need both.
Two kinds, two jobs
| Offline | Online | |
|---|---|---|
| Runs on | A fixed dataset | Sampled live traces, thumbs, A/B tests |
| When | Every meaningful change, in CI | Continuously |
| Catches | Regressions | New phrasing, seasonal topics, provider drift |
| Weakness | Only covers what you thought to test | Signals are noisy |
Running offline evals in CI
Split the offline suite into three tiers, so cheap checks run often and slow ones run less often.
| Suite | When it runs | What is in it | Gate |
|---|---|---|---|
| Smoke | Every PR that touches prompts or tools | 20 to 40 fast code assertions | Must pass |
| Full offline | Merge to main, or nightly | All categories, plus judges | Compared to a baseline threshold |
| Adversarial | Weekly or before release | Injection, jailbreak and PII probes | Reviewed, not always a hard fail |
Stochastic checks can be flaky. Use temperature 0 where you can, average several trials, or use enough cases that a few points of noise is expected.
What online evals catch
Online evals find what the offline set missed. Sample production traces, put them into the same failure categories, and grade a slice with the same checks. Thumbs, edits and escalations are weak labels.
Warning
A thumbs-down can mean "wrong answer," "too slow," or a misclick. Use these signals to choose which traces to read. Do not use them as the only quality number.
When an online failure is real, make it an offline case within a day. That is how the loop gets stronger over time.
Record every run
Store each eval run as a row with these fields: timestamp, git_sha, prompt_version, model_id, retrieval_config, suite_id, category, pass_rate, n, cost_usd and p50_latency_ms. A chart of pass rates means little unless you know which prompt and model produced it.
When you want to learn something, change one thing per experiment. If you change the model and the prompt together and the score moves, you cannot tell which change did it.
Example
Model upgrade in shadow mode: the candidate model gets a copy of production traffic and is scored asynchronously. Users still see the current model. Promote the candidate only when the offline suite and a sampled online slice both look good.
Common mistakes
- Treating thumbs as the KPI. They are noisy and they skew toward strong feelings.
- Changing two things at once. You lose the ability to attribute the change.
- Unversioned prompts. Without a version, a pass-rate chart is guesswork.
Check yourself
- A new model passes the offline suite, but users complain about a topic the suite never covered. Which eval caught it, and what now? The online eval caught it. Sample those traces, label them into a category and add them as offline cases.
- Why change one axis at a time? So you can tell which change moved the pass rate.
Key takeaways
- Offline suites guard against regressions. Online sampling finds what you did not test.
- Run a smoke suite in CI on every prompt or tool change.
- Store every run with its prompt, model and config versions.
- Feed every real failure back into the offline set.
Further learning
Next, learn why small suites give noisy numbers in eval noise and sample size. When you A/B two prompts in production, use the same care as in experimentation pitfalls.
Related
Evaluating LLM apps
A fixed set of inputs plus a way to score outputs, so you can tell whether a change helped.
LLMOps and observability
Tracing, prompt management, feedback loops and online monitoring for LLM applications.
Experimentation pitfalls
Peeking, metric fishing, sample ratio mismatch, novelty effects, network interference, CUPED and Twyman's law — how trustworthy experiments go wrong and how to catch them.