Back to Checklists
Applied IntelligenceChecklistJuly 3, 2026

Hardening a Model-Agnostic LLM Pipeline: Gateway, Evaluation, and Reproducibility Controls

Quick Answer

A release-readiness checklist for platform engineers shipping an LLM-powered pipeline that must not be locked to a single vendor. It converts the reference architecture — gateway, interchangeable backends, evaluation layer, experiment tracking, and layered determinism controls — into verifiable checks. Run it per release, before a backend swap, and when adding a new provider. Background lives in the linked /learn explainer; this artifact is prescriptive.

This checklist turns the architecture in What building a model-agnostic research pipeline actually took into release-readiness controls for platform engineers and ML tech leads. It targets pipelines that must survive a provider price change, outage, or backend swap without touching application code. For scope and definitions, start with what a model-agnostic AI pipeline is.

It covers the substitution point, backend interchangeability, evaluation, observability, and reproducibility. It does not cover secrets management, adversarial-input handling, or orchestration-framework choice.

Checks28 total17 MUST10 SHOULD1 NICE

How to use this checklist

Run it before a public release, before promoting a new backend to primary, and quarterly against production. Ownership sits with the platform lead for the pipeline. "Done" means every applicable MUST is verified in the run manifest for the current release, and every SHOULD has either a passing signal or a written exception.

Two controls depend on your deployment shape. The reproducibility section splits into hosted-API and self-hosted tracks — apply the one that matches where inference runs. Applying the self-hosted track to a hosted-API pipeline produces checks nobody can pass, which is how checklists get ignored.

The substitution point

4 checks

Route every LLM call through one interface

MUST

Why it matters

Code that calls vendor SDKs directly reintroduces the lock-in the pipeline exists to eliminate. That single interface is also where budgets, retries, usage accounting, and tracing attach — retrofitting it later means touching every call site.

How to implement

Define one internal interface for model access and ban direct provider SDK imports outside it via a lint rule or import guard.

Verify it's done

A repository grep for provider SDK client instantiations outside the abstraction module returns zero matches, across every service and script in the repo — not just the flagship application.

Size the substitution point to your topology

MUST

Why it matters

A networked LLM API gateway buys centralized credentials, cross-team fallback policy, and canarying, at the cost of a service to run, an extra hop, and a new failure mode. Below the scale that needs those, an in-process abstraction delivers identical vendor isolation for none of the cost.

How to implement

Choose in-process for a single process or team; choose a shared library for several same-language services; choose a gateway when multiple services share backends and credentials. Record the choice and the trigger that would change it.

Verify it's done

The architecture record names the current level and the specific condition that would justify moving up. Model-agnosticism is verified by the call-site grep above, not by the presence of a gateway.

Route by task role, not only per request

SHOULD

Why it matters

Per-request override selects a model; task-role lanes express what each model is for. Separating the lane that produces work from the lane that checks it is what stops verification being self-assessment.

How to implement

Define lanes by role — high-volume/low-reasoning, quality-determining, and verification — and make each independently configurable, including across vendors.

Verify it's done

Pointing the verification lane at a different provider than the synthesis lane is a config change requiring no code edit.

Put fallback where failures actually happen

SHOULD

Why it matters

Fallback is worth its complexity where the dependency is flaky. In practice search, retrieval, and scraping backends fail far more often than model APIs, so unconditional model-level failover is often complexity spent in the wrong place.

How to implement

Measure failure rates per external dependency, then implement ordered fallback chains for the ones that actually fail. Where model failover is warranted, put it behind the substitution point so callers see none of it.

Verify it's done

Each fallback chain maps to a measured failure rate. Simulating an outage in staging completes via the fallback path and the trace shows which backend served the request.

Backend interchangeability

4 checks

Reach hosted and self-hosted backends through the same interface

MUST

Why it matters

If self-hosted models require a different call path than hosted APIs, the pipeline develops two lineages and swaps stop being free.

How to implement

Register self-hosted servers through the same surface used for hosted providers. Most multi-provider client libraries and gateways support this via OpenAI-compatible endpoints.

Verify it's done

A single integration test that varies only the model identifier exercises a hosted and a self-hosted backend, and both pass.

Add a new provider without a downstream code change

MUST

Why it matters

If onboarding a provider requires editing pipeline, evaluation, or observability code, the abstraction has already broken.

How to implement

Extend the provider list via the abstraction's declared config surface. No pipeline, evaluation, or tracking code should be touched.

Verify it's done

A dry-run PR adding a provider modifies only configuration. CI passes without changes elsewhere.

Confirm licenses and record pinned versions

MUST

Why it matters

