LLMsThe providers
The big three, and what actually differs
Anthropic, OpenAI, Google, and the layers you can buy them through. Where the real differences live.
WHY THIS, FOR YOU
You picked a provider because it was the one you had heard of, wired its SDK into forty files, and then a compliance question or a price change meant you should test another one. You could not, cheaply. Worse, you tried the quick fix: change the model id and carry on. The call came back as a 400, because a setting that was fine on the old model is refused on the new one. Meanwhile the arguments you read about which lab is best are about scores that will be stale next quarter. This lesson separates what really differs between providers from what only looks like it, and shows the small piece of code that keeps the door open.
This lesson compares three sellers of closed frontier models: Anthropic, OpenAI, and Google. They are the three it covers because I used their docs, not because anyone measured market share. Others exist; I did not check them. It will not say which of the three is best, because that ordering changes with every release. It says what differs when you write the code, and what only seems to. Every provider fact below is dated, because these pages move.
1.Who sells it, and how you can buy it
As of Oct 2026, each of the three offers a consumer product and a direct developer API. Some are also sold through a cloud, and one more layer sits in front of everyone.
Feature coverage is the part that surprises people. Anthropic's structured-outputs page, for one, says that on Bedrock the feature is limited to a legacy integration and older models (as of Oct 2026). Each Anthropic feature page carries its own platform table, so read the one for the feature you need before you commit to a route.
In a company, the deciding factor is often which vendor is already through legal.
2.What genuinely differs when you write the code
Here is the honest split. Each provider fact carries its own date.
Differences that cost real work:
- Request and response shape. All three take messages and return content, but content blocks, file and image attachment, usage field names and streaming events all differ. None of it is hard. It is where the days go.
- Tool calling. Anthropic describes tools with JSON Schema (as of Oct 2026), and forcing a specific tool returns an error on several current Anthropic models, as of Oct 2026 (its migration guide and compatibility page name them). Neither point was checked here for OpenAI or Google.
- Context windows. As of Oct 2026 Anthropic lists 1M tokens on all four current models, Google's flagship lists 1M, and OpenAI's models page lists about 1M for its flagships. The real difference is price past a length. As of Oct 2026, one Anthropic model charges it, and so do some of Google's Pro models (not its flagship). Read the pricing page for your model.
- Tokenizers. Each lab has its own tokenizer, so the same text is a different count at each; I did not measure the gap for OpenAI or Google. As of Oct 2026 Anthropic says its newer tokenizer makes about 30% more tokens than Claude Sonnet 4.6 and earlier models for the same text, and offers a free counting endpoint. Count with the provider's own tool, never a rule of thumb. Cost is in What you actually pay for; limits are in Rate limits and reliability.
- Safety behaviour. Refusal lines and tone can differ. If your domain sits near a line, such as security, medical, legal or moderation work, test your real inputs. No source used here measures the gap, so this lesson does not rank anyone.
- Data handling. Retention, training use, regions and certifications are contractual, and in regulated work they decide the question.
What mostly does not differ: the core loop (messages in, tokens out, stateless, billed per token, capped output), and the engineering problems on top of it. Hallucination, injection, evaluation, cost and latency get the same fixes under any logo. A design that only works while one model is ahead needs rework when the ranking moves.
3.Lock-in, and the thin layer that removes it
The lock-in is never the weights. It is the shape of your own code: the provider's client imported in forty files, its response objects passed around as your type, tool schemas in one dialect, streaming handled the provider's way inside your UI. Nobody decides this. It accumulates, and switching is a project, not an afternoon.
The fix is small. One module, one function, your own type coming out.
- The adapter owns: building the request, placing the system prompt, translating tool schemas, normalising streaming, retries, and logging usage.
- What comes out: your own result object, never the provider's.
- What it must not become: a universal wrapper over every feature. Support what you use. When you need something only one provider has, expose it by name.
The lock-in is never the model. It is the forty files that import one provider's SDK.
A failure you can predict. Say your adapter was written for an older model and reads resp.content[0].text. You change the id to claude-sonnet-5-5 and keep the rest. Two things break. Anthropic's migration guide says five old settings now fail with a 400 (as of Oct 2026): assistant prefill, forcing one tool, thinking: {"type": "disabled"}, a thinking token budget, and sampling parameters. Also, as of Oct 2026, that model thinks by default, so a reply can begin with a thinking block, and that block has no text. Both are plumbing, not prompts.
Settings sit beside each call, so each one is checked by hand. The reply is read as content[0].text. A setting that the new id rejects shows up as a 400 in production.
Settings come from one table keyed by id, and an id missing from it fails loudly. The reply is the joined text of the text blocks, so a leading thinking block does no harm.
from dataclasses import dataclass
from anthropic import Anthropic
MODEL = "claude-sonnet-5-5" # check the models page; as of Oct 2026
# Settings that suit one id and return a 400 on the next live here, not at the call sites.
# An id that is missing from this table raises KeyError, which is the point.
SETTINGS = {
"claude-sonnet-5-5": {"output_config": {"effort": "medium"}},
}
@dataclass
class Answer:
text: str
input_tokens: int
output_tokens: int
served_model: str # log this: it is the id the provider says answered
_client = Anthropic()
def complete(system: str, messages: list[dict], model: str = MODEL) -> Answer:
"""The only function in the app that knows which provider is being called."""
resp = _client.messages.create(
model=model,
max_tokens=2000, # on this model it covers thinking plus the answer
system=system, # a top-level field here, `instructions` or a message elsewhere
messages=messages,
**SETTINGS[model],
)
# A reply can start with a thinking block, so never read content[0].text.
text = "".join(b.text for b in resp.content if b.type == "text")
return Answer(text, resp.usage.input_tokens, resp.usage.output_tokens, resp.model)
Call complete("Answer in one sentence.", [{"role": "user", "content": "What is a token?"}]) and you get one Answer. A second provider means one more branch in this file, with its own settings table, and no change elsewhere. This sample was checked against a mock transport with anthropic 1.15.0, not the live API, so run it once with your own key and check that the effort setting is accepted for the model.
When a thin adapter is the wrong tool. A compatibility layer looks like a free adapter: point the OpenAI SDK at another provider's URL. Anthropic's own page says its layer is "primarily intended to test and compare model capabilities" (as of Oct 2026). It puts reliability of its native API first, and does not offer the layer as a lasting choice for most production work. The page also lists what the layer ignores: strict in function calling, response_format and reasoning_effort. Prompt caching is not available through it. Most ignored fields raise no error, so you can lose a schema guarantee without noticing. Use such layers to compare models in an afternoon. If your app leans on one provider's deep features, expose them by name. And if you have one model, no compliance question and a prototype to ship, the adapter can wait a week. The one-file rule for SDK imports should not.
WHAT YOU TAKE AWAY
- Check each feature's own docs page for the platforms that list it before you choose a cloud route over the provider's own API.
- Expect to rewrite plumbing first, and to retune prompts too, when you move between providers or between models of one provider.
- Test refusal behaviour on your own inputs; it can differ between providers, and no source used here measures by how much.
- Keep every provider SDK import inside one adapter module, with per-model request settings in a table beside it.
- Do not design around today's ranking; it changes with each release. Read ids, limits and features off the provider's pages and date what you copy.
RECALL NO SCROLLING BACK
As of Oct 2026, Anthropic's Messages API takes the system prompt as...
The main practical reason to call a model through a cloud route such as Amazon Bedrock instead of the provider directly is...
Provider lock-in mostly comes from...
Something that genuinely differs between the three providers is...
You change only the model id in a working call, to a newer model from the same provider, and it now returns a 400. The likeliest cause is...
GO DEEPER
- Anthropic: Messages API reference (the top-level system field) (opens in a new tab)
- OpenAI: text generation guide (instructions and developer messages) (opens in a new tab)
- Google: Gemini API text generation (system_instruction) (opens in a new tab)
- Anthropic: OpenAI SDK compatibility (what the layer ignores) (opens in a new tab)