Skip to content
learn
  1. Learn
  2. Artificial Intelligence
  3. Building AI Products

AI AI-14.05

LLMOps: shipping and iterating

Treat prompts and model choices like code: version them, gate every change on evals in CI, roll out with a canary and roll back fast.

Intermediate 10 min Last reviewed

The big idea

Someone edits one sentence in the support assistant's system prompt to make it friendlier, and ships it on a Friday afternoon. On Monday the refund complaints arrive: the new wording quietly dropped the 30-day rule, and the assistant has been promising refunds it should not. No test failed, because there were no tests. Nobody can say exactly which prompt was live when, because the prompt lived in a string in the code and a copy in someone's notes.

In an AI product, a prompt edit, a new retrieval setting or a switch to a different model changes behavior as much as a code change does, and usually less predictably. So treat them like code. Version every prompt and model setting, run the eval suite in CIContinuous integration: an automated pipeline that builds and tests every proposed change before it can be merged. on every change, release to a small share of users first, and keep a one-click way back.

Think of how a restaurant changes its menu. A new recipe is written down with a date, the kitchen cooks it for staff tasting before it goes on the menu, it is offered as a special to a few tables first, and if diners send it back, yesterday's recipe is still in the book. Nobody improvises a new dish for the whole dining room on a busy night.

How it works

The release path of a prompt or model change Step 1: a prompt or model change is committed with a new version id. Step 2: CI runs the eval suite against the baseline and blocks the merge if any category regresses. Step 3: a canary sends a small share of real traffic to the new version while monitoring compares it with the old one. Step 4: if the canary is healthy, traffic is raised to 100 percent; if not, it is rolled back to the previous version by changing configuration. Step 5: provider model updates enter the same path, through a pinned version and scheduled eval runs, and production failures are added to the eval set. pass healthy roll back scheduled run failures change prompt or model eval gate CI, per category canary 5 percent 100 percent promoted monitoring traces by version previous version still deployed provider update new model snapshot eval set grows from failures Figure 1. Every change, including a provider's, passes the same eval gate and canary, and the old version stays one config switch away. Step through it.
  1. Commit a versioned change The new prompt, model id or retrieval setting is a commit with a version id such as refunds-v15. Nothing is edited in place.
  2. Eval gate in CI CI runs the eval suite on the candidate and the baseline and compares per category. A regression fails the pipeline and blocks the merge.
  3. Canary on real traffic A feature flag sends a few percent of users to the new version. Traces carry the version id, so dashboards compare the two directly.
  4. Promote or roll back Healthy: raise the share step by step to 100 percent. Worse: switch traffic back to the old version, which is still deployed.
  5. Close the loop Pin the provider's model version and re-run evals on a schedule, so their changes go through the same gate. Add every production failure to the eval set.

Version prompts and model settings

Pull prompts out of the code into a prompt registryA store of prompt templates where each version is kept under an id, and configuration decides which version is live.: files in the repository or a small service, each version with an id, never edited in place. The same goes for everything else that shapes behavior: model id, temperature, max tokens, tool definitions, retrieval settings such as top k. Bundle them as one versioned configuration per feature.

Three things follow. Every trace can record which version produced it ( LLM observability and tracing ), so "what changed?" has an answer. Rolling back means pointing configuration at the previous id, which takes seconds. And code review covers prompt changes, so a reviewer sees the diff that dropped the 30-day rule.

Pin the model version

Model providers release new versions and retire old ones. If your configuration names a moving alias, the model under your prompt can change without any commit on your side, and behavior can shift: formatting, refusals, verbosity, tool use. Where the provider offers dated or pinned version ids, use them in production and upgrade on purpose, through the same gate as any other change. Retirement dates still force upgrades, so plan for them instead of being surprised.

Eval gates in CI

An eval gateAn automated check in CI that runs the eval suite on a proposed change and blocks the merge if quality drops below a set threshold. runs your eval set ( Building an eval set ) on every change to prompts, models or retrieval, compares with the current version, and fails the pipeline on a regression. Three rules make gates useful:

  • Compare per category, not just overall. A 2-point overall drop can hide a 20-point collapse in refund questions. Gate on each important slice.
  • Allow for noise. Model outputs vary between runs, and LLM judges ( LLM-as-judge ) vary too. Set a tolerance, run more cases for the slices that matter, or run each case several times. A gate that fails randomly gets ignored.
  • Keep it fast and cheap enough to run every time. A few hundred well-chosen cases run on every pull request; the large suite can run nightly.