Abstraction and observability components are load-bearing; discovering a license conflict after adoption is expensive.

How to implement

Read each dependency's LICENSE directly rather than trusting a summary. Record the license string next to the pinned version in the dependency manifest.

Verify it's done

Every load-bearing dependency lists an explicit license and pinned version.

Keep model identifiers in one place

SHOULD

Why it matters

Default model ids scattered across services drift. Ours reached two different answers across four files, which is invisible until a release behaves differently depending on which entry point ran.

How to implement

Resolve every model id from one config module or environment contract. No hardcoded model strings in application code.

Verify it's done

A grep for model-id string literals outside the config module returns zero matches.

Evaluation

5 checks

Maintain a fixed-corpus evaluation harness

MUST

Why it matters

This is the control teams most often skip and most often regret. Portability without evaluation makes model swaps easy and tells you nothing about whether a swap was good. Inline self-checking cannot detect regression across versions — only a stable corpus can.

How to implement

Freeze a representative set of inputs and expected properties. Replay the deterministic stages against them and assert on output quality. Run it in CI on every prompt or model change.

Verify it's done

Changing a prompt or model id produces a diffable quality report before merge.

Run evaluation against the abstraction, not a vendor SDK

MUST

Why it matters

Tests bound to a vendor SDK cannot compare across backends and silently block the swaps the pipeline was built to allow.

How to implement

Point the evaluation harness at the same interface production uses, parameterized by model identifier.

Verify it's done

The harness accepts a model id as a parameter and produces a per-model report with no code changes.

Define the metric set and its thresholds up front

SHOULD

Why it matters

Without a defined metric set, "did the swap regress quality?" has no answer.

How to implement

Choose metrics appropriate to the task, store thresholds in versioned config, and fail the build on regression. Any competent LLM evaluation library will do; having one matters far more than which.

Verify it's done

A release evaluation run emits every defined metric and fails the build when a threshold regresses.

Distinguish claim-level checks from corpus-level judgment

SHOULD

Why it matters

A verifier that asks "do the sources support this sentence" cannot ask "are these sources good enough to support this conclusion." A report can score full marks on the first while resting entirely on one weak source. We shipped exactly that report.

How to implement

Add a separate assessment of source diversity and sufficiency — for example, refusing to let any section rest on a single domain — and report it independently of per-claim verdicts.

Verify it's done

Output carries both a per-claim verdict summary and a corpus-quality assessment, and they can disagree.

Run cross-provider comparison as a release gate

SHOULD

Why it matters

A release process that only tests the incumbent backend cannot certify a swap.

How to implement

Add a job that evaluates the primary backend and at least one alternate, and persist both reports.

Verify it's done

Every release artifact carries at least two backend evaluation reports.

Observability

5 checks

Trace whole units of work, not individual calls

MUST

Why it matters

Per-call spans answer "what did this request cost." Only a trace spanning the complete task answers "what did this deliverable cost," which is the question anyone actually asks. Multi-process pipelines lose this by default.

How to implement

Open a root span at the entry point and propagate W3C trace context (traceparent) into every spawned process or job.

Verify it's done

One trace id resolves every model call made in service of a single completed unit of work.

Record model id and token counts; let the backend price them

MUST

Why it matters

A price table checked into the repo is stale within weeks, and stale cost data is worse than none because it is quietly wrong.

How to implement

Attach model id and input/output token counts — including cache reads, which are separately priced and can dominate spend on prompts that resend a large shared context — to each generation span.

Verify it's done

Cost per unit of work is answerable from the tracing backend without any repo-side price constant.

Trace retries as separate observations

SHOULD

Why it matters

A retry that eventually succeeds is invisible if the call is recorded once. Those tokens are spent and the failure rate is real.

How to implement

Emit one generation observation per attempt, marking retries distinctly.

Verify it's done

A forced schema failure in staging produces multiple attempt observations with their own token counts.

Make telemetry fail-safe and verify the disabled path

MUST

Why it matters

Observability that can break the pipeline it observes is worse than none. The tracing-off path is usually the default, so it is the one most likely to ship broken.

How to implement

Degrade to a no-op when credentials, packages, or the collector are absent. Wrap every backend call. Give the no-op the full interface, not a subset.

Verify it's done

A test asserts the pipeline completes with tracing disabled and with an unreachable collector. Both are covered in CI.

Stamp each run with the parameters that shaped it

MUST

Why it matters

Two runs that differ in preset, budget, prompt revision, or which backend served retrieval can read identically in their output. Without stamping, they are not comparable and nobody can tell.

How to implement

Write a run manifest recording configuration, resource budgets, a fingerprint of the prompt set, which external backends served results, and elapsed time against budget. Surface truncation explicitly.

