Skip to content

Vendor Lock-In and the Abstraction Tax

You have chosen a model and a provider. The standard next move is to wrap the provider in an abstraction so you can switch later.

That move is frequently wrong, and understanding why requires being precise about what lock-in actually consists of. It is not one thing. It is five, with escape costs differing by orders of magnitude — and an abstraction only addresses the cheapest one.

graph LR
  A["<b>API shape</b><br/>request format,<br/>SDK calls<br/><i>days</i>"] --> B["<b>Operational knowledge</b><br/>quotas, errors,<br/>failure behaviour<br/><i>weeks</i>"]
  B --> C["<b>Prompt behaviour</b><br/>prompts tuned to<br/>one model's habits<br/><i>weeks to months</i>"]
  C --> D["<b>Eval baselines</b><br/>every number you<br/>promised, re-measured<br/><i>a full cycle</i>"]
  D --> E["<b>Tuned weights</b><br/>non-portable<br/>by construction<br/><i>start again</i>"]

  classDef cheap fill:#7D9A4E,stroke:#5F7A38,stroke-width:2px,color:#ffffff
  classDef mid fill:#E8A33D,stroke:#C4832A,stroke-width:2px,color:#ffffff
  classDef dear fill:#D9695B,stroke:#B4483A,stroke-width:2px,color:#ffffff
  class A cheap
  class B,C mid
  class D,E dear

Five kinds of lock-in by escape cost. An abstraction layer addresses the olive one. The terracotta ones are where switching actually hurts.

API shape is the cheapest and the only one an abstraction fixes. Request formats differ, SDKs differ, and adapting is a few days of unglamorous work.

Operational knowledge is the accumulated understanding of how a provider behaves: its rate-limit semantics, which errors are retryable, how it degrades under load, what its quota increases require. You cannot abstract this, and you re-learn it per provider.

Prompt behaviour is the one that surprises teams. Prompts are tuned — often unconsciously, over months — to one model’s habits. Move them and quality drops in ways that are tedious to diagnose, because nothing errors. Instructions that were load-bearing on one model are redundant on another; a model that needed an explicit formatting rule may produce worse output when given one. This is real work, and no wrapper touches it.

Eval baselines must be re-measured entirely. Every number in your NFR budget — correctness, confident-error rate, latency, cost per interaction — was measured against one model and tells you nothing about another. Switching means re-running the whole evaluation and renegotiating anything that moved.

Tuned weights are non-portable by construction. If you fine-tuned, switching providers means starting that work again, which is the subject of the next topic but one.

Here is the trade an abstraction makes, stated plainly: it buys portability by forfeiting capability.

A universal wrapper can only expose what all providers share. The features that most improve your system are exactly the ones that differ — caching semantics and where breakpoints may be placed, reasoning-effort controls, server-side tooling, structured-output enforcement, geographic pinning of inference, batch discounts. A layer that normalises these away leaves you running every provider at its lowest common denominator, permanently, in exchange for a switch you may never make.

The tax compounds. Each time the provider ships something useful, you either extend the abstraction — which is now your maintenance burden — or you do not adopt it. Teams in this position routinely sit two or three capability generations behind, having paid for an option they never exercised.

A thin seam at the boundary you might actually cross. One place where model invocation happens, so the call sites are not scattered. That is a day of design and it is worth doing — Module 3’s reference architecture already required it for a different reason.

Provider-specific features used directly, behind a flag rather than an abstraction. If caching semantics differ, use this provider’s caching properly and record in your ADR that it is provider-specific. A documented dependency you chose is far better than a capability you forfeited silently.

Switching cost measured, not eliminated. Put a number on it: how long would a provider change take, what would have to be re-measured, what would it cost? That number is what makes this a bet with a reversal condition rather than an anxiety. Module 3’s bet format asks for exactly this.

The uncertainty is worth stating: how much lock-in matters depends on how fast the frontier moves and whether capability converges. If providers converge, abstraction was cheap insurance. If they diverge, abstraction was a permanent capability tax. Reasonable people differ, and the evidence is not in — so treat this as a strong recommendation rather than a settled conclusion, and revisit it when your reversal condition fires.

Your AI stack selection should name what sits behind the seam and what is used directly, with the provider-specific dependencies recorded rather than hidden. The measured switching cost belongs next to the model choice as its reversal condition.


→ Model chosen, seam placed. But does this problem even need retrieval — or training — or just a better prompt? — Prompting, RAG or fine-tuning