Skip to content

Add bedrock-mantle support to chat_aws_bedrock() - #1064

Open
hadley wants to merge 9 commits into
mainfrom
bedrock-mantle
Open

Add bedrock-mantle support to chat_aws_bedrock()#1064
hadley wants to merge 9 commits into
mainfrom
bedrock-mantle

Conversation

@hadley

@hadley hadley commented Jul 22, 2026

Copy link
Copy Markdown
Member

AWS now serves Bedrock models from two endpoints. The original bedrock-runtime endpoint speaks the Converse API, which is what ellmer has always used. The newer bedrock-mantle endpoint speaks the Anthropic Messages and OpenAI Responses APIs instead, and hosts models that Converse can't reach at all — including the GPT-5 family, Grok 4.3, and Claude Mythos. Until now those models were simply unavailable through ellmer.

chat_aws_bedrock() gains an api argument to choose between them. You rarely need to set it: ellmer picks the API from the model name and falls back to Converse for anything it doesn't recognise, so existing code behaves exactly as before.

Example

# Frontier OpenAI models, which only exist on bedrock-mantle
chat <- chat_aws_bedrock(model = "openai.gpt-5.6-sol")
chat$chat("What is 1 + 1? Just the number.")
#> 2

# Claude through the Anthropic Messages API rather than Converse
chat <- chat_aws_bedrock(model = "anthropic.claude-haiku-4-5", api = "messages")

Passing a mantle-only model without an api used to fail with a bare "The provided model identifier is invalid". That error now suggests the fix.

Design decisions

The argument names the request format, not the endpoint. api takes "converse", "messages", or "responses" rather than "runtime" and "mantle", because one mantle endpoint serves three different request formats — naming the endpoint wouldn't say which one to send.

Chat Completions is deliberately not supported. Every Bedrock model that speaks it also speaks either Converse or Responses, so it would add no model coverage.

The model lookup is generated, not hand-written. data-raw/prices.R already fetches litellm's model metadata weekly, and that data records which models live only on mantle. It now also writes R/provider-aws-api.R, so the table refreshes on the existing schedule. It's a plain R file rather than binary data in sysdata.rda so the weekly diff is reviewable.

The table only lists models Converse can't serve. Everything else falls through to Converse. This keeps a data refresh from silently moving a working call onto a different endpoint with different quotas — it can only ever turn a failure into a success. It also means an unfamiliar model id (a custom ARN, a new inference profile) keeps working as it does today rather than being rejected.

Not included

  • Bedrock pricing data is missing for all modern Claude ids, so token_usage() reports NA for them, including on mantle. That's a pre-existing bug in the same extraction script, tracked in Bedrock prices missing for all modern Claude models #1063.
  • Claude Mythos is listed as mantle-only in AWS's docs but doesn't appear in our account's model listing, so those two entries are doc-derived and untested.

Verification

Live-tested against a real AWS account: basic chat, streaming, tool calling, and structured data all work on openai.gpt-5.6-sol via "responses", and on anthropic.claude-haiku-4-5 via "messages".

That testing caught a bug worth flagging for review: mantle has two OpenAI-compatible paths that are disjoint rather than aliases. Newer models are served from /openai/v1 and older open-weight ones like gpt-oss from /v1, and each rejects the other's models. AWS's API compatibility tables don't record this distinction — only the individual model cards mention it — so the first implementation used /v1 and failed on exactly the models this PR is meant to unlock.

Note that test-provider-aws.R:229 currently fails. It's unrelated to this PR — it fails identically on main, and was previously invisible because it skips without AWS credentials.

🤖 Generated with Claude Code

hadley and others added 3 commits July 22, 2026 12:16
Bedrock serves models from two endpoints. bedrock-mantle speaks the
Anthropic Messages and OpenAI Responses APIs, and hosts models that the
Converse API on bedrock-runtime can't reach at all, including Claude
Mythos and the GPT-5 family.

chat_aws_bedrock() gains an `api` argument to pick between them, using
ProviderAnthropic and ProviderOpenAI subclasses that swap the API key
for the existing paws SigV4 flow. The API is guessed from `model` by
default, falling back to converse, so existing code is unaffected.

The model -> api lookup is derived from litellm by data-raw/prices.R and
written to R/provider-aws-api.R, so it refreshes with the weekly prices
job. It only lists models converse can't serve, which keeps a data
refresh from moving a working call onto a different endpoint.

Also extracts the Anthropic and OpenAI error body parsers so the mantle
providers can fall back to the AWS error shape, which is what surfaces
for signature, credential, and throttling failures.
Live testing showed frontier models reject /v1/responses: mantle serves
newer models from /openai/v1 and older open-weight ones like gpt-oss
from /v1, and the two are disjoint rather than aliases. Confirmed
against gpt-5.4, gpt-5.6-sol, grok-4.3, gemma-4, and gpt-oss-20b, and
documented in the model cards.

Everything we route to responses is on /openai/v1, since the /v1 models
are all reachable through converse anyway.

Also adds the error hint the plan called for: a model that only exists
on mantle otherwise fails against converse with a bare "The provided
model identifier is invalid", which gives no clue that `api` is the fix.
@hadley
hadley requested review from gadenbuie and thisisnic July 22, 2026 17:54
@hadley

hadley commented Jul 22, 2026

Copy link
Copy Markdown
Member Author

@gadenbuie mostly an FYI so you can see that I'm using the liteLLM data in another way
@thisisnic probably not much you can do here without AWS access, but please let me know if you spot anything odd.

hadley added 3 commits July 22, 2026 13:07
The model now determines the API rather than the reverse, so there's a
single default model again instead of one per API. That removes the
ordering dependency where the API had to be resolved before the model.

Deletes the base_url closures: aws_bedrock_base_url() takes the region
directly, and models_aws_bedrock() no longer special-cases the control
plane URL because models_list() already rewrites the host.

Two bugs found while doing this:

* aws_error_body() called `if (resp_content_type(resp) != ...)`, which
  errors when a response has no content type at all, masking the real
  error with "Failed to parse error body".

* models_aws_bedrock() derived the listing URL from the chat base_url,
  so it broke for the responses API when that moved to /openai/v1.
  Mantle lists every model at one /v1/models regardless of which API you
  chat with, so build that URL from the region instead.
The three constructor calls differed only in the class and how each
expresses caching, so the other nine arguments were written out three
times. aws_bedrock_class() maps the api to a class name, get() resolves
it, and the cache argument is spliced in.
Comment thread NEWS.md Outdated
hadley and others added 3 commits July 27, 2026 14:10
Co-authored-by: Nic Crane <thisisnic@gmail.com>
Regenerated by the pricing workflow; keep the binary out of this PR.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants