Repository navigation
fix(bedrock): list the account's invocable models behind bedrock/* when check_provider_endpoint is on - #43981
Conversation
…en check_provider_endpoint is on
|
I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".
|
|
Merging this PR will not alter performance
|
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
…Error on a failed listing
…models Main added the same discovery hook as BaseLLMModelInfo.discover_models (#44448), so the PR's get_models_for_deployment hook and the HTTPHandler header widening are dropped for main's versions and the Bedrock override now reads the deployment's region and credentials from the mapping the proxy passes. The inference profile pager is a bounded for loop (20 pages of 1000) instead of recursion, which the recursion detector flagged.
…k discovery test is new
… config keeps its event parser
|
bugbot run |
…ial wildcards filter get_valid_models returns bare vendor ids for every other provider and for the static bedrock catalog, and the proxy's wildcard expansion adds the provider prefix itself. The lister prefixed its ids, so a partial wildcard like bedrock/anthropic.* never matched the proxy's filter and listed uncallable bedrock/anthropic.bedrock/<id> entries
…ats the prefix The wildcard expansion guessed filter-or-alias by whether any provider id carried the prefix. A deployment such as model_name bedrock/anthropic.* over model bedrock/anthropic.* can only ever route names that share the prefix, so when the account has no on-demand anthropic.* id (every Anthropic model behind an inference profile) the guess fell into the alias branch and listed bedrock/anthropic.<every id>, none of them callable. A deployment whose model repeats the suffix now always filters, and lists nothing when nothing matches
|
bugbot run |
… that starts a vendor id
|
bugbot run |
|
bugbot run |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 3dbe39e. Configure here.
TLDR
Problem this solves:
bedrock/*lists every Bedrock id LiteLLM knows, not the account'scheck_provider_endpoint: true, the documented remedy, listed zero Bedrock modelsHow it solves it:
check_provider_endpoint: true,bedrock/*asks Bedrock what the account can invokebedrock/anthropic.*lists only the discovered ids with that prefixanthropic/*overbedrock/*lists every discovered id asanthropic/<id>, also when the prefix starts a vendor idUser Flow
Before: a developer picks a Bedrock model from the gateway's model list and the call fails, because the list is the static catalog rather than what the account can invoke, and the documented discovery flag lists nothing at all
model_name: bedrock/*withaws_region_name: us-east-1and AWS keys, turns onlitellm_settings: check_provider_endpoint: true, and restartsGET https://litellm-domain/v1/modelsand gets{"data": [], "object": "list"}; with the flag off the same call returns the 347 ids of the static catalog, among thembedrock/eu.anthropic.claude-opus-4-5-20251101-v1:0andbedrock/jp.anthropic.claude-sonnet-4-5-20250929-v1:0bedrock/eu.anthropic.claude-opus-4-5-20251101-v1:0from that list (the playground dropdown at https://litellm-domain/ui/playground offers the same ids with the flag off, and "No models available for this key" with it on) and sendPOST https://litellm-domain/v1/chat/completionswith itBedrockException - {"message":"The provided model identifier is invalid."}After: the same developer sees only the models the account can invoke in that region, and every listed id answers
model_name: bedrock/*withaws_region_name: us-east-1and AWS keys, turns onlitellm_settings: check_provider_endpoint: true, and restartsGET https://litellm-domain/v1/modelsand gets the 158 ids the account can call in us-east-1, such asbedrock/global.anthropic.claude-opus-4-5-20251101-v1:0andbedrock/amazon.nova-micro-v1:0; theeu.,jp., andus-gov.ids are gone, andGET https://litellm-domain/model/infoshows the same setbedrock/global.anthropic.claude-opus-4-5-20251101-v1:0(the playground dropdown at https://litellm-domain/ui/playground now offers it) and sendPOST https://litellm-domain/v1/chat/completionswith itLinear ticket
Resolves LIT-1694
Design decisions
BaseLLMModelInfo.discover_models(litellm_params), the hook main added in feat(anthropic): workload identity federation and pluggable identity sources #44448, which defaults to the oldget_models(api_key, api_base)for every other provider. Bedrock overrides it to read the deployment's region and AWS credentials, so no other provider changes. The PR first carried its ownget_models_for_deploymenthook for this and dropped it for main's once main shipped the same thingtypeEquals=SYSTEM_DEFINED,byInferenceType=ON_DEMAND). Application inference profiles and provisioned throughput are not listed. Listing every foundation model was rejected because it brings back the ids that only work through a profileapi_keyorAWS_BEARER_TOKEN_BEDROCK) wins over SigV4, the same precedence as the invoke path. Query strings are percent-encoded RFC 3986 style (a space is%20, never+), since that is the form SigV4 canonicalizes tocheck_provider_endpointflag, so the default list is still the static catalog. Listing live by default was rejected because it adds two AWS calls per deployment at boot and needs two more IAM permissionsbedrock/*lists no model for that call, and the next listing call tries again, since the shared discovery cache stores successes only (a transient failure never hides the models for five minutes, a persistent one costs one round trip per call, measured in the blast radius section), like every other discovered provider. Falling back to the static catalog on failure was rejected because it silently brings the bug backnextTokenfor at most 20 pages of 1000 and raises past that, the same bounded loop the Anthropic lister uses on main, so a control plane that keeps returning a token can never loop foreveramazon.nova-micro-v1:0), deduped and sorted, the shapeget_valid_modelsreturns for every other provider and the shape the static Bedrock catalog has. The proxy's wildcard expansion adds the deployment's own prefix (bedrock/, or a custommodel_nameprefix), so a custom-prefix deployment lists under its own name. Returningbedrock/-prefixed ids was tried first and dropped because the proxy's partial-wildcard filter (anthropic.*over the discovered ids) never matched a prefixed idBaseAWSLLMto reuse its region and credential resolution instead of calling those helpers from outsidemodel_name: bedrock/anthropic.*overmodel: bedrock/anthropic.*) is a filter over the discovered ids, never an alias: it lists the ids that start withanthropic., and nothing when the account has none (an account whose Anthropic models are reachable only throughus.orglobal.profiles, which is every account in us-east-1 today). Before 5ce9254 the proxy took the alias branch ofget_known_models_from_wildcardfor that deployment, meant forgenai/test/*overopenai/*(fix(model_checks.py): handle custom values in wildcard model name (e.g. genai/test/*) #13116), and listed every discovered id asbedrock/anthropic.<id>, 156 names no deployment can route. The alias branch still applies to a deployment whosemodeldoes not repeat the suffixget_known_models_from_wildcarddecided whether an id already carries the deployment's prefix bystartswith(prefix)without the/, soanthropic/*overbedrock/*listedanthropic.claude-haiku-4-5-20251001-v1:0bare (the id starts with the stringanthropic) and every other id asanthropic/<id>. It now matches<prefix>/. The bare names were never routable on that key (a chat on one answers 403 on both legs). Pre-existing on main with the static catalog (33 of its 347 ids start withanthropic), reached by every discovered id with the flag on. Fixed by 8fa44a2Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
uv run pytest tests/unit/<your_test_file>.py -v. Leave the suites (make test-unit-*,make test-unit) to CI: it finishes in ~15 minutes where a laptop takes an hour or more@greptileaito re-request a review after pushing changes)Screenshots / Proof of Fix
Last updated: 3dbe39e. QA and /live-pr-risk ran on 8fa44a2, the last commit with a product diff
Both legs ran on the same box against the real Bedrock control plane and real model invocations in us-east-1 with the CI AWS account's keys on the deployment, plus a real OpenAI deployment as the control. Each leg is one proxy per boot (
--num_workers 2, so two uvicorn workers) with its own worktree, venv and Postgres (the Admin UI login needs one), no Redis: the discovery cache is a per-process 5 minute TTL with no shared state, so one two-worker proxy covers the whole runtime path. Everyamazonaws.comrequest from either proxy went through a forwarding recorder in front of the real control plane and runtime (it delaysbedrock.us-east-2.amazonaws.comby 12 s for thebedrock-slow/*deployment and forwards everything else untouched). Each proxy booted withcheck_provider_endpointon, then off for the static-catalog case, then on again. The same 44 requests ran in the same order on both legs; the base answered on port 56739, the head on 57097Reproduce in the Admin UI: log in at http://localhost:4000/ui, open http://localhost:4000/ui/playground, click the "Select Model" box and type
claude-opus-4-5-20251101. Before (flag on) the box says "No models available for this key"; after it offersbedrock/global.anthropic.claude-opus-4-5-20251101-v1:0andbedrock/us.anthropic.claude-opus-4-5-20251101-v1:0, the same two underanthropic/from the custom-prefix deployment, and noeu.id. The screenshots are under User Flow aboveBefore (8fc31b1)
Flag on: listings
curl -s http://127.0.0.1:56739/v1/models -H "Authorization: Bearer $LITELLM_MASTER_KEY" | jq '.data | length'->1(gpt-control;eu.=0 jp.=0 us-gov.=0 global.=0 us.=0, no Bedrock id at all), 0.11 s cold, 6 ms warm;GET /modelsthe samecurl -s 'http://127.0.0.1:56739/v1/models?return_wildcard_routes=true' ... | jq '.data | length'->7(gpt-controlplus the six wildcard routes)curl -s http://127.0.0.1:56739/model/info ... | jq '.data | length'->7(the six wildcard rows andgpt-control);GET /v1/model/info->7;GET /model_group/info->1group (gpt-control)POST /key/generatewith{"models": ["bedrock/*"]}, thenGET /v1/modelswith that key ->0ids; a key on{"models": ["bedrock-group"]}->0; a key on a team with{"models": ["bedrock/*"]}->0; a key on{"models": ["bedrock/anthropic.*"]}->0claude-opus-4-5-20251101: "No models available for this key" (first screenshot under User Flow)Flag on: custom prefix over the wildcard
POST /key/generatewith{"models": ["anthropic/*"]}, thencurl -s http://127.0.0.1:56739/v1/models -H "Authorization: Bearer $KEY_CUSTOM" | jq '.data | length'->0curl -s -w '%{http_code}' http://127.0.0.1:56739/v1/chat/completions -H "Authorization: Bearer $KEY_CUSTOM" -H 'Content-Type: application/json' -d '{"model": "anthropic/us.anthropic.claude-haiku-4-5-20251001-v1:0", "messages": [{"role": "user", "content": "Say hi in three words"}], "max_tokens": 20}'-> HTTP 200"Hey there, friend!"(not listed, routes anyway); the same with the bareanthropic.claude-haiku-4-5-20251001-v1:0-> 403not available for this API keyFlag on: /v1/chat/completions
curl -s -w '%{http_code}' http://127.0.0.1:56739/v1/chat/completions -H "Authorization: Bearer $KEY_BEDROCK_WILDCARD" -H 'Content-Type: application/json' -d '{"model": "bedrock/amazon.nova-micro-v1:0", "messages": [{"role": "user", "content": "Say hi in three words"}], "max_tokens": 20}'-> HTTP 200"Hello there!",model=bedrock/amazon.nova-micro-v1:0(not listed, routes anyway); the team key -> 200"Hello there!", the access-group key -> 200"Hello, world!"bedrock/eu.anthropic.claude-opus-4-5-20251101-v1:0-> HTTP 400BedrockException - {"message":"The provided model identifier is invalid."}bedrock/anthropic.claude-opus-4-5-20251101-v1:0-> HTTP 400Invocation of model ID anthropic.claude-opus-4-5-20251101-v1:0 with on-demand throughput isn't supportedbedrock/global.anthropic.claude-opus-4-5-20251101-v1:0-> HTTP 200"Hi there, friend!""stream": trueonbedrock/amazon.nova-micro-v1:0-> 200, 6 chunks,[DONE],"Hello, friend!";"model": "gpt-control"on thebedrock/*key -> 403not available for this API key;"fallbacks": ["bedrock/amazon.nova-micro-v1:0"]-> 200"Hello, world!"; afterPOST /key/deletethe same key -> 200"Hello, friend!"this pass (401 in earlier passes; the two workers keep separate key caches and the delete had not reached the one that served it, see Blast radius)Flag on: /v1/messages
curl -s -w '%{http_code}' http://127.0.0.1:56739/v1/messages -H "Authorization: Bearer $KEY_BEDROCK_WILDCARD" -H 'Content-Type: application/json' -d '{"model": "bedrock/amazon.nova-micro-v1:0", "max_tokens": 20, "messages": [{"role": "user", "content": "Say hi in three words"}]}'-> HTTP 200type=message,"Hello, friend!",input_tokens=5 output_tokens=5"stream": true-> 200, 8 events ending inmessage_stopFlag on: /v1/responses
curl -s -w '%{http_code}' http://127.0.0.1:56739/v1/responses -H "Authorization: Bearer $KEY_BEDROCK_WILDCARD" -H 'Content-Type: application/json' -d '{"model": "bedrock/amazon.nova-micro-v1:0", "input": "Say hi in three words", "max_output_tokens": 20}'-> HTTP 200status=completed,"Hello, friend!",id=resp_JtPRyQt"stream": true-> 200, 13 events,response.completed,"Hello, friend!"Flag off: the static catalog
curl -s http://127.0.0.1:56739/v1/models ... | jq '.data | length'->1769(the fourbedrockwildcard deployments each expand to the 347 ids of the static catalog,eu.=34 jp.=10 us-gov.=15 global.=32 us.=64 anthropic.=66underbedrock/; the partial one to 33;anthropic/*to 347),GET /model/info->1769rows{"models": ["bedrock/anthropic.*"]},GET /v1/models->33staticbedrock/anthropic.ids{"models": ["anthropic/*"]},GET /v1/models->347: 314 underanthropic/plus 33 bare ids such asanthropic.claude-3-5-haiku-20241022-v1:0andanthropic.claude-3-5-sonnet-20240620-v1:0POST /v1/chat/completionswithbedrock/eu.anthropic.claude-opus-4-5-20251101-v1:0(listed) -> HTTP 400The provided model identifier is invalid.;bedrock/global.anthropic.claude-opus-4-5-20251101-v1:0-> 200"Hi there, friend!";bedrock/amazon.nova-micro-v1:0-> 200"Hello, friend!";anthropic/us.anthropic.claude-haiku-4-5-20251001-v1:0on theanthropic/*key -> 200"Hey there, friend!"After (8fa44a2)
Flag on: listings
curl -s http://127.0.0.1:57097/v1/models -H "Authorization: Bearer $LITELLM_MASTER_KEY" | jq '.data | length'->317: the 158 ids the account can invoke in us-east-1 underbedrock/(global.=32 us.=67, 59 bare ids,eu.=0 jp.=0 us-gov.=0), the same 158 underanthropic/from the custom-prefix deployment, plusgpt-control; first idsanthropic/ai21.jamba-1-5-large-v1:0,anthropic/ai21.jamba-1-5-mini-v1:0,anthropic/amazon.nova-2-5-sonic,anthropic/amazon.nova-2-multimodal-embeddings-v1:0,anthropic/amazon.nova-2-sonic-v1:0; 11.6 s cold and 10.3 s warm, both set by thebedrock-slow/*deployment's 10 s listing timeout (see Caveats);GET /modelsthe samecurl -s 'http://127.0.0.1:57097/v1/models?return_wildcard_routes=true' ... | jq '.data | length'->323(the 317 plus the six wildcard routes)curl -s http://127.0.0.1:57097/model/info ... | jq '.data | length'->321(316 expanded rows,gpt-control, and the four wildcard rows that expanded to nothing:bedrock/anthropic.*,bedrock-badkey/*,bedrock-nocreds/*,bedrock-slow/*), 10.5 s;GET /v1/model/info->321(10.4 s);GET /model_group/info->317groupsPOST /key/generatewith{"models": ["bedrock/*"]}, thenGET /v1/modelswith that key ->158ids; a key on{"models": ["bedrock-group"]}->158; a key on a team with{"models": ["bedrock/*"]}->158; a key on{"models": ["bedrock/anthropic.*"]}->0(no invocable id starts withanthropic.; the account's Anthropic models are theus.andglobal.profiles), andPOST /v1/chat/completionson that key withbedrock/global.anthropic.claude-opus-4-5-20251101-v1:0orbedrock/amazon.nova-micro-v1:0-> 403 on both legsclaude-opus-4-5-20251101:bedrock/global.anthropic.claude-opus-4-5-20251101-v1:0,bedrock/us.anthropic.claude-opus-4-5-20251101-v1:0and the same two underanthropic/, noeu.id (second screenshot under User Flow)Flag on: custom prefix over the wildcard
POST /key/generatewith{"models": ["anthropic/*"]}, thencurl -s http://127.0.0.1:57097/v1/models -H "Authorization: Bearer $KEY_CUSTOM" | jq '.data | length'->158, every id underanthropic/and none bare; firstanthropic/ai21.jamba-1-5-large-v1:0,anthropic/ai21.jamba-1-5-mini-v1:0,anthropic/amazon.nova-2-5-soniccurl -s -w '%{http_code}' http://127.0.0.1:57097/v1/chat/completions -H "Authorization: Bearer $KEY_CUSTOM" -H 'Content-Type: application/json' -d '{"model": "anthropic/us.anthropic.claude-haiku-4-5-20251001-v1:0", "messages": [{"role": "user", "content": "Say hi in three words"}], "max_tokens": 20}'-> HTTP 200"Hi, how are you?",prompt_tokens=12 completion_tokens=9(listed); the same with the bareanthropic.claude-haiku-4-5-20251001-v1:0-> 403not available for this API keyFlag on: /v1/chat/completions
curl -s -w '%{http_code}' http://127.0.0.1:57097/v1/chat/completions -H "Authorization: Bearer $KEY_BEDROCK_WILDCARD" -H 'Content-Type: application/json' -d '{"model": "bedrock/amazon.nova-micro-v1:0", "messages": [{"role": "user", "content": "Say hi in three words"}], "max_tokens": 20}'-> HTTP 200"Hello, world!",model=bedrock/amazon.nova-micro-v1:0(now listed); the team key -> 200"Hello there!", the access-group key -> 200"Hello, friend!"bedrock/eu.anthropic.claude-opus-4-5-20251101-v1:0(no longer listed) -> HTTP 400BedrockException - {"message":"The provided model identifier is invalid."}bedrock/anthropic.claude-opus-4-5-20251101-v1:0(not listed; the static catalog listed it) -> HTTP 400Invocation of model ID anthropic.claude-opus-4-5-20251101-v1:0 with on-demand throughput isn't supportedbedrock/global.anthropic.claude-opus-4-5-20251101-v1:0(listed) -> HTTP 200"Hi there, friend!""stream": trueonbedrock/amazon.nova-micro-v1:0-> 200, 5 chunks,[DONE],"Hello there!";"model": "gpt-control"on thebedrock/*key -> 403not available for this API key;"fallbacks": ["bedrock/amazon.nova-micro-v1:0"]-> 200"Hello there!"; afterPOST /key/deletethe same key -> 401Flag on: /v1/messages
curl -s -w '%{http_code}' http://127.0.0.1:57097/v1/messages -H "Authorization: Bearer $KEY_BEDROCK_WILDCARD" -H 'Content-Type: application/json' -d '{"model": "bedrock/amazon.nova-micro-v1:0", "max_tokens": 20, "messages": [{"role": "user", "content": "Say hi in three words"}]}'-> HTTP 200type=message,"Hello there!",input_tokens=5 output_tokens=4"stream": true-> 200, 8 events ending inmessage_stopFlag on: /v1/responses
curl -s -w '%{http_code}' http://127.0.0.1:57097/v1/responses -H "Authorization: Bearer $KEY_BEDROCK_WILDCARD" -H 'Content-Type: application/json' -d '{"model": "bedrock/amazon.nova-micro-v1:0", "input": "Say hi in three words", "max_output_tokens": 20}'-> HTTP 200status=completed,"Hello there!",id=resp_YKrfN4j"stream": true-> 200, 12 events,response.completed,"Hello, world!"Flag off: the static catalog
curl -s http://127.0.0.1:57097/v1/models ... | jq '.data | length'->1769(eu.=34 jp.=10 us-gov.=15 global.=32 us.=64 anthropic.=66underbedrock/, the same set as the base),GET /model/info->1769rows{"models": ["bedrock/anthropic.*"]},GET /v1/models->33staticbedrock/anthropic.ids{"models": ["anthropic/*"]},GET /v1/models->347, all underanthropic/and none bare; firstanthropic/1024-x-1024/50-steps/bedrock/amazon.nova-canvas-v1:0(the static catalog's image ids included, as before underbedrock/)POST /v1/chat/completionswithbedrock/eu.anthropic.claude-opus-4-5-20251101-v1:0-> HTTP 400The provided model identifier is invalid.;bedrock/global.anthropic.claude-opus-4-5-20251101-v1:0-> 200"Hi there, friend!";bedrock/amazon.nova-micro-v1:0-> 200"Hello, friend!";anthropic/us.anthropic.claude-haiku-4-5-20251001-v1:0on theanthropic/*key -> 200"Hey there, friend!"Closing observations
/model/info,/model_group/info, key and team listings agree with/v1/models; the PR causes thisanthropic/<id>; the PR causes thisPOST /key/deleteanswers 200 or 401 by worker; pre-existing, the PR leaves it alone/spend/logsanswers the same two rows on both legs; the PR leaves spend alonePASS: with
check_provider_endpoint: true,bedrock/*lists the account's invocable ids in the region instead of nothing, a partial wildcard lists only matching ids, a custom prefix lists every discovered id under that prefix, each listed id answers on all three endpoints, and the flag-off catalog is unchanged apart from the bare ids a colliding custom prefix used to listBlast radius, base 8fc31b1 vs head 8fa44a2 (/live-pr-risk)
Verdict: no dependent path breaks. The contract changes are the fix itself and, behind the opt-in flag, the cost of a listing: every Bedrock wildcard deployment now makes one or two control-plane round trips per cold listing (one per worker per 5 minutes when they succeed, every call when they fail), and a deployment whose control plane does not answer stalls each listing call for the lister's 10 s read timeout on the worker's event loop. That is the shape the OpenAI and Anthropic listers already have on main behind the same flag. Both are listed under backward incompatible with their measurements and dispositions, and the class fix (cache failures, run discovery off the event loop) is tracked on a private ticket. The custom-prefix match is the one flag-off change, listed there too
Rig: the same box, worktree, venv and Postgres per leg as the proof above,
--num_workers 2on both, no Redis, the same config and the same 44 requests in the same order, each leg'slitellm.__file__asserted inside its own worktree, plus a concurrency cell (two cold listings at once with 70/health/livelinesspings at 0.5 s and agpt-controlchat 2 s in) and an SDK cell (litellm.get_valid_modelsin a subprocess of each worktree). Everyamazonaws.comrequest went through a forwarding recorder per leg (mitmdump, SigV4 intact) in front of the real control plane and runtime, so the outbound wire is diffed base vs head belowBreaking: none. Status, body and token usage matched on every chat and auth request but one:
bedrock/amazon.nova-micro-v1:0200 on the wildcard key, the access-group key and the team key; the unlistedeu.id 400 from Bedrock (invalid identifier) and the bareanthropic.claude-opus-4-5-20251101-v1:0400 from Bedrock (on-demand unsupported) on both legs;gpt-controlon abedrock/*key 403 on both;anthropic/us.anthropic.claude-haiku-4-5-20251001-v1:0200 and its bare form 403 on the custom-prefix key on both; fallback, streaming,/v1/messages,/v1/responses, key info,/health/readiness,/uiand the playground all identical;/spend/logsanswers the same two rows on both legs. The one difference is the chat sent right afterPOST /key/delete: 200 on the base and 401 on the head in this pass, 401 on the base and 200 on the head in earlier passes, 200 on the merged head. The proxy runs two uvicorn workers with no Redis, so each worker keeps its own key cache and the chat passes when it lands on the worker that did not serve the delete; the same on both legs, outside this PR. The 14 runtime POSTs per leg are identical on the wire (the base has a 15th, the chat that race let through): the same eightbedrock-runtime.us-east-1.amazonaws.compaths (converseandconverse-streamon nova micro;converseon theeu., bare,global.andus.haiku ids), the same 118-byte bodies, the same nine header keys, 11 x 200 and 3 x 400 on the head, one attempt each. The head adds 18 control-plane GETs tobedrock.us-east-1.amazonaws.comand nothing else: 4GET /foundation-models?byInferenceType=ON_DEMAND(median 152 ms, max 167) and 4GET /inference-profiles?maxResults=1000&typeEquals=SYSTEM_DEFINED(median 370 ms, max 412) from the good deployments (two workers, two cold periods in the run;anthropic/*shares the discovery cache entry withbedrock/*because theirlitellm_paramsare identical, so it added no round trip) and 10GET /inference-profilesanswered 403 from thebedrock-badkey/*deployment (one per listing call, median 38 ms, max 43); header keysaccept,accept-encoding,authorization,connection,host,user-agent,x-amz-date, no body.bedrock-nocreds/*never reaches the wire. Thebedrock-slow/*requests time out on the proxy side before the recorder logs them, so their count comes from the proxy'sError getting valid models: The read operation timed outwarning, one per listing callBackward incompatible:
/v1/modelsand/models1 -> 317 ids (158 under each of the two discovered wildcard deployments),?return_wildcard_routes=true7 -> 323,/model/infoand/v1/model/info7 -> 321 rows,/model_group/info1 -> 317 groups, wildcard-key, access-group-key, team-key and custom-prefix-key listings 0 -> 158; the playground offers the chat-mode entries. Intended, this PRbedrock/anthropic.*overbedrock/anthropic.*) filters the discovered ids instead of aliasing them: with the flag on it lists 0 ids on both legs here (no invocable id starts withanthropic.), with the flag off it lists the same 33 static ids on both legs. The intermediate commit 25d5a61 of this PR (never on main) listed 156bedrock/anthropic.<id>alias names that no deployment can route. Fixed by 5ce9254bedrock:ListInferenceProfilesorbedrock:ListFoundationModels, no credentials) now costs one failed control-plane round trip and oneError getting valid modelsWARNING per listing call, retried on every call because the discovery cache stores successes only: the badkey deployment's 403 was 38 ms median upstream, one per listing call; the nocreds deployment fails its credential chain locally once per call. Base: those deployments list nothing and log nothing. Low caveat, shipped as-is, approved by mateo-berri in the review on this PRbedrock-runtimeendpoint but nobedrockendpoint) adds the lister's 10 s read timeout to every listing call, and the listing runs synchronously inside the async route handler, so that worker's event loop is blocked meanwhile: withbedrock-slow/*delayed 12 s, warm/v1/modelstook 10.45 s and/model/info10.96 s on the head against 0.11 s and 0.35 s on the base; two cold listings sent at once took 16.0 s and 16.9 s and left both workers blocked, so a/health/livelinessping in that window waited 12.15 s (3 of 70 pings over 1 s against a base max of 1.48 s, 1 of 70) and agpt-controlchat sent 2 s in took 18.7 s against 8.27 s on the base. The same shape as the OpenAI and Anthropic listers on main behind the same flag, and the reason the flag stays off by default (design decision 4). Medium caveat, shipped as-is behind the opt-in flag, approved by mateo-berri in the review on this PR; the class fix is on a private ticketlitellm.get_valid_models(check_provider_endpoint=True, custom_llm_provider="bedrock")returns the account's 158 ids inAWS_REGION_NAMEinstead of 0 (0.78 s cold), 347 with the flag off on both legs, andaws_region_nameonlitellm_paramspicks the region. The 5 minute cache keys on the provider and litellm_params only, so a second call inside the TTL after changingAWS_REGION_NAMEtoeu-west-1returned the cached us-east-1 list (158 ids,eu.=0): the env-hash check inget_cached_model_infoonly runs when litellm_params is None, whichget_valid_modelsnever passes. Pre-existing cache shape, now reachable from Bedrock; Low caveat, same private ticketAWS_ACCESS_KEY_IDandAWS_SECRET_ACCESS_KEYset and nocustom_llm_provider,get_valid_models(check_provider_endpoint=True)lists nothing for Bedrock on both legs (_infer_valid_provider_from_env_varskeys on<PROVIDER>_API_KEYnames, so onlyBEDROCK_API_KEYinfers it). Unchanged by the PR, named so nobody reads it as a regressionmodel_nameprefix that starts a vendor id (anthropic/*overbedrock/*) lists every id under the prefix: with the flag on, a key onanthropic/*lists 158anthropic/<id>on the head against 0 on the base; with the flag off it lists 347 on both legs, but the base lists 33 of them bare (anthropic.claude-3-5-haiku-20241022-v1:0and the other ids that start with the stringanthropic) where the head lists all 347 asanthropic/<id>. The bare names never routed on that key (a chat on one answers 403 on both legs), so a client that picked one from the list failed before and no longer sees it. Pre-existing on main with the static catalog, reached by every discovered id with the flag on. Fixed by 8fa44a2Regression risk (reachable dependents this rig did not exercise):
api_keyorAWS_BEARER_TOKEN_BEDROCK), proven against a scripted control plane in the audit cells below and unit tested intests/unit/llms/bedrock/test_bedrock_model_listing.py; the live rig unsets the token to keep one SigV4 identityBaseAWSLLM's resolver, which the invoke path exercises live, but the listing ran with static keys onlyDependency graph (dependents of
BedrockModelInfo.__init__,get_models,discover_models,BedrockModelLister, and the partial-wildcard and custom-prefix branches ofget_known_models_from_wildcard):get_provider_model_info(utils.py) verified live through every listing route;_get_valid_models_from_provider_apiandget_valid_modelsverified live (SDK cell, both legs);get_known_models_from_wildcard,_get_wildcard_modelsandget_complete_model_listverified live (/v1/models,/models,/model/info,/v1/model/info,/model_group/info, the partial and custom-prefix wildcards, flag on and off);get_available_models_for_user(key, team and access-group scoping) verified live;expand_wildcard_deployments_for_model_infoverified live; the dashboard playground verified live (screenshots);BedrockPassthroughConfigandBedrockMantlePassthroughConfig(the only subclasses; the new__init__adds_client=Noneand keepsparser) verified live through the proxy boot and the passthrough routes in the earlier run at b44ccc0; the alias branch ofget_known_models_from_wildcard(genai/test/*overopenai/*) tested intests/unit/proxy/auth/test_model_checks.pyand unchanged for a deployment whosemodeldoes not repeat the suffix; the custom-prefix branch's<prefix>/match verified live withanthropic/*overbedrock/*(key listing and a chat on a listed id, flag on and off) and unit tested in the same fileMerge-ref check: main moved to 4df5006 past the branch point. Of its commits only #44447 touches the Bedrock auth helpers (
base_aws_llm.py,common_utils.py); the lister signs its own GETs withSigV4Authin_auth_headers, which that change does not touch.origin/mainmerged cleanly into the head worktree on the box (merge commit c02803425d), the head leg rebooted on the merged tree, and the 44 requests reran identical to the head run: 317 / 323 / 321 / 317 on the master-key routes, 158 on the wildcard, access-group, team and custom-prefix keys, 0 on the partial key, the same chat statuses and bodies apart from the deleted-key race (200 there) and the model's own stream event counts (9 and 13 against 8 and 12), and the same 18 control-plane GETs with 15 runtime POSTs (the race's chat included). A local merge of the later main 04f0ea8 into the tip is clean and passes the 98 unit tests of the lister and the wildcard expanderNot verified: the four regression-risk items above; the
bedrock-slow/*control-plane requests themselves (the proxy times out before the recorder logs them, so their count is read from the proxy warning rather than the wire); the runtime boundary is recorded for the us-east-1conversepaths only; which worker served the chat afterPOST /key/delete(the per-worker key cache is read from the proxy's design, not instrumented in the run); a Redis-backed multi-pod cache (not applicable, the discovery cache is per process and the PR does not change that)Audit round 5 of 3 (/audit, base 8fc31b1 vs head 8fa44a2, changed cells again at 3dbe39e)
Every surface in the inventory (34 rows: the listing routes, key, team and access-group scoping, bearer and SigV4 listing auth, the listing host per region and partition, the three unified endpoints on a discovered id, flag off, control-plane 403/404/html/shape/empty answers, no credentials, refused egress, a hung control plane, endless pagination, unusable region values, non-string credential fields, the per-worker cache, two regions, partial wildcards, custom-prefix wildcards both distinct from and starting a discovered id, inactive profiles, concurrent cold listings, a region update mid-flow, an outage and a slow control plane under a burst, a worker killed mid-listing, the SDK entrypoints, and the live row above) is a cell in
tests/integration/providers/test_bedrock_model_discovery_wire.py,tests/integration/chaos/test_bedrock_model_discovery_chaos.pyortests/integration/sdk/test_bedrock_model_discovery_sdk.py, run against a scripted AWS control plane that terminates TLS forbedrock.<region>.<suffix>and verifies the SigV4 signature of every listing. Each cell ran on both legs at the tip (two runs on the head, one on the base, same seeds), plus the control cells that share the SigV4 verifier. Head 39/39 twice on the provider and chaos cells, 3/3 twice on the SDK cells, controls 5/5 and 2/2; the base is red on every cell the PR changes and green on the ones it leaves alone (flag off, no credentials, unusable region, non-string fields, environment-only SDK). Rig lit1694a3 on the same box, base :34181, head :45787; run idsround-lanes-round5.json(head-sdk-run2,ctl-providers,ctl-security),round-lanes-round5b.json(base-sdk,head-providers-run1,head-providers-run2) andround-lanes-round5c.json(base-providers,head-sdk-run1; the first two tries of those lanes were refused by the box's memory admission and reran once it had room)Rounds 4 and 5 ran past the cap of three, each after a product fix the live QA found: the partial-wildcard filter in 5ce9254 and the custom prefix in 8fa44a2
The tests-only commit 3dbe39e (type annotations in the discovery cells and in
aws_control_plane.pyandbedrock_discovery.py, the two helpers every one of them uses) reran those 42 cells on both legs at 3dbe39e with the same seeds, base once and head twice, on rig lit1694d2 (base :57753, head :37595, run idround-lanes-rerun.json). Head 39/39 and 3/3 on both runs with identical collected and passed selections, base green on the same 7 cells as round 5 and red on the rest, so every row matches round 5. The 7 controls and the live row do not use those helpers and keep their 8fa44a2 results; the Last ran at column names each row's commitCell matrix (49 rows)
Type
🐛 Bug Fix
Caveats (if any)
The before/after proof holds: before at the merge base 8fc31b1 (flag on lists nothing), after at 8fa44a2, the last commit with a product diff (flag on lists the account's 158 ids under each wildcard deployment, a custom prefix included), both on live proxies against the real Bedrock control plane and runtime, through the Admin UI playground the reporter used plus curl, with the commands and raw outputs above
Medium
check_provider_endpoint: true, abedrock/*deployment whose control plane does not answer stalls every listing call for the lister's 10 s timeout/v1/models,/models,/model/info,/model_group/info, key and team scoped listings, on every call (failures are not cached)/v1/models10.45 s against 0.11 s, two concurrent cold listings 16.0 s and 16.9 s with both workers blocked, a liveliness ping in that window 12.15 s, an unrelated chat 18.7 s against 8.27 sbedrock.<region>.amazonaws.com(a firewall, or a VPC with only thebedrock-runtimeendpoint)bedrockVPC endpoint plus the two IAM permissions (documented in the docs PR), or the flag offLow
bedrock:ListInferenceProfilesorbedrock:ListFoundationModels, no credentials) costs one failed round trip (38 ms median) and one WARNING per listing call. Same private ticketapi_base,aws_bedrock_runtime_endpoint) does not apply to the listing, which callsbedrock.<region>.<dns suffix>; a VPC needs abedrockinterface endpoint with private DNSaws_region_nameand noAWS_REGION_NAMElistsus-west-2's set, the invoke path's default regionget_valid_models(check_provider_endpoint=True, custom_llm_provider="bedrock")keys its 5 minute cache on the provider and litellm_params only, so a region change throughAWS_REGION_NAMEinside the TTL returns the first region's list (pre-existing cache shape). Same private ticket/v1/modelsconsumers that do not filter by capability see themus.,global.); with the flag off it still lists the static ids with that prefixanthropic/*) no longer lists those ids bare; a client that read a bare name from that list and sent it got 403 before and now does not see the nameFinal Attestation
The tests check the right things, including the edge cases, and regressions in the respective real-world customer use-cases are not possible after this PR
8fa44a2 passes /live-pr-risk
3dbe39e passes /audit
Note
Medium Risk
Changes proxy model listing and AWS-signed control-plane calls behind an opt-in flag; unreachable or slow Bedrock endpoints can block listing for up to 10s per worker and affect wildcard expansion.
Overview
With
check_provider_endpoint: true, Bedrock wildcard deployments no longer return an empty list or only the static catalog.BedrockModelInfonow implementsdiscover_modelsvia a newBedrockModelListerthat calls the regional Bedrock control plane (/foundation-modelsfor on-demand models and paginated/inference-profilesfor ACTIVE system-defined profiles), signed with the deployment’s SigV4 credentials or Bedrock bearer token. Proxy/SDK listings and wildcard expansion use that live set; with the flag off, behavior is unchanged.Proxy wildcard handling is tightened: partial wildcards whose
modelrepeats the suffix (e.g.bedrock/anthropic.*) filter discovered ids instead of aliasing them, and custom-prefix wildcards only strip a leading segment when it is a known provider (requiresprefix/match).Broad unit, integration, and SDK tests add a scripted TLS control-plane proxy and cover auth, regions/partitions, failures, caching, concurrency, and chaos (worker killed mid-listing).
Reviewed by Cursor Bugbot for commit 3dbe39e. Bugbot is set up for automated code reviews on this repo. Configure here.