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.

RouteWhat it isWhy you would take it, and what it costs
The provider's own APIA direct account and key with the labThe lab's docs describe every feature for this route, and it is the simplest thing to debug
A cloud route: for Claude, Amazon Bedrock, Google Cloud, Microsoft Foundry, Claude Platform on AWSThe same model served inside your cloud accountOne bill you already have, your identity and permissions, region choice. Costs: Amazon Bedrock and Google Cloud set their own retirement dates (Microsoft Foundry and Claude Platform on AWS follow Anthropic's), a feature can be missing or limited there, and regional endpoints carry a 10% premium on some Claude models
An aggregator, such as OpenRouterOne key and one API shape in front of many providersCompare many models in an afternoon through one interface. Costs: one more party between you and the model, so one more hop and one more place your prompts pass through
Self-hosted open weightsYou run the modelCovered in part 3 of this course
Four routes to the same models, as of Oct 2026. The cloud names are the ones Anthropic's models page lists for Claude; cloud routes for OpenAI and Google models were not checked.

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.

JobAnthropicOpenAIGoogle
Place the system promptTop-level system fieldinstructions, or a developer message in inputsystem_instruction field (Interactions API)
Ask for a JSON schemaoutput_config.formattext.format with json_schema and strictresponse_format with a schema (Interactions API)
Prompt cachingOpt in with cache_controlImplicit by default; explicit mode with prompt_cache_options on newer OpenAI modelsImplicit; explicit caches on some models, with a storage fee
Shape of the limitsRequests, input tokens and output tokens per minute, plus a monthly spend capRequests and tokens per minute, plus daily capsRequests, input tokens per minute and requests per day, per project
Shape of a model idUndated pinned snapshots from the 4.6 generation onUndated ids on its models page; snapshot status not stated thereStable ids, plus a -latest alias that moves
The same five jobs in three dialects, as of Oct 2026. Field names come from each provider's own docs. OpenAI's column is its Responses API. Google's fields are from its Interactions API; the older generateContent shape differs.

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.
Your call siteA system prompt and messages, in your own shape
The adapterBuilds the request, applies the per-model settings, places the system prompt, reads the reply by block type, logs
The provider SDKThe only place in the codebase that imports it
Your result typeText, token counts, served model id. Never the provider's response
The adapter's whole job in one shape: everything that knows which vendor it is talking to lives in the middle box, and what leaves it on the right is a type you defined.

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.

NaiveChange the id at the call site

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.

What to doA per-model table, and read by block type

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.

The same one-line change, handled two ways. The right-hand side moves the model-specific knowledge into one table, so a new id costs a table row, not a hunt through forty files.
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

  1. 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.
  2. Expect to rewrite plumbing first, and to retune prompts too, when you move between providers or between models of one provider.
  3. Test refusal behaviour on your own inputs; it can differ between providers, and no source used here measures by how much.
  4. Keep every provider SDK import inside one adapter module, with per-model request settings in a table beside it.
  5. 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

  1. As of Oct 2026, Anthropic's Messages API takes the system prompt as...

  2. The main practical reason to call a model through a cloud route such as Amazon Bedrock instead of the provider directly is...

  3. Provider lock-in mostly comes from...

  4. Something that genuinely differs between the three providers is...

  5. 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