Building AI Products Without Locking Into One Model :

A model provider should supply model capability. The data, the state, the approvals, the business rules, the security posture and the audit trail should belong to the application. Get that division right and switching providers is an engineering decision; get it wrong and it is a rewrite.

By Tim Bryant, LYFYEPublished
Key takeaways
  • Answer first: keep business state in your application and treat the model as a capability you call. Almost everything else follows from that one boundary.
  • There is no such thing as zero lock-in, and any vendor claiming it is describing a wrapper they have not stress-tested.
  • The cost of switching is not rewriting API calls — it is re-establishing that behavior is still acceptable.
  • Some vendor-specific capabilities are worth the coupling. The rule is to choose it deliberately and write down what it costs.
Delivery standard

Every briefing becomes a deliverable: diagrams, control mappings, evidence packs, and a prioritized execution backlog. If it can't be implemented and audited, it doesn't ship.

The lock-in that actually hurts

Teams worry about the wrong coupling. Swapping one chat-completions call for another is a small, well-understood change; the SDKs differ, the request shapes differ slightly, and a competent engineer handles it in an afternoon. The expensive lock-in is structural: business state living in provider-managed threads, approvals recorded only as messages, workflow implemented inside a vendor's orchestration product, and behavior no one can characterize precisely enough to reproduce elsewhere. That is not an afternoon. That is a migration project competing with your roadmap.

Provider abstraction, done at the right altitude

The useful abstraction is not a lowest-common-denominator wrapper that hides every provider difference — those become their own maintenance burden and quietly prevent you from using anything good. The useful abstraction is a narrow internal interface expressed in your product's terms, with provider-specific adapters behind it. Your application asks for a task to be performed; the adapter decides how to express that to a given provider, including which capabilities it can take advantage of.

  • Define the interface around what your product needs, not around the union of every provider's API.
  • Keep provider-specific handling in the adapter, where it is visible and testable.
  • Model selection should be configuration and routing, not conditionals scattered through feature code.
  • One choke point for provider calls also gives you one place for budget enforcement, retries, and telemetry.

Application-owned state is the load-bearing decision

If you only take one thing from this: keep the record in your database. The artifact, its version, the decisions that were accepted, who owns it, what was approved and by whom. When that lives in your application, a provider change touches the layer that calls the model and nothing else. When it lives in a provider's thread or assistant abstraction, everything you know about your users is on the wrong side of a boundary you do not control — and you will discover this at the least convenient moment, usually during a pricing change or a deprecation notice.

  • Persist the inputs and the outputs you care about, not just the final rendering.
  • Version the state so late or concurrent completions can be rejected rather than silently applied.
  • If you cannot reconstruct a user's work without calling the provider, you do not own it.

Switching is an evaluation problem, not an integration problem

Once the architecture is right, the remaining cost of changing providers is establishing that the new one is acceptable — and that is the part people underestimate. Prompts tuned against one model's behavior do not transfer cleanly. Tool-calling reliability differs. Refusal behavior differs. Output formatting differs in ways that break downstream parsing. None of this is discoverable from a benchmark; it is discoverable from running your own tasks with your own data and comparing against acceptance criteria you wrote before you started.

  • Keep a task suite that represents real usage, including the awkward cases.
  • Evaluate on your acceptance criteria, not on the provider's chosen examples.
  • Run both providers in parallel on real traffic before committing, where the task allows it.
  • Expect prompt work. A model change is a behavior change even when the interface is identical.

When vendor-specific capabilities are worth it

Model-agnostic is a default, not a religion. Providers ship genuinely differentiated capabilities, and refusing all of them on principle means shipping a worse product to preserve an option you may never exercise. The discipline is to make the coupling a decision rather than an accident: name what you are depending on, know what it would take to replace it, and confine it to a place where its absence degrades one feature instead of breaking the product.

  • Isolate the dependency behind your own interface so the blast radius is one adapter.
  • Write down the fallback, even if the fallback is 'this feature is unavailable'.
  • Revisit the decision when the capability becomes commoditized, which it usually does.
  • Be explicit with clients about where their system depends on one provider. They are entitled to know.

The honest tradeoffs

Portability is not free, and pretending otherwise is how architecture astronautics starts. An abstraction layer is code to maintain. Multi-provider evaluation costs engineering time on something users never see directly. Avoiding a provider's managed conveniences means building and operating the equivalent yourself. These are real costs, and for a short-lived internal tool they may exceed the benefit. For a product a business depends on — where provider pricing, availability, terms, and deprecation schedules are all outside your control — the calculation is different, and it has repeatedly favored owning the boundary.

  • Small internal tool, short lifespan: couple freely, move on.
  • Product with customers and a roadmap: own the state and the boundary from the start.
  • Regulated or contractual data constraints: portability may not be optional at all.

Where each responsibility should live

ConcernBelongs toWhy
Model inferenceProviderIt is the thing you are buying, and the thing that improves fastest
Prompt and instruction contentApplicationIt encodes your product's behavior; it should be versioned with your code
Business state and recordsApplicationIt must outlive any provider relationship and be queryable on your terms
Identity and authorizationApplicationA trust boundary must not sit inside a component that can be argued with
Approvals and workflowApplicationWho approved what, and when, is a business record subject to audit
Conversation historyApplicationProvider-held threads are convenient and become the thing you cannot export
Tool definitionsApplicationCapability boundaries are security decisions you need to own
Evaluation resultsApplicationYou cannot compare providers using one provider's scoring
Embeddings and vector indexApplication (usually)Re-embedding a large corpus is a real migration cost; keep the source text
Fine-tuned weightsProvider (accept coupling)Genuinely portable only in the sense that you can retrain — budget for that
Want the "enterprise version" of this?

We tailor the briefing to your environment: boundary definitions, control mapping, evidence workflows, and an implementation plan. Designed for executive sign-off and audit scrutiny.