Infrastructure

LLMOps: what changes once a model call ships to production

September 28, 20269 min read

A prompt determines the behavior and quality of a system as directly as code does — but it's routinely treated as an implementation detail: a string embedded somewhere in the application, with no history of what changed and why, no easy way to revert, and no way to test a change in isolation before it reaches production. Once a system is live, that gap stops being cosmetic.

Prompts are logic, not strings

The basic fix is version control: prompts live in their own files, tracked like any other change. The more mature version decouples prompt changes from code deploys entirely — a wording change shouldn't require a full redeploy, especially when the person iterating on a prompt (a domain expert, a product manager) isn't the person who owns the deploy pipeline. What actually needs versioning is wider than the text: the model, the parameters (temperature, max tokens), few-shot examples, and the retrieval/tool configuration that ran alongside that specific call — the same prompt text can behave differently across providers, or across versions of the same model. Versioning just the string is versioning half the thing. And a version history without evaluation attached only tells you what changed, not whether it was an improvement — every prompt change should run against an evaluation set before it merges, the same discipline as running tests before merging code.

Evaluation is a snapshot; monitoring is a video

Running an evaluation suite once, before launch, describes quality at that moment, on that sample — it says nothing about a month from now, when the traffic includes question types the original dataset never anticipated, or the underlying documents have changed. Continuous monitoring needs a sampling strategy (evaluating 100% of traffic with an LLM judge isn't affordable — sample continuously, weighted toward recent changes or low-confidence responses), cheap online signals that don't need a judge at all (thumbs up/down, whether a user immediately rephrased the question, whether they exported the answer), drift detection (is this week's average faithfulness holding, is the distribution of incoming questions still close to what the eval set represents), and alert thresholds that turn a metric into an action — a rollback, a page, not just a dashboard nobody checks. The loop closes back to the offline dataset: every real failure monitoring catches should get added to it, or the offline benchmark stays frozen in the moment it was built while production keeps moving.

Cost and latency, per call, not per invoice

A traditional request costs roughly the same regardless of what the client asked for. An LLM call's cost varies enormously per request — input and output tokens, which model, whether retrieval or reranking ran alongside it — and without tracking that per call, tagged with feature and user, the only number available is the total bill at the end of the month, which answers nothing about what's actually expensive. Latency needs the same granularity: beyond the pipeline stages (embedding, retrieval, reranking, generation), streamed responses split into time-to-first-token and total latency — and perceived speed tracks the first number more than the second. And when a single user request triggers a chain of LLM calls — an agent calling a tool, evaluating the result, calling again — the number that matters for the economics of the product is the total cost of that chain, not any individual call inside it.

No single provider, ever

Hardcoding one provider is a single point of failure, the same problem as any other system with no redundancy — and the fix isn't just failover, it's routing tasks to the right model by cost and complexity in the first place, instead of paying for a top-tier model on a simple classification. Fallback has a taxonomy problem: a rate limit or a 5xx is worth retrying elsewhere; a content-filter rejection usually isn't, since it'll likely bounce again for the same reason, adding latency for nothing. And a circuit breaker — stop calling a provider that's repeatedly failing, for a cooldown period, instead of paying the fallback cost on every single request while it's down — is the same pattern used everywhere else in distributed systems. What's genuinely different from a database failover: when a DB replica fails over, the data is identical. When an LLM call fails over to a different provider, the response is not guaranteed to be equivalent — it can differ in format, tone, or correctness — so a fallback response needs its own validation before it reaches a user, not just "the API call didn't error."

Guardrails act before the response ships

There's a real difference between measuring faithfulness on a sample, after the fact, and gating every response, in real time, before it reaches the user. A production guardrail runs a lighter, faster check on every response — the full LLM-judge is too expensive to run on 100% of traffic — and blocks or regenerates on failure, reserving the expensive judge for periodic sampling. Sensitive data moves in two directions worth defending separately: inbound, a user pasting sensitive information into a prompt that leaves the system toward an external provider; outbound, in a multi-tenant system, retrieval returning one tenant's private documents inside another tenant's answer — a leak that structural isolation at the database layer prevents far more reliably than a prompt instruction ever could. And prompt injection has a version specific to RAG that's easy to miss: instructions embedded inside a retrieved document, not the user's direct input — if the system treats retrieved content as something to obey rather than something to cite, that's the opening. The durable defense is architectural: retrieved content never gets authority to change behavior, only to be cited — the same discipline that citation verification already requires, incidentally closing this gap too.

Deploying a prompt like you'd deploy code

A prompt or model change has the same blast radius as a code deploy, but rarely gets treated with the same rigor — a wording change often ships straight to every user on the next request, because "it's just text." Canary deployment applies the same mechanism (route a small percentage of traffic, measure, expand) with one real difference: the signal that something broke isn't fast or binary the way a crash or an error spike is — it's statistical ("faithfulness dropped from 94% to 89%") and needs volume and the same evaluation methodology running specifically on the canary slice. Shadow deployment runs the new version alongside the current one, on a copy of real traffic, without showing its output to anyone — zero user risk, and it closes the gap between a synthetic eval set and what people actually ask, at the cost of doubling API spend for the duration. And the rollback path only works if it's fast: pointing traffic back at the previous prompt version has to be possible without a code redeploy, which is the entire reason prompt versioning gets decoupled from the deploy pipeline in the first place — ideally wired directly to the alert thresholds from monitoring, so a regression reverts itself instead of waiting for someone to notice.

None of this is exotic — it's the same operational discipline any production system needs, applied to a component (a model call) that fails, drifts, and costs money in ways a typical service doesn't.