Verify it's done

Any completed run can be reconstructed from its own manifest, and a truncated run says so.

Reproducibility — hosted-API pipelines

4 checks

Freeze retrieved inputs into an append-only record

MUST

Why it matters

Retrieval is the irreducibly non-reproducible stage — the web changes. Freezing it makes everything downstream replayable, which is the reproducibility property that actually matters.

How to implement

Append every retrieved input to an immutable per-run record as it arrives, and synthesize from that record rather than from live state.

Verify it's done

A past run can be replayed from its frozen record with no network access.

Version the prompts

MUST

Why it matters

Output is a function of prompts, and an unversioned prompt change is an unattributable quality change.

How to implement

Record a fingerprint of the prompt set in every run manifest — hashing the prompt source keeps this honest with no bookkeeping.

Verify it's done

Every run records a prompt fingerprint, and editing any prompt changes it.

Pin exact model versions, not model names

MUST

Why it matters

A model name is a moving target; providers update behind stable aliases.

How to implement

Reference the most specific version identifier the provider exposes and record it per run.

Verify it's done

Run manifests carry an exact model version string, not a family alias.

Report what the abstraction cannot give you

SHOULD

Why it matters

Claiming reproducibility you cannot deliver creates false confidence. Replay reproduces the reasoning, not the tokens.

How to implement

State in release documentation that outputs are not bit-identical and that replay covers the deterministic stages only.

Verify it's done

Reproducibility documentation carries the caveat explicitly.

Reproducibility — self-hosted inference

5 checks

Pin decoding and seed configuration on the record

MUST

Why it matters

Seeds and decoding pins are the first layer of determinism — necessary, though not sufficient.

How to implement

Set temperature=0, top_p=1, greedy decoding, a pinned seed, and speculative decoding disabled. Persist all five in the run manifest.

Verify it's done

Every release-blocking run's manifest lists the decoding pins and seed.

Pin serving-framework, CUDA, and library versions

MUST

Why it matters

A framework upgrade can silently change kernel selection, which changes outputs.

How to implement

Lock the serving framework in the dependency manifest and document CUDA and framework versions in the environment file.

Verify it's done

Reproducing an old run rebuilds the same environment from recorded fields.

Pin GPU count, GPU type, and batch size

SHOULD

Why it matters

GPU generations differ at the kernel level, and batch size and device count shift computation order. Reasoning models are especially vulnerable, where early-token floating-point drift cascades into divergent chains of thought.

How to implement

Schedule release-blocking runs on a fixed GPU SKU and count, with single-request batches.

Verify it's done

Manifests record the hardware profile; scheduling on a different SKU is blocked or flagged.

Set determinism flags and treat them as best-effort

SHOULD

Why it matters

Deterministic-algorithm flags raise the floor but do not guarantee bit-identical output. Claiming otherwise creates false confidence.

How to implement

Enable the framework's deterministic-algorithm settings and document that residual nondeterminism is expected.

Verify it's done

Configuration shows the flags set and documentation carries the best-effort caveat.

Choose and document the reporting protocol explicitly

MUST

Why it matters

Mixing sampled and greedy runs in one report is uninterpretable.

How to implement

Per evaluation, pick either sampling with multiple runs reporting mean and error bars, or greedy decoding at full precision with a single run. Record the choice.

Verify it's done

Every evaluation report names its protocol and carries the matching statistics.

Out of scope

1 check

Flag uncovered controls explicitly

NICE

Why it matters

Silently omitting controls implies coverage that does not exist.

How to implement

List what this checklist does not cover — orchestration-framework choice, secrets and credential handling, adversarial-input safety, and abstraction-overhead benchmarking — with a named owner for each. Reliability patterns for tool-driven agents belong in tool-use reliability hardening.

Verify it's done

Release documentation carries a named-owner entry for each gap.

Acceptance criteria

The pipeline is release-ready under this checklist when: every model call in every service resolves through one interface, verified by grep rather than by architecture diagram; the substitution point's scale is a recorded decision with a stated trigger for change; adding a provider is a config-only change; a fixed-corpus evaluation harness runs in CI and gates merges on quality regression; one trace id covers every model call for a completed unit of work, with cost answerable from the tracing backend and no price constants in the repo; telemetry is proven to no-op with tracing disabled and with a dead collector; every run stamps its configuration, budgets, prompt fingerprint, and retrieval backends, and declares truncation; the applicable reproducibility track is fully satisfied and the inapplicable one is documented as such; and the out-of-scope controls carry named owners rather than being assumed.

Derived From

Related Work

External References