Deterministic checks (valid JSON, required fields, forbidden phrases) belong in the gate too. They are cheap, exact and catch the most embarrassing failures.

Canary rollout

Evals only test what you thought to include. Real users ask things your eval set never imagined, and some quality problems only show in behavior, such as users rephrasing, abandoning or asking for a human. So release the change to a small share of traffic first, a canaryA release where a small share of users gets the new version while monitoring compares it with the old one, before the rest of users get it., and compare the two versions on online signals ( Online evaluation and monitoring ): thumbs-down rate, escalations, retries, output validation failures, cost per request and latency.

A feature flagA runtime switch, read from configuration, that decides which users get which version of a feature, without a new deploy. makes this easy: it assigns users (not single requests, so a conversation does not switch versions halfway) to the old or new version. Raise the share in steps, such as 1, 5, 25 and 100 percent, holding each step long enough to see a difference. Before any real traffic, some teams also run a shadowSending a copy of real requests to the new version without showing its output to users, to compare outputs, cost and latency safely. test: the new version answers real requests in the background, and its outputs are compared offline.

Roll back fast

Keep the previous version deployed and live-switchable. Rolling back should be a config change that takes effect in seconds, not a new build. Decide the rollback conditions before the canary starts ("thumbs-down rate more than 1 point above control for 2 hours"), so nobody argues about it at 2 a.m.

Try it

This interactive needs JavaScript. The diagrams and text above cover the same idea.

  1. Step through change 2 (reworded policy). Where is it stopped, and how many users saw it?
  2. Change 3 (always clarify) passes the gate. Step to the canary with 5 percent for 4 hours. Can you tell the versions apart? Raise the duration until you can. Why did the evals miss this one?
  3. Change 4 involves no commit at all. What has to exist in your system for this change to be caught?

The gate itself is a short script. Here the candidate prompt lost the refund window, and two eval cases catch it:

gate.pyruns with python3 (standard library)
# An eval gate as CI would run it: versioned prompts, a pinned model, a fixed eval set,
# and a non-zero exit code when the candidate regresses. The "model" is a fake so it runs offline.
import hashlib, re

PROMPTS = {  # prompt registry: every version is kept, the live one is chosen by config
    "refunds-v14": "Refunds: items can be returned within 30 days. Be brief.",
    "refunds-v15": "Refunds: customers may return items. Be brief and friendly.",
}
CONFIG = {"live": "refunds-v14", "candidate": "refunds-v15", "model": "small-model-2026-06-01"}

EVAL_SET = [  # (question, check on the answer)
    ("Can I return shoes after 3 weeks?", lambda a: "30 days" in a),
    ("What is the refund window?", lambda a: "30 days" in a),
    ("Do you refund gift cards?", lambda a: len(a.split()) < 40),
    ("Return policy please", lambda a: "return" in a.lower()),
]

def fake_model(model, system, question):  # answers only from what the prompt says
    days = re.search(r"\d+ days", system)
    return f"You can return items{' within ' + days.group(0) if days else ''}. Thanks for asking!"

def run(version):
    system = PROMPTS[version]
    passed = [check(fake_model(CONFIG["model"], system, q)) for q, check in EVAL_SET]
    digest = hashlib.sha256(system.encode()).hexdigest()[:8]  # content hash, logged in traces
    return sum(passed) / len(passed), passed, digest

base, base_cases, h1 = run(CONFIG["live"])
cand, cand_cases, h2 = run(CONFIG["candidate"])
print(f"model {CONFIG['model']}")
print(f"{CONFIG['live']} ({h1}): {base:.0%}   {CONFIG['candidate']} ({h2}): {cand:.0%}")
for (q, _), b, c in zip(EVAL_SET, base_cases, cand_cases):
    if b and not c:
        print(f"  REGRESSION: {q}")
ok = cand >= base - 0.02   # tolerance: allow at most a 2-point drop
print("gate:", "PASS, safe to merge" if ok else "FAIL, merge blocked")
exit_code = 0 if ok else 1   # in CI: sys.exit(exit_code) fails the pipeline
print("exit code", exit_code)

Output

model small-model-2026-06-01
refunds-v14 (d70efe15): 100%   refunds-v15 (b7ca63f4): 50%
  REGRESSION: Can I return shoes after 3 weeks?
  REGRESSION: What is the refund window?
gate: FAIL, merge blocked
exit code 1

In CI the script would end with a non-zero exit code and the merge button would stay disabled. The content hash next to each version id is what traces record, so a trace can always be matched to the exact prompt text.

Going deeper

Small effects need big canaries

A canary can only detect differences larger than its noise. With a 6 percent baseline thumbs-down rate, 400 canary conversations give a 95 percent interval of roughly plus or minus 2.3 points, so a 2-point regression is invisible. You need thousands of conversations, which means a larger share or a longer hold. This is why big regressions should be caught by evals before the canary, and why the canary is the net for what evals cannot see, not a replacement for them.

Everything that is a change

Prompt edits are obvious. Less obvious changes that deserve the same path: tool descriptions (the model reads them), retrieval settings and re-indexed documents, a new embedding model, guardrail thresholds, and the provider's model version. If a change can alter what the model sees or does, it gets a version, an eval run and a gradual rollout.

From production back to evals

The loop closes when production failures become eval cases. Each rolled-back canary, user complaint or flagged trace is a case the eval set did not have. Add it, with the expected behavior, and the gate will catch the same failure next time. Over months this is how an eval set grows from a few dozen hand-written cases to one that reflects how users really behave ( Error analysis ).

Trade-offs

OptionWins whenLoses when
Edit prompts in place, no gate A prototype with no users yet Any product: regressions reach everyone and cannot be traced
Eval gate on every change Known failure modes and important categories Problems the eval set does not cover; flaky if too small
Canary rollout Effects visible only with real users and real traffic Low traffic: too few samples to see small differences
Shadow testing Comparing outputs, cost and latency with zero user risk Actions with side effects; no user feedback on shadow answers
Pinned model version Stable behavior and planned upgrades Missing provider fixes until you upgrade; retirement deadlines

Common mistakes

  • Keeping prompts as strings scattered through the code, so nobody knows which version is live or what changed.
  • Gating on a single overall score, so a collapse in one important category hides inside the average.
  • Pointing production at a moving model alias and treating the provider's update as "no change".
  • Declaring a canary healthy after an hour at 1 percent, when the sample is far too small to show anything.
  • Rolling back by rebuilding and redeploying, instead of switching configuration to a version that is still running.

In the wild

GitHub

How we evaluate AI models and LLMs for GitHub Copilot (opens in a new tab)

The Copilot team describes running more than 4,000 offline tests, most of them in its automated CI pipeline, before changing production, plus live internal evaluations similar to canary testing where some employees are switched to a new model. Tests include about 100 containerized repositories deliberately broken so the model must make their tests pass again, and more than 1,000 technical questions, some graded by another LLM. They also run these tests against production models every day and investigate when quality degrades, sometimes by changing prompts, and a proxy lets them switch models without changing product code.

Check yourself

  1. A teammate changed the system prompt and quality dropped. Which practice lets you find and undo the change fastest?

    Show answer

    B. Versioned prompts with the version id recorded on every trace and a config switch back to the old id With versions in traces you see exactly when behavior changed and which prompt caused it, and rolling back is a config change.

  2. Overall eval score went from 91 to 89, within your 3-point tolerance. Refund questions went from 92 to 70. What should the gate do?

    Show answer

    B. Fail, because an important category regressed far beyond tolerance Gate per category. A collapse in one slice can hide in the average, and refund answers are exactly where mistakes cost money.

  3. Your canary got 300 conversations and its thumbs-down rate is 1 point higher than control. What do you conclude?

    Show answer

    C. The sample is too small to tell; hold longer or raise the share With a few hundred conversations the uncertainty is several points wide, so a 1-point difference is noise. Decide sample sizes before the canary starts.

  4. Nobody changed your code, but JSON validity fell from 99 to 88 percent overnight. What is the most likely cause and the lasting fix?

    Show answer

    B. The provider changed the model behind an alias; pin a version and run evals on a schedule A provider model update is a change too. Pinning versions makes upgrades deliberate, and scheduled eval runs catch drift you did not cause.

Recap

  • Prompts, model ids and settings shape behavior like code, so version them in a registry and review their diffs.
  • Run the eval suite in CI on every change, gate per category with a tolerance for noise, and block regressions.
  • Release to a small canary behind a feature flag, compare online signals by version, and size the canary to see real differences.
  • Keep the previous version running so rollback is a config switch decided by rules set in advance.
  • Pin provider model versions, re-run evals on a schedule, and turn every production failure into an eval case.

Get new lessons by email

Get new posts on backend and AI internals. No spam, unsubscribe anytime.

Your email is only used to send new posts. Privacy