The $40,000 Bill You Don't Know Is Coming: LLM Vendor Lock-In
The Three Real Cases
Case 1 — The customer support startup
A 12-person team had the model name hardcoded across 40+ files, inconsistently. The rewrite took three and a half weeks plus a week of regression testing.
Case 2 — The internal tools team
Their "abstraction layer" was a thin pass-through that still assumed OpenAI's exact response shape. Migration took four weeks and touched every caller relying on provider-specific quirks.
Case 3 — The analytics SaaS
Used a framework abstraction, but pinned a model name deep in config and had fallback logic that silently retried the same failing model instead of failing over. The bug ran for weeks before a rushed migration followed.
Interactive: Lock-In Risk Scorer
Check every statement that's true for your codebase.
Why This Happens
Lock-in is rarely deliberate — it's the accumulated result of moving fast. The first integration calls the SDK directly because it's the fastest way to ship a demo. The second feature copies the first. By the time there are a dozen call sites, adding an abstraction layer feels like a refactor with no immediate payoff — until a deprecation notice, price hike, or outage makes the deferred cost due all at once.
The DeepSeek Disruption Example
In early 2025, DeepSeek's aggressive pricing forced a broad industry reevaluation of cost-to-quality ratios. Teams that could swap a config value captured savings within days. Teams with tightly coupled, single-provider code couldn't even evaluate the option without first doing the abstraction work they'd postponed — and by the time they were ready, pricing had often shifted again.
Interactive: Migration Cost Estimator
Three-Layer Defense
- Abstraction layer. One internal interface that every feature calls. Provider-specific code lives only inside adapters behind it.
- Metadata tracking. Every call logs provider, model, tokens, latency, cost — without this you're migrating blind.
- Fallback configuration. A secondary provider/model defined in config, not code, with failover logic that actually switches providers on failure.
// Provider-agnostic call site — the only thing feature code should know about
const result = await llm.generate({
feature: 'support-reply',
prompt: buildPrompt(ticket),
fallback: true // adapter handles provider failover internally
})
Track which provider and model handled every request today.
FAQ
Not automatically. Frameworks reduce boilerplate but can still leak provider-specific assumptions, as in Case 3. Verify the abstraction actually isolates provider details.
Enough to make model and provider a config value, not a code change. Usually a few days of work — far cheaper than a $40,000 emergency migration.
Centralize every LLM call through one internal function and log provider/model/cost metadata on every call. That alone makes a future migration measurable.
Don't find out your lock-in cost during an emergency
Start free. One async tracking call. No proxy and no credit card required.
Start free →