Skip to content

fix(bedrock): resolve the region prefix the same way on every Bedrock surface - #39865

Open
mateo-berri wants to merge 5 commits into
mainfrom
litellm_lit_6969_bedrock_embed_region
Open

mateo-berri wants to merge 5 commits into
mainfrom
litellm_lit_6969_bedrock_embed_region

Conversation

@mateo-berri

@mateo-berri mateo-berri commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

TLDR

Problem this solves:

  • bedrock/<region>/<embedding model> fails every embedding request
  • Titan answers 500 "Unable to map Bedrock request to provider"
  • Nova and Cohere send the prefixed id to AWS and get a 400 back
  • Region-prefixed invoke chat, image generation, and rerank ids fail the same way: the id goes to AWS with the region still inside it, at the env region
  • Bedrock converse chat models already accept the same region-prefixed shape
  • The cost map prices GovCloud embeddings under exactly that prefixed key
  • The Bedrock region list stopped at 23 regions, so newer ones like ap-southeast-3 never split off
  • /health dropped the id's region and probed the env or default region instead

How it solves it:

  • One shared helper splits a leading Bedrock region off the model id
  • Embeddings use the bare id for provider detection, request shape, and the invoke URL
  • BaseAWSLLM resolves the region out of the model id ahead of the ARN and env fallbacks and drops it from the model id it signs and posts, so invoke chat, image generation, and rerank (bare ARN as modelArn) follow
  • The region in the id becomes the request's region unless aws_region_name is set; an empty aws_region_name no longer wins over the id in the converse handler either
  • The response keeps the prefixed id, so cost lookup hits the region's row
  • The converse handler now uses the same helper instead of its own inline strip
  • The region list now covers every Bedrock region the cost map prices, and a test keeps it that way
  • The health check carries the id's region into aws_region_name unless the deployment already sets one
  • One region precedence for every Bedrock call that resolves its region through BaseAWSLLM (chat, embeddings, images, rerank, count_tokens, batches, realtime): aws_region_name, then the ARN region, then the id's prefix, then env; an empty aws_region_name counts as unset there. Vector stores, Claude platform, search, and SageMaker keep reading the value as is
  • count_tokens, batch jobs, realtime sessions, and the OpenAI-compatible invoke route send the bare id to AWS as well
  • get_bedrock_base_model strips the region before the cross-region prefix, and cost lookup keeps the us./eu. profile row for a region-prefixed profile id
  • Route detection, cache points, parallel tool use, Claude 4.5 detection, and the model budget limiter look the id up as the full id, then the routing-free id, then the region-free profile id, then the base, so bedrock/<region>/us.deepseek.r1-v1:0 keeps its converse route and its profile row

User Flow

Before: a proxy admin who deploys the region-prefixed Bedrock ids the cost map documents (bedrock/us-gov-west-1/amazon.titan-embed-text-v2:0, bedrock/us-east-1/mistral.mistral-7b-instruct-v0:2, and so on) gets embeddings, invoke-route chat, streaming, image editing, and rerank requests refused, Anthropic Messages calls answered with an AWS error body, token counts from a local fallback, image generation silently empty, and /health reporting the deployments unhealthy, because every request goes to the proxy's default region with the region still glued to the model id

  1. The admin adds govcloud-titan with model: bedrock/us-gov-west-1/amazon.titan-embed-text-v2:0 and GovCloud keys, plus mistral-east (bedrock/us-east-1/mistral.mistral-7b-instruct-v0:2), haiku-invoke-east (bedrock/invoke/us-east-1/us.anthropic.claude-haiku-4-5-20251001-v1:0), haiku-east (bedrock/us-east-1/us.anthropic.claude-haiku-4-5-20251001-v1:0), canvas-east (bedrock/us-east-1/amazon.nova-canvas-v1:0), rerank-east (bedrock/us-east-1/arn:aws:bedrock:us-east-1::foundation-model/cohere.rerank-v3-5:0), and deepseek-jakarta (bedrock/ap-southeast-3/deepseek.v3.2) with commercial credentials, leaves aws_region_name unset everywhere except one copy of haiku-east where it is cleared to "", and starts the proxy in an environment whose default region is AWS_REGION_NAME=us-west-2
  2. A developer sends POST https://litellm-domain/v1/embeddings with {"model": "govcloud-titan", "input": "region prefix"} and gets HTTP 500 litellm.APIConnectionError: Unable to map Bedrock request to provider with x-litellm-response-cost: 0; the same shape with amazon.nova-2-multimodal-embeddings-v1:0 or cohere.embed-english-v3 behind the prefix gets HTTP 400 BedrockException - {"message":"The provided model identifier is invalid."}
  3. The developer sends POST https://litellm-domain/v1/chat/completions with {"model": "mistral-east", "messages": [{"role": "user", "content": "Reply with the single word ok"}], "max_tokens": 5} and gets HTTP 422 BedrockException - Error processing={"Output":{"__type":"com.amazon.coral.service#UnknownOperationException"},"Version":"1.0"}; the same call to haiku-invoke-east gets HTTP 500 litellm.APIConnectionError: 'content', and with "stream": true it gets HTTP 500 litellm.APIConnectionError: Checksum mismatch with no SSE chunks
  4. The developer sends the same chat request to the haiku-east copy whose aws_region_name is "" and gets HTTP 500 litellm.APIConnectionError: Invalid AWS region format: ''. Region names must contain only lowercase letters, digits, and hyphens., and to deepseek-jakarta and gets HTTP 404 BedrockException - Bedrock Invoke HTTPX: Unknown provider=None, model=ap-southeast-3/deepseek.v3.2
  5. The developer sends POST https://litellm-domain/v1/messages with {"model": "haiku-invoke-east", "max_tokens": 5, "messages": [{"role": "user", "content": "Reply with the single word ok"}]} and gets HTTP 200 whose body is {"Output": {"__type": "com.amazon.coral.service#UnknownOperationException"}, "Version": "1.0"}, no content, no usage, and x-litellm-response-cost-input: 0.0; POST https://litellm-domain/v1/messages/count_tokens with the same messages for haiku-east gets HTTP 200 {"input_tokens": 13}, a number from the proxy's local tokenizer because the provider count was requested in us-west-2, where the deployment's credentials are not allowed
  6. The developer sends POST https://litellm-domain/v1/images/generations with {"model": "canvas-east", "prompt": "a red circle on white", "n": 1, "size": "320x320"} and gets HTTP 200 with "data": [], no image and no cost header; POST https://litellm-domain/v1/images/edits with model=canvas-east, a PNG image, and prompt=make the circle blue gets HTTP 500 BedrockException - Nova Canvas image edit returned no images
  7. The developer sends POST https://litellm-domain/v1/rerank with {"model": "rerank-east", "query": "capital of the United States", "documents": [...], "top_n": 2} and gets HTTP 400 1 validation error detected: Value 'us-east-1/arn:aws:bedrock:us-east-1::foundation-model/cohere.rerank-v3-5:0' at 'rerankingConfiguration.bedrockRerankingConfiguration.modelConfiguration.modelArn' failed to satisfy constraint
  8. The admin opens GET https://litellm-domain/health and finds govcloud-titan under unhealthy_endpoints with BedrockException Invalid Authentication - {"message":"The security token included in the request is invalid."}, canvas-east with The provided model identifier is invalid., the "" copy of haiku-east with Invalid AWS region format: '', and deepseek-jakarta with Unknown provider=None, model=ap-southeast-3/deepseek.v3.2, because every probe went to us-west-2 or nowhere
  9. The admin works around it by dropping the prefix from every id and adding aws_region_name per deployment; the requests now succeed, but the titan embedding prices at 6e-08 for 3 tokens, the commercial rate, not the GovCloud rate the cost map carries under the prefixed id

After: the same region-prefixed deployments embed, chat, stream, count tokens, draw, edit, and rerank against the region named in the id even though the environment defaults to us-west-2, price from that region's row, treat an empty aws_region_name as unset, accept ids prefixed with newer regions, and show healthy

  1. The admin adds govcloud-titan with model: bedrock/us-gov-west-1/amazon.titan-embed-text-v2:0 and GovCloud keys, plus mistral-east (bedrock/us-east-1/mistral.mistral-7b-instruct-v0:2), haiku-invoke-east (bedrock/invoke/us-east-1/us.anthropic.claude-haiku-4-5-20251001-v1:0), haiku-east (bedrock/us-east-1/us.anthropic.claude-haiku-4-5-20251001-v1:0), canvas-east (bedrock/us-east-1/amazon.nova-canvas-v1:0), rerank-east (bedrock/us-east-1/arn:aws:bedrock:us-east-1::foundation-model/cohere.rerank-v3-5:0), and deepseek-jakarta (bedrock/ap-southeast-3/deepseek.v3.2) with commercial credentials, leaves aws_region_name unset everywhere except one copy of haiku-east where it is cleared to "", and starts the proxy in an environment whose default region is AWS_REGION_NAME=us-west-2
  2. A developer sends POST https://litellm-domain/v1/embeddings with {"model": "govcloud-titan", "input": "region prefix"} and gets HTTP 200 with a 1024-dimension embedding, 3 prompt tokens, and x-litellm-response-cost: 6e-07 (the GovCloud rate; a plain bedrock/amazon.titan-embed-text-v2:0 deployment still prices at 6e-08); the nova and cohere shapes answer 200 with 3072- and 1024-dimension embeddings
  3. The developer sends the same POST https://litellm-domain/v1/chat/completions and gets HTTP 200 with content: "\nOk." from mistral-east and content: "ok" from haiku-invoke-east, each with a non-zero x-litellm-response-cost, and with "stream": true gets HTTP 200 text/event-stream chunks that spell ok and end with finish_reason: "stop"
  4. The developer sends the same chat request to the "" copy of haiku-east and gets HTTP 200 content: "ok" with x-litellm-response-cost: 3.63e-05, and to deepseek-jakarta and gets HTTP 200 content: "ok" with x-litellm-response-cost: 1.184e-05
  5. The developer sends the same POST https://litellm-domain/v1/messages to haiku-invoke-east and gets HTTP 200 with content: [{"type": "text", "text": "ok"}], stop_reason: "end_turn", usage.input_tokens: 13, and x-litellm-response-cost: 3.3e-05; the same POST https://litellm-domain/v1/messages/count_tokens for haiku-east gets HTTP 200 {"input_tokens": 29}, the count Bedrock itself returns from us-east-1
  6. The developer sends the same POST https://litellm-domain/v1/images/generations and gets HTTP 200 with one b64_json image in data and x-litellm-response-cost: 0.06; the same POST https://litellm-domain/v1/images/edits gets HTTP 200 with one edited b64_json image and x-litellm-response-cost: 0.06
  7. The developer sends the same POST https://litellm-domain/v1/rerank and gets HTTP 200 with results: [{"index": 0, "relevance_score": 0.875}, {"index": 1, "relevance_score": 0.182}] and x-litellm-response-cost: 0.002
  8. The admin opens GET https://litellm-domain/health and finds every deployment under healthy_endpoints with aws_region_name filled in from the id (us-gov-west-1 for titan, ap-southeast-3 for deepseek, us-east-1 for the rest, including the "" copy)
  9. The admin keeps the prefixed ids: no duplicate plain deployments, the spend logs carry the GovCloud price, and GET https://litellm-domain/v1/model/info lists deepseek-jakarta with litellm_provider: bedrock, max_input_tokens: 163840, and input_cost_per_token: 7.4e-07 from the cost map

Relevant issues

Linear ticket

Resolves LIT-6969

Pre-Submission checklist

Please complete all items before asking a LiteLLM maintainer to review your PR

  • I have added meaningful tests
  • The handful of test files covering my change pass locally, e.g. uv run pytest tests/test_litellm/<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
  • My PR passes all required CI/CD checks (e.g., lint, schema.d.ts sync check, etc.)
  • My PR's scope is as isolated as possible; it only solves 1 specific problem
  • I have received a Greptile Confidence Score of at least 4/5 before requesting a maintainer review (Greptile reviews automatically once the PR is opened; only comment @greptileai to re-request a review after pushing changes)

Delays in PR merge?

If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).

Screenshots / Proof of Fix

Live proxy against the real Bedrock GovCloud account (us-gov-west-1, SigV4 keys) and the real commercial account (us-east-1, Bedrock API key as api_key; SigV4 keys for rerank, which takes no bearer token), each leg booted as one proxy instance with two uvicorn workers (AWS_REGION_NAME=us-west-2 python litellm/proxy/proxy_cli.py --config config.yaml --port <port> --num_workers 2 --detailed_debug) and LITELLM_LOCAL_MODEL_COST_MAP=True, so the cost headers come from that commit's cost map. AWS_REGION_NAME=us-west-2 in the proxy env is the trap: every *-prefixed deployment except govcloud-titan-prefixed has no aws_region_name, so only the region inside the model id can send it to the right region. Same config, same eleven deployments, same requests on both sides; only the proxy commit differs

model_list:
  - model_name: govcloud-titan-prefixed
    litellm_params:
      model: bedrock/us-gov-west-1/amazon.titan-embed-text-v2:0
      aws_region_name: us-gov-west-1
      aws_access_key_id: os.environ/AWS_GOVCLOUD_ACCESS_KEY_ID
      aws_secret_access_key: os.environ/AWS_GOVCLOUD_SECRET_ACCESS_KEY
    model_info:
      mode: embedding
  - model_name: govcloud-titan-prefixed-noregion
    litellm_params:
      model: bedrock/us-gov-west-1/amazon.titan-embed-text-v2:0
      aws_access_key_id: os.environ/AWS_GOVCLOUD_ACCESS_KEY_ID
      aws_secret_access_key: os.environ/AWS_GOVCLOUD_SECRET_ACCESS_KEY
    model_info:
      mode: embedding
  - model_name: govcloud-titan-plain
    litellm_params:
      model: bedrock/amazon.titan-embed-text-v2:0
      aws_region_name: us-gov-west-1
      aws_access_key_id: os.environ/AWS_GOVCLOUD_ACCESS_KEY_ID
      aws_secret_access_key: os.environ/AWS_GOVCLOUD_SECRET_ACCESS_KEY
    model_info:
      mode: embedding
  - model_name: govcloud-nova-prefixed
    litellm_params:
      model: bedrock/us-gov-west-1/amazon.nova-2-multimodal-embeddings-v1:0
      aws_region_name: us-gov-west-1
      aws_access_key_id: os.environ/AWS_GOVCLOUD_ACCESS_KEY_ID
      aws_secret_access_key: os.environ/AWS_GOVCLOUD_SECRET_ACCESS_KEY
    model_info:
      mode: embedding
  - model_name: govcloud-nova-plain
    litellm_params:
      model: bedrock/amazon.nova-2-multimodal-embeddings-v1:0
      aws_region_name: us-gov-west-1
      aws_access_key_id: os.environ/AWS_GOVCLOUD_ACCESS_KEY_ID
      aws_secret_access_key: os.environ/AWS_GOVCLOUD_SECRET_ACCESS_KEY
    model_info:
      mode: embedding
  - model_name: commercial-cohere-embed-prefixed
    litellm_params:
      model: bedrock/us-east-1/cohere.embed-english-v3
      api_key: os.environ/BEDROCK_COMMERCIAL_BEARER_TOKEN
    model_info:
      mode: embedding
  - model_name: commercial-mistral-invoke-prefixed
    litellm_params:
      model: bedrock/us-east-1/mistral.mistral-7b-instruct-v0:2
      api_key: os.environ/BEDROCK_COMMERCIAL_BEARER_TOKEN
    model_info:
      mode: chat
  - model_name: commercial-haiku-invoke-prefixed
    litellm_params:
      model: bedrock/invoke/us-east-1/us.anthropic.claude-haiku-4-5-20251001-v1:0
      api_key: os.environ/BEDROCK_COMMERCIAL_BEARER_TOKEN
    model_info:
      mode: chat
  - model_name: commercial-haiku-converse-prefixed
    litellm_params:
      model: bedrock/us-east-1/us.anthropic.claude-haiku-4-5-20251001-v1:0
      api_key: os.environ/BEDROCK_COMMERCIAL_BEARER_TOKEN
    model_info:
      mode: chat
  - model_name: commercial-nova-canvas-prefixed
    litellm_params:
      model: bedrock/us-east-1/amazon.nova-canvas-v1:0
      api_key: os.environ/BEDROCK_COMMERCIAL_BEARER_TOKEN
    model_info:
      mode: image_generation
  - model_name: commercial-rerank-prefixed
    litellm_params:
      model: bedrock/us-east-1/arn:aws:bedrock:us-east-1::foundation-model/cohere.rerank-v3-5:0
      aws_access_key_id: os.environ/AWS_COMMERCIAL_ACCESS_KEY_ID
      aws_secret_access_key: os.environ/AWS_COMMERCIAL_SECRET_ACCESS_KEY
    model_info:
      mode: rerank
general_settings:
  master_key: os.environ/LITELLM_MASTER_KEY

Before (78ad88f)

govcloud-titan-prefixed (POST /v1/embeddings)

  1. Run

    curl -s -D - -X POST http://127.0.0.1:22784/v1/embeddings -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d '{"model": "govcloud-titan-prefixed", "input": "region prefix"}'
    
  2. Observed

    HTTP/1.1 500 Internal Server Error
    x-litellm-response-cost: 0
    body: {"error": {"message": "litellm.APIConnectionError: Unable to map Bedrock request to provider", "type": "internal_server_error", "param": null, "code": "500"}}
    

govcloud-titan-prefixed-noregion (POST /v1/embeddings)

  1. Run

    curl -s -D - -X POST http://127.0.0.1:22784/v1/embeddings -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d '{"model": "govcloud-titan-prefixed-noregion", "input": "region prefix"}'
    
  2. Observed

    HTTP/1.1 500 Internal Server Error
    x-litellm-response-cost: 0
    body: {"error": {"message": "litellm.APIConnectionError: Unable to map Bedrock request to provider", "type": "internal_server_error", "param": null, "code": "500"}}
    

govcloud-titan-plain (POST /v1/embeddings)

  1. Run

    curl -s -D - -X POST http://127.0.0.1:22784/v1/embeddings -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d '{"model": "govcloud-titan-plain", "input": "region prefix"}'
    
  2. Observed

    HTTP/1.1 200 OK
    x-litellm-response-cost: 6.000000000000001e-08
    body: model=govcloud-titan-plain dims=1024 usage={'completion_tokens': 0, 'prompt_tokens': 3, 'total_tokens': 3, 'completion_tokens_details': None, 'prompt_tokens_details': None}
    

govcloud-nova-prefixed (POST /v1/embeddings)

  1. Run

    curl -s -D - -X POST http://127.0.0.1:22784/v1/embeddings -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d '{"model": "govcloud-nova-prefixed", "input": "region prefix"}'
    
  2. Observed

    HTTP/1.1 400 Bad Request
    x-litellm-response-cost: 0
    body: {"error": {"message": "litellm.BadRequestError: BedrockException - {\"message\":\"The provided model identifier is invalid.\"}. Received Model Group=govcloud-nova-prefixed\nAvailable Model Group Fallbacks=None", "type": "invalid_request_error", "param": null, "code": "400"}}
    

govcloud-nova-plain (POST /v1/embeddings)

  1. Run

    curl -s -D - -X POST http://127.0.0.1:22784/v1/embeddings -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d '{"model": "govcloud-nova-plain", "input": "region prefix"}'
    
  2. Observed

    HTTP/1.1 200 OK
    x-litellm-response-cost: 0.00010368000000000001
    body: model=govcloud-nova-plain dims=3072 usage={'completion_tokens': 0, 'prompt_tokens': 768, 'total_tokens': 768, 'completion_tokens_details': None, 'prompt_tokens_details': None}
    

commercial-cohere-embed-prefixed (POST /v1/embeddings)

  1. Run

    curl -s -D - -X POST http://127.0.0.1:22784/v1/embeddings -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d '{"model": "commercial-cohere-embed-prefixed", "input": "region prefix"}'
    
  2. Observed

    HTTP/1.1 400 Bad Request
    x-litellm-response-cost: 0
    body: {"error": {"message": "litellm.BadRequestError: BedrockException - {\"message\":\"The provided model identifier is invalid.\"}. Received Model Group=commercial-cohere-embed-prefixed\nAvailable Model Group Fallbacks=None", "type": "invalid_request_error", "param": null, "code": "400"}}
    

commercial-mistral-invoke-prefixed (POST /v1/chat/completions)

  1. Run

    curl -s -D - -X POST http://127.0.0.1:22784/v1/chat/completions -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d '{"model": "commercial-mistral-invoke-prefixed", "messages": [{"role": "user", "content": "Reply with the single word ok"}], "max_tokens": 5}'
    
  2. Observed

    HTTP/1.1 422 Unprocessable Entity
    x-litellm-response-cost: 0
    body: {"error": {"message": "litellm.BadRequestError: BedrockException - Error processing={\"Output\":{\"__type\":\"com.amazon.coral.service#UnknownOperationException\"},\"Version\":\"1.0\"}, Received error=Unexpected mistral completion response. Received Model Group=commercial-mistral-invoke-prefixed\nAvailable Model Group Fallbacks=None", "type": "invalid_request_error", "param": null, "code": "422"}}
    

commercial-haiku-invoke-prefixed (POST /v1/chat/completions)

  1. Run

    curl -s -D - -X POST http://127.0.0.1:22784/v1/chat/completions -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d '{"model": "commercial-haiku-invoke-prefixed", "messages": [{"role": "user", "content": "Reply with the single word ok"}], "max_tokens": 5}'
    
  2. Observed

    HTTP/1.1 500 Internal Server Error
    x-litellm-response-cost: 0
    body: {"error": {"message": "litellm.APIConnectionError: 'content'", "type": "internal_server_error", "param": null, "code": "500"}}
    

commercial-haiku-converse-prefixed (POST /v1/chat/completions)

  1. Run

    curl -s -D - -X POST http://127.0.0.1:22784/v1/chat/completions -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d '{"model": "commercial-haiku-converse-prefixed", "messages": [{"role": "user", "content": "Reply with the single word ok"}], "max_tokens": 5}'
    
  2. Observed

    HTTP/1.1 200 OK
    x-litellm-response-cost: 3.63e-05
    body: model=commercial-haiku-converse-prefixed content='ok' finish=stop
    

commercial-nova-canvas-prefixed (POST /v1/images/generations)

  1. Run

    curl -s -D - -X POST http://127.0.0.1:22784/v1/images/generations -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d '{"model": "commercial-nova-canvas-prefixed", "prompt": "a red circle on white", "n": 1, "size": "320x320"}'
    
  2. Observed

    HTTP/1.1 200 OK
    body: {"created": 1788594042, "background": null, "data": [], "output_format": null, "quality": null, "size": null, "usage": {"total_tokens": 0, "input_tokens": 0, "input_tokens_details": {"image_tokens": 0, "text_tokens": 0}, "output_tokens": 0}}
    

commercial-rerank-prefixed (POST /v1/rerank)

  1. Run

    curl -s -D - -X POST http://127.0.0.1:22784/v1/rerank -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d '{"model": "commercial-rerank-prefixed", "query": "capital of the United States", "documents": ["Washington, D.C. is the capital of the United States.", "Carson City is the capital of Nevada."], "top_n": 2}'
    
  2. Observed

    HTTP/1.1 400 Bad Request
    body: {"error": {"message": "litellm.BadRequestError: BedrockException - {\"message\":\"1 validation error detected: Value 'us-east-1/arn:aws:bedrock:us-east-1::foundation-model/cohere.rerank-v3-5:0' at 'rerankingConfiguration.bedrockRerankingConfiguration.modelConfiguration.modelArn' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:aws(-[^:]+)?:(bedrock|sagemaker):[a-z
    

GET /health

  1. Run

    curl -s -w 'HTTP/1.1 %{http_code}\n' http://127.0.0.1:22784/health -H "Authorization: Bearer $LITELLM_MASTER_KEY"
    
  2. Observed

    HTTP/1.1 200
    healthy_count=9 unhealthy_count=2
    healthy: model=amazon.titan-embed-text-v2:0 aws_region_name=us-gov-west-1
    healthy: model=amazon.titan-embed-text-v2:0 aws_region_name=us-gov-west-1
    healthy: model=amazon.nova-2-multimodal-embeddings-v1:0 aws_region_name=us-gov-west-1
    healthy: model=amazon.nova-2-multimodal-embeddings-v1:0 aws_region_name=us-gov-west-1
    healthy: model=cohere.embed-english-v3 aws_region_name=unset
    healthy: model=mistral.mistral-7b-instruct-v0:2 aws_region_name=unset
    healthy: model=invoke/us.anthropic.claude-haiku-4-5-20251001-v1:0 aws_region_name=unset
    healthy: model=us.anthropic.claude-haiku-4-5-20251001-v1:0 aws_region_name=unset
    healthy: model=arn:aws:bedrock:us-east-1::foundation-model/cohere.rerank-v3-5:0 aws_region_name=unset
    unhealthy: model=amazon.titan-embed-text-v2:0 aws_region_name=unset error=litellm.AuthenticationError: BedrockException Invalid Authentication - {"message":"The security token included in the request is invalid."}
    unhealthy: model=amazon.nova-canvas-v1:0 aws_region_name=unset error=litellm.BadRequestError: BedrockException - {"message":"The provided model identifier is invalid."}
    

After (18a8c6b)

govcloud-titan-prefixed (POST /v1/embeddings)

  1. Run

    curl -s -D - -X POST http://127.0.0.1:55975/v1/embeddings -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d '{"model": "govcloud-titan-prefixed", "input": "region prefix"}'
    
  2. Observed

    HTTP/1.1 200 OK
    x-litellm-response-cost: 6e-07
    body: model=govcloud-titan-prefixed dims=1024 usage={'completion_tokens': 0, 'prompt_tokens': 3, 'total_tokens': 3, 'completion_tokens_details': None, 'prompt_tokens_details': None}
    

govcloud-titan-prefixed-noregion (POST /v1/embeddings)

  1. Run

    curl -s -D - -X POST http://127.0.0.1:55975/v1/embeddings -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d '{"model": "govcloud-titan-prefixed-noregion", "input": "region prefix"}'
    
  2. Observed

    HTTP/1.1 200 OK
    x-litellm-response-cost: 6e-07
    body: model=govcloud-titan-prefixed-noregion dims=1024 usage={'completion_tokens': 0, 'prompt_tokens': 3, 'total_tokens': 3, 'completion_tokens_details': None, 'prompt_tokens_details': None}
    

govcloud-titan-plain (POST /v1/embeddings)

  1. Run

    curl -s -D - -X POST http://127.0.0.1:55975/v1/embeddings -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d '{"model": "govcloud-titan-plain", "input": "region prefix"}'
    
  2. Observed

    HTTP/1.1 200 OK
    x-litellm-response-cost: 6.000000000000001e-08
    body: model=govcloud-titan-plain dims=1024 usage={'completion_tokens': 0, 'prompt_tokens': 3, 'total_tokens': 3, 'completion_tokens_details': None, 'prompt_tokens_details': None}
    

govcloud-nova-prefixed (POST /v1/embeddings)

  1. Run

    curl -s -D - -X POST http://127.0.0.1:55975/v1/embeddings -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d '{"model": "govcloud-nova-prefixed", "input": "region prefix"}'
    
  2. Observed

    HTTP/1.1 200 OK
    x-litellm-response-cost: 0.00010368000000000001
    body: model=govcloud-nova-prefixed dims=3072 usage={'completion_tokens': 0, 'prompt_tokens': 768, 'total_tokens': 768, 'completion_tokens_details': None, 'prompt_tokens_details': None}
    

govcloud-nova-plain (POST /v1/embeddings)

  1. Run

    curl -s -D - -X POST http://127.0.0.1:55975/v1/embeddings -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d '{"model": "govcloud-nova-plain", "input": "region prefix"}'
    
  2. Observed

    HTTP/1.1 200 OK
    x-litellm-response-cost: 0.00010368000000000001
    body: model=govcloud-nova-plain dims=3072 usage={'completion_tokens': 0, 'prompt_tokens': 768, 'total_tokens': 768, 'completion_tokens_details': None, 'prompt_tokens_details': None}
    

commercial-cohere-embed-prefixed (POST /v1/embeddings)

  1. Run

    curl -s -D - -X POST http://127.0.0.1:55975/v1/embeddings -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d '{"model": "commercial-cohere-embed-prefixed", "input": "region prefix"}'
    
  2. Observed

    HTTP/1.1 200 OK
    x-litellm-response-cost: 2e-07
    body: model=commercial-cohere-embed-prefixed dims=1024 usage={'completion_tokens': 0, 'prompt_tokens': 2, 'total_tokens': 2, 'completion_tokens_details': None, 'prompt_tokens_details': None}
    

commercial-mistral-invoke-prefixed (POST /v1/chat/completions)

  1. Run

    curl -s -D - -X POST http://127.0.0.1:55975/v1/chat/completions -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d '{"model": "commercial-mistral-invoke-prefixed", "messages": [{"role": "user", "content": "Reply with the single word ok"}], "max_tokens": 5}'
    
  2. Observed

    HTTP/1.1 200 OK
    x-litellm-response-cost: 3.3499999999999997e-06
    body: model=commercial-mistral-invoke-prefixed content='\nOk.' finish=stop
    

commercial-haiku-invoke-prefixed (POST /v1/chat/completions)

  1. Run

    curl -s -D - -X POST http://127.0.0.1:55975/v1/chat/completions -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d '{"model": "commercial-haiku-invoke-prefixed", "messages": [{"role": "user", "content": "Reply with the single word ok"}], "max_tokens": 5}'
    
  2. Observed

    HTTP/1.1 200 OK
    x-litellm-response-cost: 3.3e-05
    body: model=commercial-haiku-invoke-prefixed content='ok' finish=stop
    

commercial-haiku-converse-prefixed (POST /v1/chat/completions)

  1. Run

    curl -s -D - -X POST http://127.0.0.1:55975/v1/chat/completions -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d '{"model": "commercial-haiku-converse-prefixed", "messages": [{"role": "user", "content": "Reply with the single word ok"}], "max_tokens": 5}'
    
  2. Observed

    HTTP/1.1 200 OK
    x-litellm-response-cost: 3.63e-05
    body: model=commercial-haiku-converse-prefixed content='ok' finish=stop
    

commercial-nova-canvas-prefixed (POST /v1/images/generations)

  1. Run

    curl -s -D - -X POST http://127.0.0.1:55975/v1/images/generations -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d '{"model": "commercial-nova-canvas-prefixed", "prompt": "a red circle on white", "n": 1, "size": "320x320"}'
    
  2. Observed

    HTTP/1.1 200 OK
    x-litellm-response-cost: 0.06
    body: images=1 b64_len=225516
    

commercial-rerank-prefixed (POST /v1/rerank)

  1. Run

    curl -s -D - -X POST http://127.0.0.1:55975/v1/rerank -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d '{"model": "commercial-rerank-prefixed", "query": "capital of the United States", "documents": ["Washington, D.C. is the capital of the United States.", "Carson City is the capital of Nevada."], "top_n": 2}'
    
  2. Observed

    HTTP/1.1 200 OK
    x-litellm-response-cost: 0.002
    body: model=None results=[(0, 0.875), (1, 0.182)]
    

GET /health

  1. Run

    curl -s -w 'HTTP/1.1 %{http_code}\n' http://127.0.0.1:55975/health -H "Authorization: Bearer $LITELLM_MASTER_KEY"
    
  2. Observed

    HTTP/1.1 200
    healthy_count=11 unhealthy_count=0
    healthy: model=amazon.titan-embed-text-v2:0 aws_region_name=us-gov-west-1
    healthy: model=amazon.titan-embed-text-v2:0 aws_region_name=us-gov-west-1
    healthy: model=amazon.titan-embed-text-v2:0 aws_region_name=us-gov-west-1
    healthy: model=amazon.nova-2-multimodal-embeddings-v1:0 aws_region_name=us-gov-west-1
    healthy: model=amazon.nova-2-multimodal-embeddings-v1:0 aws_region_name=us-gov-west-1
    healthy: model=cohere.embed-english-v3 aws_region_name=us-east-1
    healthy: model=mistral.mistral-7b-instruct-v0:2 aws_region_name=us-east-1
    healthy: model=invoke/us.anthropic.claude-haiku-4-5-20251001-v1:0 aws_region_name=us-east-1
    healthy: model=us.anthropic.claude-haiku-4-5-20251001-v1:0 aws_region_name=us-east-1
    healthy: model=amazon.nova-canvas-v1:0 aws_region_name=us-east-1
    healthy: model=arn:aws:bedrock:us-east-1::foundation-model/cohere.rerank-v3-5:0 aws_region_name=us-east-1
    

Cost sanity check: the cost map prices amazon.titan-embed-text-v2:0 at 2e-08 per token and bedrock/us-gov-west-1/amazon.titan-embed-text-v2:0 at 2e-07 per token, so 3 tokens cost 6e-08 on the plain deployment and 6e-07 on the region-prefixed ones

QA notes:

Every other Bedrock surface, same Before and After commits

The live-pr-risk rig boots the same way (AWS_REGION_NAME=us-west-2, LITELLM_LOCAL_MODEL_COST_MAP=True, two workers) with nineteen deployments covering what the embeddings proof above does not: /v1/messages on invoke and converse ids, /v1/messages/count_tokens with SigV4 keys, streaming invoke chat, an aws_region_name: "" copy, bedrock/ap-southeast-3/deepseek.v3.2, /v1/images/edits, /v1/rerank on a bare prefixed id and on a plain ARN, two region-prefixed ids whose only cost-map row is the us. profile row (us.deepseek.r1-v1:0, us.openai.gpt-5.6-luna), /v1/model/info, and /health. Every request hits real Bedrock. Payloads: chat and messages send Reply with the single word ok with max_tokens 5 (60 for the profile-only ids, plus reasoning_effort: low on the gpt-5.6 one), rerank sends the two-document capital query with top_n 2, image edits post a 320x320 circle PNG with make the circle blue. The output below is the rig's log verbatim: the curl -s -D - status and cost headers, the parsed body, then the upstream URLs the proxy log shows for count_tokens and for the profile-only ids

model_list:
  - model_name: govcloud-titan-prefixed
    litellm_params:
      model: bedrock/us-gov-west-1/amazon.titan-embed-text-v2:0
      aws_region_name: us-gov-west-1
      aws_access_key_id: os.environ/AWS_GOVCLOUD_ACCESS_KEY_ID
      aws_secret_access_key: os.environ/AWS_GOVCLOUD_SECRET_ACCESS_KEY
    model_info:
      mode: embedding
  - model_name: govcloud-titan-prefixed-noregion
    litellm_params:
      model: bedrock/us-gov-west-1/amazon.titan-embed-text-v2:0
      aws_access_key_id: os.environ/AWS_GOVCLOUD_ACCESS_KEY_ID
      aws_secret_access_key: os.environ/AWS_GOVCLOUD_SECRET_ACCESS_KEY
    model_info:
      mode: embedding
  - model_name: govcloud-titan-plain
    litellm_params:
      model: bedrock/amazon.titan-embed-text-v2:0
      aws_region_name: us-gov-west-1
      aws_access_key_id: os.environ/AWS_GOVCLOUD_ACCESS_KEY_ID
      aws_secret_access_key: os.environ/AWS_GOVCLOUD_SECRET_ACCESS_KEY
    model_info:
      mode: embedding
  - model_name: govcloud-nova-prefixed
    litellm_params:
      model: bedrock/us-gov-west-1/amazon.nova-2-multimodal-embeddings-v1:0
      aws_region_name: us-gov-west-1
      aws_access_key_id: os.environ/AWS_GOVCLOUD_ACCESS_KEY_ID
      aws_secret_access_key: os.environ/AWS_GOVCLOUD_SECRET_ACCESS_KEY
    model_info:
      mode: embedding
  - model_name: govcloud-nova-plain
    litellm_params:
      model: bedrock/amazon.nova-2-multimodal-embeddings-v1:0
      aws_region_name: us-gov-west-1
      aws_access_key_id: os.environ/AWS_GOVCLOUD_ACCESS_KEY_ID
      aws_secret_access_key: os.environ/AWS_GOVCLOUD_SECRET_ACCESS_KEY
    model_info:
      mode: embedding
  - model_name: commercial-cohere-embed-prefixed
    litellm_params:
      model: bedrock/us-east-1/cohere.embed-english-v3
      api_key: os.environ/BEDROCK_COMMERCIAL_BEARER_TOKEN
    model_info:
      mode: embedding
  - model_name: commercial-mistral-invoke-prefixed
    litellm_params:
      model: bedrock/us-east-1/mistral.mistral-7b-instruct-v0:2
      api_key: os.environ/BEDROCK_COMMERCIAL_BEARER_TOKEN
    model_info:
      mode: chat
  - model_name: commercial-haiku-invoke-prefixed
    litellm_params:
      model: bedrock/invoke/us-east-1/us.anthropic.claude-haiku-4-5-20251001-v1:0
      api_key: os.environ/BEDROCK_COMMERCIAL_BEARER_TOKEN
    model_info:
      mode: chat
  - model_name: commercial-haiku-converse-prefixed
    litellm_params:
      model: bedrock/us-east-1/us.anthropic.claude-haiku-4-5-20251001-v1:0
      api_key: os.environ/BEDROCK_COMMERCIAL_BEARER_TOKEN
    model_info:
      mode: chat
  - model_name: commercial-nova-canvas-prefixed
    litellm_params:
      model: bedrock/us-east-1/amazon.nova-canvas-v1:0
      api_key: os.environ/BEDROCK_COMMERCIAL_BEARER_TOKEN
    model_info:
      mode: image_generation
  - model_name: commercial-rerank-prefixed
    litellm_params:
      model: bedrock/us-east-1/arn:aws:bedrock:us-east-1::foundation-model/cohere.rerank-v3-5:0
      aws_access_key_id: os.environ/AWS_COMMERCIAL_ACCESS_KEY_ID
      aws_secret_access_key: os.environ/AWS_COMMERCIAL_SECRET_ACCESS_KEY
    model_info:
      mode: rerank
  - model_name: commercial-haiku-converse-emptyregion
    litellm_params:
      model: bedrock/us-east-1/us.anthropic.claude-haiku-4-5-20251001-v1:0
      aws_region_name: ""
      api_key: os.environ/BEDROCK_COMMERCIAL_BEARER_TOKEN
    model_info:
      mode: chat
  - model_name: jakarta-deepseek-prefixed
    litellm_params:
      model: bedrock/ap-southeast-3/deepseek.v3.2
      aws_access_key_id: os.environ/AWS_COMMERCIAL_ACCESS_KEY_ID
      aws_secret_access_key: os.environ/AWS_COMMERCIAL_SECRET_ACCESS_KEY
    model_info:
      mode: chat
  - model_name: commercial-haiku-count-sigv4
    litellm_params:
      model: bedrock/us-east-1/us.anthropic.claude-haiku-4-5-20251001-v1:0
      aws_access_key_id: os.environ/AWS_COMMERCIAL_ACCESS_KEY_ID
      aws_secret_access_key: os.environ/AWS_COMMERCIAL_SECRET_ACCESS_KEY
    model_info:
      mode: chat
  - model_name: commercial-canvas-plain
    litellm_params:
      model: bedrock/amazon.nova-canvas-v1:0
      aws_region_name: us-east-1
      api_key: os.environ/BEDROCK_COMMERCIAL_BEARER_TOKEN
    model_info:
      mode: image_generation
  - model_name: commercial-rerank-bare-prefixed
    litellm_params:
      model: bedrock/us-east-1/cohere.rerank-v3-5:0
      aws_access_key_id: os.environ/AWS_COMMERCIAL_ACCESS_KEY_ID
      aws_secret_access_key: os.environ/AWS_COMMERCIAL_SECRET_ACCESS_KEY
    model_info:
      mode: rerank
  - model_name: commercial-rerank-plain
    litellm_params:
      model: bedrock/arn:aws:bedrock:us-east-1::foundation-model/cohere.rerank-v3-5:0
      aws_region_name: us-east-1
      aws_access_key_id: os.environ/AWS_COMMERCIAL_ACCESS_KEY_ID
      aws_secret_access_key: os.environ/AWS_COMMERCIAL_SECRET_ACCESS_KEY
    model_info:
      mode: rerank
  - model_name: profile-only-deepseek-prefixed
    litellm_params:
      model: bedrock/us-east-1/us.deepseek.r1-v1:0
      api_key: os.environ/BEDROCK_COMMERCIAL_BEARER_TOKEN
    model_info:
      mode: chat
  - model_name: profile-only-gpt-luna-prefixed
    litellm_params:
      model: bedrock/us-east-1/us.openai.gpt-5.6-luna
      api_key: os.environ/BEDROCK_COMMERCIAL_BEARER_TOKEN
    model_info:
      mode: chat
general_settings:
  master_key: os.environ/LITELLM_MASTER_KEY

Before (78ad88f)

leg=before commit=78ad88f52c port=46551 proxy_env=AWS_REGION_NAME=us-west-2 at=2026-09-05T11:05:52Z
=== [anthropic messages, region-prefixed id] POST http://127.0.0.1:46551/v1/messages model=commercial-haiku-invoke-prefixed ===
HTTP/1.1 200 OK
body: {"Output": {"__type": "com.amazon.coral.service#UnknownOperationException"}, "Version": "1.0"}
=== [anthropic messages, region-prefixed id] POST http://127.0.0.1:46551/v1/messages model=commercial-haiku-converse-prefixed ===
HTTP/1.1 200 OK
body: {"Output": {"__type": "com.amazon.coral.service#UnknownOperationException"}, "Version": "1.0"}
=== [anthropic count_tokens, region-prefixed id, sigv4 user] POST http://127.0.0.1:46551/v1/messages/count_tokens model=commercial-haiku-count-sigv4 ===
HTTP/1.1 200 OK
body: input_tokens=13
=== [streaming invoke chat, region-prefixed id] POST http://127.0.0.1:46551/v1/chat/completions model=commercial-haiku-invoke-prefixed stream=true ===
HTTP/1.1 500 Internal Server Error
content-type: application/json
body: <no sse chunks> {"error":{"message":"litellm.APIConnectionError: Checksum mismatch: expected 0x223a7b22, calculated 0xb68aca11\nTraceback (most recent call last):\n  File \"<litellm>/litellm/litellm_core_utils/streaming_handler.py\", line 2137, in __anext__\n  
=== [converse chat, empty-string aws_region_name] POST http://127.0.0.1:46551/v1/chat/completions model=commercial-haiku-converse-emptyregion ===
HTTP/1.1 500 Internal Server Error
x-litellm-response-cost: 0
body: {"error": {"message": "litellm.APIConnectionError: Invalid AWS region format: ''. Region names must contain only lowercase letters, digits, and hyphens.", "type": "internal_server_error", "param": null, "code": "500"}}
=== [converse chat, newly recognized region ap-southeast-3] POST http://127.0.0.1:46551/v1/chat/completions model=jakarta-deepseek-prefixed ===
HTTP/1.1 404 Not Found
x-litellm-response-cost: 0
body: {"error": {"message": "litellm.NotFoundError: BedrockException - Bedrock Invoke HTTPX: Unknown provider=None, model=ap-southeast-3/deepseek.v3.2. Try calling via converse route - `bedrock/converse/<model>`.. Received Model Group=jakarta-deepseek-prefixed\nAvailable Model Group Fallbacks=None", "type": "invalid_request_error", "param": null, "code": "404"}}
=== [converse chat, region-prefixed profile-only id (no bare cost row)] POST http://127.0.0.1:46551/v1/chat/completions model=profile-only-deepseek-prefixed ===
HTTP/1.1 200 OK
x-litellm-response-cost: 0.00033885
body: model=profile-only-deepseek-prefixed content='' finish=length reasoning_content=True
=== [converse chat, region-prefixed profile-only id with reasoning_effort] POST http://127.0.0.1:46551/v1/chat/completions model=profile-only-gpt-luna-prefixed ===
HTTP/1.1 200 OK
x-litellm-response-cost: 9.24e-06
body: model=profile-only-gpt-luna-prefixed content='ok' finish=stop reasoning_content=False
=== [image edit, region-prefixed id] POST http://127.0.0.1:46551/v1/images/edits multipart model=commercial-nova-canvas-prefixed image=lit6969_circle.png prompt='make the circle blue' ===
HTTP/1.1 500 Internal Server Error
x-litellm-response-cost: 0
body: {"error": {"message": "litellm.APIConnectionError: BedrockException - Nova Canvas image edit returned no images. Received Model Group=commercial-nova-canvas-prefixed\nAvailable Model Group Fallbacks=None", "type": "internal_server_error", "param": null, "code": "500"}}
=== [image edit, plain id control] POST http://127.0.0.1:46551/v1/images/edits multipart model=commercial-canvas-plain image=lit6969_circle.png prompt='make the circle blue' ===
HTTP/1.1 200 OK
x-litellm-response-cost: 0.06
body: images=1 b64_len=232944
=== [rerank, region-prefixed bare id] POST http://127.0.0.1:46551/v1/rerank model=commercial-rerank-bare-prefixed ===
HTTP/1.1 400 Bad Request
body: {"error": {"message": "litellm.BadRequestError: BedrockException - {\"message\":\"1 validation error detected: Value 'us-east-1/cohere.rerank-v3-5:0' at 'rerankingConfiguration.bedrockRerankingConfiguration.modelConfiguration.modelArn' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:aws(-[^:]+)?:(bedrock|sagemaker):[a-z0-9-]{1,20}:([0-9]{12})?:([a-z-]+/)?)?([a-zA-Z0-9.-]{1,63}){0,2}
=== [rerank, plain arn control] POST http://127.0.0.1:46551/v1/rerank model=commercial-rerank-plain ===
HTTP/1.1 200 OK
x-litellm-response-cost: 0.002
body: model=None results=[(0, 0.875), (1, 0.182)]
=== [model info, newly recognized region] GET http://127.0.0.1:46551/v1/model/info ===
HTTP/1.1 200
model_info: commercial-mistral-invoke-prefixed max_input_tokens=32000 input_cost_per_token=1.5e-07 litellm_provider=bedrock
model_info: jakarta-deepseek-prefixed max_input_tokens=163840 input_cost_per_token=7.4e-07 litellm_provider=bedrock
model_info: profile-only-deepseek-prefixed max_input_tokens=128000 input_cost_per_token=1.35e-06 litellm_provider=bedrock_converse
model_info: profile-only-gpt-luna-prefixed max_input_tokens=1000000 input_cost_per_token=2.2e-07 litellm_provider=bedrock_converse
=== GET http://127.0.0.1:46551/health ===
HTTP/1.1 200
healthy_count=14 unhealthy_count=5
unhealthy: model=amazon.titan-embed-text-v2:0 aws_region_name=unset error=litellm.AuthenticationError: BedrockException Invalid Authentication - {"message":"The security token included in the request is invalid."}
unhealthy: model=amazon.nova-canvas-v1:0 aws_region_name=unset error=litellm.BadRequestError: BedrockException - {"message":"The provided model identifier is invalid."}
unhealthy: model=us.anthropic.claude-haiku-4-5-20251001-v1:0 aws_region_name= error=litellm.APIConnectionError: Invalid AWS region format: ''. Region names must contain only lowercase letters, digits, and hyphens.
unhealthy: model=ap-southeast-3/deepseek.v3.2 aws_region_name=unset error=litellm.NotFoundError: BedrockException - Bedrock Invoke HTTPX: Unknown provider=None, model=ap-southeast-3/deepseek.v3.2. Try calling via converse route - `bed
unhealthy: model=cohere.rerank-v3-5:0 aws_region_name=unset error=litellm.BadRequestError: BedrockException - {"message":"The provided model ARN for reranking is invalid. Please, use a valid model ARN. Also check the model ARN
=== proxy log: count-tokens upstream calls ===
   1 upstream: https://bedrock-runtime.us-west-2.amazonaws.com/model/anthropic.claude-haiku-4-5-20251001-v1%3A0/count-tokens
=== proxy log: upstream Bedrock routes for the profile-only ids ===
   1 upstream: https://bedrock-runtime.us-east-1.amazonaws.com/model/us.deepseek.r1-v1%3A0/converse
   1 upstream: https://bedrock-runtime.us-east-1.amazonaws.com/model/us.openai.gpt-5.6-luna/converse
   1 upstream: https://bedrock-runtime.us-west-2.amazonaws.com/model/us.deepseek.r1-v1%3A0/converse
   1 upstream: https://bedrock-runtime.us-west-2.amazonaws.com/model/us.openai.gpt-5.6-luna/converse

After (18a8c6b)

leg=after commit=18a8c6b2e0 port=51671 proxy_env=AWS_REGION_NAME=us-west-2 at=2026-09-05T11:07:14Z
=== [anthropic messages, region-prefixed id] POST http://127.0.0.1:51671/v1/messages model=commercial-haiku-invoke-prefixed ===
HTTP/1.1 200 OK
x-litellm-response-cost: 3.3e-05
body: model=commercial-haiku-invoke-prefixed content='ok' stop=end_turn usage={'input_tokens': 13, 'cache_creation_input_tokens': 0, 'cache_read_input_tokens': 0, 'cache_creation': {'ephemeral_5m_input_tokens': 0, 'ephemeral_1h_input_tokens': 0}, 'output_tokens': 4, 'service_tier': 'standard'}
=== [anthropic messages, region-prefixed id] POST http://127.0.0.1:51671/v1/messages model=commercial-haiku-converse-prefixed ===
HTTP/1.1 200 OK
x-litellm-response-cost: 3.3e-05
body: model=commercial-haiku-converse-prefixed content='ok' stop=end_turn usage={'input_tokens': 13, 'cache_creation_input_tokens': 0, 'cache_read_input_tokens': 0, 'cache_creation': {'ephemeral_5m_input_tokens': 0, 'ephemeral_1h_input_tokens': 0}, 'output_tokens': 4, 'service_tier': 'standard'}
=== [anthropic count_tokens, region-prefixed id, sigv4 user] POST http://127.0.0.1:51671/v1/messages/count_tokens model=commercial-haiku-count-sigv4 ===
HTTP/1.1 200 OK
body: input_tokens=29
=== [streaming invoke chat, region-prefixed id] POST http://127.0.0.1:51671/v1/chat/completions model=commercial-haiku-invoke-prefixed stream=true ===
HTTP/1.1 200 OK
content-type: text/event-stream; charset=utf-8
stream: chunks=2 model=commercial-haiku-invoke-prefixed content='ok' finish=['stop']
=== [converse chat, empty-string aws_region_name] POST http://127.0.0.1:51671/v1/chat/completions model=commercial-haiku-converse-emptyregion ===
HTTP/1.1 200 OK
x-litellm-response-cost: 3.63e-05
body: model=commercial-haiku-converse-emptyregion content='ok' finish=stop reasoning_content=False
=== [converse chat, newly recognized region ap-southeast-3] POST http://127.0.0.1:51671/v1/chat/completions model=jakarta-deepseek-prefixed ===
HTTP/1.1 200 OK
x-litellm-response-cost: 1.184e-05
body: model=jakarta-deepseek-prefixed content='ok' finish=stop reasoning_content=False
=== [converse chat, region-prefixed profile-only id (no bare cost row)] POST http://127.0.0.1:51671/v1/chat/completions model=profile-only-deepseek-prefixed ===
HTTP/1.1 200 OK
x-litellm-response-cost: 0.00033885
body: model=profile-only-deepseek-prefixed content='' finish=length reasoning_content=True
=== [converse chat, region-prefixed profile-only id with reasoning_effort] POST http://127.0.0.1:51671/v1/chat/completions model=profile-only-gpt-luna-prefixed ===
HTTP/1.1 200 OK
x-litellm-response-cost: 9.24e-06
body: model=profile-only-gpt-luna-prefixed content='ok' finish=stop reasoning_content=False
=== [image edit, region-prefixed id] POST http://127.0.0.1:51671/v1/images/edits multipart model=commercial-nova-canvas-prefixed image=lit6969_circle.png prompt='make the circle blue' ===
HTTP/1.1 200 OK
x-litellm-response-cost: 0.06
body: images=1 b64_len=232944
=== [image edit, plain id control] POST http://127.0.0.1:51671/v1/images/edits multipart model=commercial-canvas-plain image=lit6969_circle.png prompt='make the circle blue' ===
HTTP/1.1 200 OK
x-litellm-response-cost: 0.06
body: images=1 b64_len=232944
=== [rerank, region-prefixed bare id] POST http://127.0.0.1:51671/v1/rerank model=commercial-rerank-bare-prefixed ===
HTTP/1.1 400 Bad Request
body: {"error": {"message": "litellm.BadRequestError: BedrockException - {\"message\":\"The provided model ARN for reranking is invalid. Please, use a valid model ARN. Also check the model ARN follows this format: arn:<partition>:bedrock:<region>::foundation-model/<model-id>\"}. Received Model Group=commercial-rerank-bare-prefixed\nAvailable Model Group Fallbacks=None", "type": null, "param": null, "code": "400"}}
=== [rerank, plain arn control] POST http://127.0.0.1:51671/v1/rerank model=commercial-rerank-plain ===
HTTP/1.1 200 OK
x-litellm-response-cost: 0.002
body: model=None results=[(0, 0.875), (1, 0.182)]
=== [model info, newly recognized region] GET http://127.0.0.1:51671/v1/model/info ===
HTTP/1.1 200
model_info: commercial-mistral-invoke-prefixed max_input_tokens=32000 input_cost_per_token=1.5e-07 litellm_provider=bedrock
model_info: jakarta-deepseek-prefixed max_input_tokens=163840 input_cost_per_token=7.4e-07 litellm_provider=bedrock
model_info: profile-only-deepseek-prefixed max_input_tokens=128000 input_cost_per_token=1.35e-06 litellm_provider=bedrock_converse
model_info: profile-only-gpt-luna-prefixed max_input_tokens=1000000 input_cost_per_token=2.2e-07 litellm_provider=bedrock_converse
=== GET http://127.0.0.1:51671/health ===
HTTP/1.1 200
healthy_count=18 unhealthy_count=1
unhealthy: model=cohere.rerank-v3-5:0 aws_region_name=us-east-1 error=litellm.BadRequestError: BedrockException - {"message":"The provided model ARN for reranking is invalid. Please, use a valid model ARN. Also check the model ARN
=== proxy log: count-tokens upstream calls ===
   1 upstream: https://bedrock-runtime.us-east-1.amazonaws.com/model/anthropic.claude-haiku-4-5-20251001-v1%3A0/count-tokens
=== proxy log: upstream Bedrock routes for the profile-only ids ===
   2 upstream: https://bedrock-runtime.us-east-1.amazonaws.com/model/us.deepseek.r1-v1%3A0/converse
   2 upstream: https://bedrock-runtime.us-east-1.amazonaws.com/model/us.openai.gpt-5.6-luna/converse

Type

🐛 Bug Fix

Caveats (if any)

Four adversarial caveat rounds ran on this PR (78ad88f-era tip, pre-4de94a0460, 4de94a0, dfac628). Round 4 still raised a High and a Medium, so the loop stopped at its four-round cap without converging; both are fixed at 18a8c6b and stay listed here with the evidence

High

  • Region-prefixed ids whose only cost-map row is a us./eu./global. profile row lost their converse route (raised at dfac628, fixed at 18a8c6b)
    • get_bedrock_base_model strips the region and then the cross-region prefix, so bedrock/us-east-1/us.deepseek.r1-v1:0 resolved to deepseek.r1-v1:0, which has no row; route detection, cache points, parallel tool use, Claude 4.5 detection, and the model budget limiter all missed and the call fell to the invoke route (Unknown provider=None) or sent cachePoint blocks to a model whose row says no
    • Fix: bedrock_model_lookup_candidates walks the full id, the routing-free id, the region-free profile id, and the base in that order, and every one of those lookups plus _bedrock_candidates in the budget limiter walks it too. Regressions in test_bedrock_common_utils.py and test_unit_test_max_model_budget_limiter.py fail at dfac628 (5 failed) and pass at 18a8c6b; the rig below runs bedrock/us-east-1/us.deepseek.r1-v1:0 and bedrock/us-east-1/us.openai.gpt-5.6-luna on the /converse URL at both commits

Medium

  • Seven User Flow results had no pasted proof at either commit (raised at dfac628, fixed in this body)
    • Streaming invoke chat, /v1/messages, count_tokens, the aws_region_name: "" copy, bedrock/ap-southeast-3/deepseek.v3.2, /v1/images/edits, and /v1/model/info were claimed from the live-pr-risk run without pasting it. The rig's Before and After output is now pasted verbatim under "Every other Bedrock surface"

Low

  • Region for the call and region for the price can disagree
    • When aws_region_name names a different region than the id, the call goes to aws_region_name but response.model keeps the id's prefix, so pricing comes from the id's row. Same design as chat; a us-gov-west-1 id sent to commercial us-east-1 bills the 10x GovCloud rate
  • Docs never mention the bedrock/<region>/<model> shape for embeddings
    • Only the cost map keys document it; bedrock.md and bedrock_embedding.md in litellm-docs have no region-prefixed example
  • Region recognition is still list-based
    • A region AWS adds after this PR does not split until AmazonBedrockGlobalConfig learns it, the same rule get_bedrock_base_model and the health check already follow; a pattern-based split is a separate change with its own false-positive risk (us/... style ids)
  • split_bedrock_region_prefix returns a tuple, not a named shape
    • Two fields, every caller unpacks it in place; a dataclass would add a type for no reader gain
  • Spend logs keep the region-prefixed id as the model
    • Same as chat today, and it is what makes the cost lookup hit the region's row; normalizing the logged model would change the billed row
  • The region inside an ARN now outranks a region prefix on the converse route too
    • Every Bedrock call resolves the region the same way: aws_region_name, then the ARN region (model_id or the model), then the prefix, then env. At base the converse handler let the prefix beat a model_id ARN; a config that names two different regions there now signs for the ARN's region, the one AWS accepts
  • Batches, realtime, and the OpenAI-compatible invoke route are covered by unit regressions only
    • They now send the bare id to AWS the same way the surfaces in the rig below do, and their mapped test files lock that in; no live batch job, realtime session, or OpenAI-compatible invoke call is pasted
  • eusc-de-east-1 is left out of the region list
    • The European Sovereign Cloud partition has no Bedrock pricing rows yet; mx-central-1 was dropped because Bedrock does not run there
  • bedrock/<region>/openai/<model> still sends openai/ in the model id
    • _get_openai_model_id strips bedrock/, then openai/, then the region, so the region has to come after openai/ (bedrock/openai/us-east-1/<model> works). Same at base; openai/ is a routing prefix that sits right after bedrock/ in every documented shape, so reordering that stripper is its own change
  • Mantle routes keep a region prefix in the body model id
    • bedrock/mantle/<region>/<model> posts <region>/<model>, same as base. Mantle calls a user-supplied endpoint, so a region prefix has no meaning there and no documented shape carries one

Final 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

bedrock/<region>/<embedding model> failed every embedding request: Titan
answered 500 "Unable to map Bedrock request to provider" and Nova or
Cohere sent the prefixed id to AWS and got a 400 back, while Bedrock chat
models already accept the same shape. Split the region off the model id
with one shared helper, use the bare id for provider detection, the
request body, and the invoke URL, let the region in the id set the
request region unless aws_region_name is passed, and keep the prefixed
id on the response so cost lookup hits the region-specific row. The chat
handler now uses the same helper instead of its inline strip
@mateo-berri
mateo-berri requested a review from a team September 5, 2026 05:33
@codspeed

codspeed Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 31 untouched benchmarks


Comparing litellm_lit_6969_bedrock_embed_region (18a8c6b) with litellm_internal_staging (c52b537)

Open in CodSpeed

@greptile-apps

greptile-apps Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR standardizes parsing and resolution of region-prefixed Bedrock model identifiers across Bedrock request surfaces.

  • Introduces shared helpers for separating region and routing prefixes from Bedrock model IDs.
  • Applies consistent region precedence and bare model IDs to invoke, converse, embeddings, batches, token counting, realtime, rerank, health checks, routing, pricing, and budget lookup.
  • Expands the recognized Bedrock region set and adds regression coverage for region-prefixed and cross-region-profile models.

Confidence Score: 4/5

The behavioral fix appears sound, but the outstanding repository requirement to keep provider-specific logic under llms/ must be satisfied before merging.

The unresolved previous thread remains applicable because model_max_budget_limiter.py directly imports and applies Bedrock model-resolution logic, while health_check.py parses Bedrock path regions and sets Bedrock-specific request parameters outside llms/. The other four previous findings were manually resolved without explanatory replies and do not affect merge safety.

Files Needing Attention: litellm/proxy/hooks/model_max_budget_limiter.py, litellm/proxy/health_check.py

Important Files Changed

Filename Overview
litellm/llms/bedrock/common_utils.py Centralizes region-prefix parsing and model lookup candidates while expanding recognized Bedrock regions.
litellm/llms/bedrock/base_aws_llm.py Resolves explicit, ARN, model-prefix, and environment regions consistently and removes prefixes from AWS model IDs.
litellm/llms/bedrock/embed/embedding.py Uses bare model IDs for provider detection and requests while preserving prefixed IDs for response pricing.
litellm/llms/bedrock/chat/converse_handler.py Replaces inline region parsing with the shared Bedrock helper.
litellm/proxy/health_check.py Carries model-path regions into health-check parameters, but remains part of the outstanding provider-boundary rule finding.
litellm/proxy/hooks/model_max_budget_limiter.py Adds Bedrock-specific lookup behavior outside llms/, which remains an outstanding repository-rule violation.
litellm/utils.py Removes Bedrock region prefixes when constructing model-name lookup candidates.
tests/test_litellm/llms/bedrock/test_bedrock_common_utils.py Adds regression coverage for region parsing, profile lookup candidates, routing, and region-list completeness.

Reviews (6): Last reviewed commit: "fix(bedrock): keep region-prefixed profi..." | Re-trigger Greptile

Comment thread litellm/llms/bedrock/common_utils.py
Comment thread litellm/llms/bedrock/embed/embedding.py Outdated
@codecov

codecov Bot commented Sep 5, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.73418% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
litellm/llms/bedrock/embed/embedding.py 94.44% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

…alth checks

The region prefix helper only knew 23 Bedrock regions, so ids like
bedrock/ap-southeast-3/<model> never split and still failed. Add the
12 regions the cost map already prices and a test that keeps the list
in step with the cost map. The combined list is now an immutable tuple.

The proxy health check stripped the region from the model id and then
probed the env or default region. It now carries that region into
aws_region_name unless the deployment already sets one.

Drive the converse region tests through litellm.completion so a wiring
regression in the shared helper fails them, and drop a stray comment.
@mateo-berri

Copy link
Copy Markdown
Contributor Author

@greptileai

Comment thread tests/test_litellm/llms/bedrock/embed/test_bedrock_embedding.py Outdated
The region prefix fix covered converse and embeddings only. Invoke chat
(bedrock/us-east-1/mistral..., bedrock/invoke/us-east-1/...), image
generation, and rerank still posted model/us-east-1/<id>/invoke to the
env region, which AWS rejects, while /health, which strips the prefix,
reported those deployments healthy. BaseAWSLLM._get_aws_region_name now
reads the region out of the model id ahead of ARN and env fallbacks,
get_bedrock_model_id and get_bedrock_invoke_provider drop the prefix,
and rerank sends the bare ARN as modelArn, so every Bedrock surface
that resolves a region through the shared base sees the prefix

The converse handler now also lets the id's region stand in for an
empty aws_region_name, matching embeddings, instead of signing for ""

Type the region-prefix test helpers (Greptile P2): an optional
aws_region_name and a Mapping for the canned response
@mateo-berri

Copy link
Copy Markdown
Contributor Author

@greptileai

Comment thread tests/test_litellm/llms/bedrock/rerank/test_bedrock_rerank_handler.py Outdated
… surface

One region precedence for every call: aws_region_name, then the ARN region
(model_id or model), then the region prefix in the model id, then env, with an
empty aws_region_name treated as unset. The converse handler stops injecting the
prefix region itself, the invoke config inherits the BaseAWSLLM provider resolver
and transforms with the bare model, and count_tokens, batch jobs, realtime
sessions, and the OpenAI-compatible invoke route send the bare id to AWS.

get_bedrock_base_model strips the routing prefix, the region prefix, the ARN, the
throughput suffix, and the cross-region prefix in one pass, and the cost lookup
keeps the us./eu. profile row for a region-prefixed profile id. mx-central-1
leaves the region list because Bedrock does not run there.
@mateo-berri

Copy link
Copy Markdown
Contributor Author

@greptileai

@mateo-berri mateo-berri changed the title fix(bedrock): accept region-prefixed embedding model ids fix(bedrock): resolve the region prefix the same way on every Bedrock surface Sep 5, 2026
…oute

Route detection, cache points, parallel tool use, Claude 4.5 detection, and
the model budget limiter now look a Bedrock id up as the full id, the
routing-free id, the region-free profile id, and the base in that order, so
bedrock/<region>/us.<model> whose only cost-map row is the profile row keeps
its converse route and its capability row instead of falling to invoke
@mateo-berri

Copy link
Copy Markdown
Contributor Author

@greptileai

Comment on lines +162 to +174
base_model: Final = next(
(candidate for candidate in reversed(bedrock_model_lookup_candidates(model)) if _priced_as_bedrock(candidate)),
None,
)
if base_model is None:
return ()
_, _, without_vendor = base_model.partition(".")
return (base_model, without_vendor) if without_vendor else (base_model,)


def _priced_as_bedrock(model: str) -> bool:
cost_entry: Final = litellm.model_cost.get(model)
return isinstance(cost_entry, dict) and str(cost_entry.get("litellm_provider", "")).startswith("bedrock")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Provider Logic Outside llms

This proxy hook now resolves and identifies Bedrock models directly. The same pattern appears in health_check.py, which parses Bedrock regions and sets Bedrock request parameters. This violates the repository directive to keep provider-specific code under llms/, so the requirement must be satisfied before merging

Rule Used: What: Avoid writing provider-specific code outside... (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@yuneng-berri
yuneng-berri deleted the branch main September 13, 2026 04:49
@mateo-berri mateo-berri reopened this Sep 13, 2026
@mateo-berri
mateo-berri changed the base branch from litellm_internal_staging to main September 13, 2026 05:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants