fix(bedrock): read invoke capabilities from base_model for an ARN that names no model and map Bedrock's 200 error body to a 404

This commit is contained in:
mateo-berri 2026-10-30 07:27:31 -07:00
parent e11c0d9a9c
commit e4cebb0fef
10 changed files with 208 additions and 105 deletions

View file

@ -622,6 +622,10 @@ class BaseAWSLLM(SignsRequestsWithAWS):
if model.startswith("invoke/"):
model = model.replace("invoke/", "", 1)
spec_provider: Final = BaseAWSLLM._get_provider_from_model_path(model)
if spec_provider is not None:
return spec_provider
# Special case: Check for "nova" in model name first (before "amazon")
# This handles amazon.nova-* models which would otherwise match "amazon" (Titan)
if "nova" in model.lower():
@ -632,11 +636,6 @@ class BaseAWSLLM(SignsRequestsWithAWS):
if _split_model in get_args(BEDROCK_INVOKE_PROVIDERS_LITERAL):
return cast(BEDROCK_INVOKE_PROVIDERS_LITERAL, _split_model)
# If not a known provider, check for pattern with two slashes
provider = BaseAWSLLM._get_provider_from_model_path(model)
if provider is not None:
return provider
invoke_providers: Final[tuple[BEDROCK_INVOKE_PROVIDERS_LITERAL, ...]] = get_args(
BEDROCK_INVOKE_PROVIDERS_LITERAL
)
@ -669,7 +668,7 @@ class BaseAWSLLM(SignsRequestsWithAWS):
# like "bedrock/invoke/arn:..." are fully stripped in one call.
from litellm.llms.bedrock.common_utils import strip_bedrock_routing_prefix
model_id = BaseAWSLLM._without_invoke_provider_spec(strip_bedrock_routing_prefix(model_id))
model_id = BaseAWSLLM.without_provider_spec(strip_bedrock_routing_prefix(model_id))
# URL-encode ARNs so colons and slashes are safe in the URL path.
if model_id.startswith("arn:"):
model_id = BaseAWSLLM.encode_model_id(model_id=model_id)
@ -697,7 +696,7 @@ class BaseAWSLLM(SignsRequestsWithAWS):
return model_id
@staticmethod
def _without_invoke_provider_spec(model_id: str) -> str:
def without_provider_spec(model_id: str) -> str:
_, _, arn = model_id.partition("/")
return arn if arn.startswith("arn:") else model_id

View file

@ -22,7 +22,13 @@ from litellm.types.utils import ModelResponse
from litellm.utils import CustomStreamWrapper
from ..base_aws_llm import BaseAWSLLM, Credentials, bedrock_bearer_token, pop_aws_auth_params, run_aws_signing
from ..common_utils import BedrockError, error_response_text, get_all_bedrock_regions
from ..common_utils import (
BedrockError,
bedrock_answered_json,
bedrock_error_in_success_body,
error_response_text,
get_all_bedrock_regions,
)
from .invoke_handler import AWSEventStreamDecoder, make_call
@ -68,6 +74,12 @@ def make_sync_call(
response=response,
)
if bedrock_answered_json(response):
response.read()
success_body_error: Final = bedrock_error_in_success_body(response)
if success_body_error is not None:
raise success_body_error
if fake_stream:
model_response: Final[ModelResponse] = litellm.AmazonConverseConfig()._transform_response(
model=model,
@ -326,7 +338,7 @@ class BedrockConverseLLM(BaseAWSLLM):
if _stripped.startswith(_nova_prefix):
_model_for_id = _model_for_id.replace(_nova_prefix, "", 1)
break
modelId = self.encode_model_id(model_id=_model_for_id)
modelId = self.encode_model_id(model_id=self.without_provider_spec(_model_for_id))
# Inject region extracted from model path so _get_aws_region_name picks it up
if _region_from_model is not None and "aws_region_name" not in optional_params:
optional_params["aws_region_name"] = _region_from_model

View file

@ -128,6 +128,8 @@ from ..common_utils import (
bedrock_model_accepts_cache_points,
bedrock_reasoning_effort_disabled,
bedrock_rejects_stop_sequences,
bedrock_service_error,
bedrock_service_error_type,
get_anthropic_beta_from_headers,
get_bedrock_tool_name,
is_bedrock_application_inference_profile_arn,
@ -2747,13 +2749,17 @@ class AmazonConverseConfig(BaseConfig):
)
## RESPONSE OBJECT
try:
completion_response: Final = ConverseResponseBlock(**response.json())
response_json: Final = response.json()
completion_response: Final = ConverseResponseBlock(**response_json)
except Exception as e:
raise BedrockError(
message=f"Error converting to valid response block={e}. File an issue if litellm error - https://github.com/BerriAI/litellm/issues",
status_code=422,
headers=response.headers,
)
service_error_type: Final = bedrock_service_error_type(response_json)
if service_error_type is not None:
raise bedrock_service_error(service_error_type, response.headers)
"""
Bedrock Response Object has optional message block

View file

@ -26,6 +26,7 @@ from litellm.llms.bedrock.chat.invoke_transformations.base_invoke_transformation
from litellm.llms.bedrock.common_utils import (
BEDROCK_INVOKE_TRACE_FIELD,
apply_bedrock_invoke_structured_output,
bedrock_capability_model,
bedrock_chat_uses_native_structured_output,
bedrock_cleaned_output_schema,
bedrock_error_in_success_body,
@ -193,8 +194,11 @@ class AmazonAnthropicClaudeConfig(AmazonInvokeConfig, AnthropicConfig):
litellm_params: dict,
headers: dict,
) -> dict:
capability_model: Final = bedrock_capability_model(
model, _LITELLM_PARAMS.validate_python(litellm_params).get("base_model")
)
_anthropic_request: Final = self._build_bedrock_anthropic_request_base(
model=model,
model=capability_model,
messages=messages,
optional_params=optional_params,
litellm_params=litellm_params,
@ -203,7 +207,7 @@ class AmazonAnthropicClaudeConfig(AmazonInvokeConfig, AnthropicConfig):
self._convert_document_url_sources_to_base64(_anthropic_request)
beta_list: Final = self._compute_bedrock_invoke_beta_headers(
model=model,
model=capability_model,
messages=messages,
optional_params=optional_params,
headers=headers,
@ -217,7 +221,7 @@ class AmazonAnthropicClaudeConfig(AmazonInvokeConfig, AnthropicConfig):
_anthropic_request, # pyright: ignore[reportUnknownArgumentType] # built by the base signature's bare dict transform
{},
drop_params=litellm_params.get("drop_params"), # pyright: ignore[reportUnknownMemberType, reportUnknownArgumentType] # litellm_params is the base signature's bare dict
model=model,
model=capability_model,
provider="bedrock_invoke",
)
)

View file

@ -2,7 +2,7 @@ import copy
import json
import time
from collections.abc import Mapping, Sequence
from typing import TYPE_CHECKING, Any, Final, cast, get_args
from typing import TYPE_CHECKING, Any, Final
import httpx
from pydantic import ConfigDict, TypeAdapter, ValidationError, with_config
@ -74,10 +74,6 @@ _GUARDRAIL_CONFIG_EXPECTED_FORMAT: Final = (
"{'guardrailIdentifier': str, 'guardrailVersion': str, 'trace': 'enabled'|'disabled'|'enabled_full'}"
)
_INVOKE_PROVIDERS: Final = TypeAdapter(tuple[litellm.BEDROCK_INVOKE_PROVIDERS_LITERAL, ...]).validate_python(
get_args(litellm.BEDROCK_INVOKE_PROVIDERS_LITERAL)
)
def _response_header(headers: Mapping[str, str], name: str) -> str | None:
return headers.get(name, None)
@ -562,62 +558,6 @@ class AmazonInvokeConfig(BaseConfig, BaseAWSLLM):
"""
return False
@staticmethod
def get_bedrock_invoke_provider(
model: str,
) -> litellm.BEDROCK_INVOKE_PROVIDERS_LITERAL | None:
"""
Helper function to get the bedrock provider from the model
handles 4 scenarios:
1. model=invoke/anthropic.claude-3-5-sonnet-20240620-v1:0 -> Returns `anthropic`
2. model=anthropic.claude-3-5-sonnet-20240620-v1:0 -> Returns `anthropic`
3. model=llama/arn:aws:bedrock:us-east-1:086734376398:imported-model/r4c4kewx2s0n -> Returns `llama`
4. model=us.amazon.nova-pro-v1:0 -> Returns `nova`
"""
if model.startswith("invoke/"):
model = model.replace("invoke/", "", 1)
# Special case: Check for "nova" in model name first (before "amazon")
# This handles amazon.nova-* models which would otherwise match "amazon" (Titan)
if "nova" in model.lower():
if "nova" in get_args(litellm.BEDROCK_INVOKE_PROVIDERS_LITERAL):
return cast(litellm.BEDROCK_INVOKE_PROVIDERS_LITERAL, "nova")
_split_model: Final = model.split(".")[0]
if _split_model in get_args(litellm.BEDROCK_INVOKE_PROVIDERS_LITERAL):
return cast(litellm.BEDROCK_INVOKE_PROVIDERS_LITERAL, _split_model)
# If not a known provider, check for pattern with two slashes
provider = AmazonInvokeConfig._get_provider_from_model_path(model)
if provider is not None:
return provider
for provider in _INVOKE_PROVIDERS:
if provider in model:
return provider
return None
@staticmethod
def _get_provider_from_model_path(
model_path: str,
) -> litellm.BEDROCK_INVOKE_PROVIDERS_LITERAL | None:
"""
Helper function to get the provider from a model path with format: provider/model-name
Args:
model_path (str): The model path (e.g., 'llama/arn:aws:bedrock:us-east-1:086734376398:imported-model/r4c4kewx2s0n' or 'anthropic/model-name')
Returns:
Optional[str]: The provider name, or None if no valid provider found
"""
parts: Final = model_path.split("/")
if len(parts) >= 1:
provider: Final = parts[0]
if provider in get_args(litellm.BEDROCK_INVOKE_PROVIDERS_LITERAL):
return cast(litellm.BEDROCK_INVOKE_PROVIDERS_LITERAL, provider)
return None
def convert_messages_to_prompt(self, model: str, messages, provider, custom_prompt_dict) -> tuple[str, list | None]:
# handle anthropic prompts and amazon titan prompts
prompt = ""

View file

@ -130,6 +130,17 @@ class _BedrockServiceErrorBody(BaseModel):
Output: _BedrockServiceErrorOutput
def bedrock_service_error(error_type: str, headers: httpx.Headers) -> BedrockError:
return BedrockError(
status_code=404,
message=(
f"Bedrock answered HTTP 200 with the error {error_type}, which usually means the model id in the "
"model string does not name a Bedrock model or profile this route can call."
),
headers=headers,
)
def bedrock_error_in_success_body(response: httpx.Response) -> BedrockError | None:
if b'"__type"' not in response.content:
return None
@ -137,14 +148,16 @@ def bedrock_error_in_success_body(response: httpx.Response) -> BedrockError | No
error_type: Final = _BedrockServiceErrorBody.model_validate_json(response.content).Output.error_type
except ValidationError:
return None
return BedrockError(
status_code=400,
message=(
f"Bedrock answered HTTP 200 with the error {error_type}, which usually means the model id in the "
"model string does not name a Bedrock model or profile this route can call."
),
headers=response.headers,
)
return bedrock_service_error(error_type, response.headers)
def bedrock_service_error_type(body: object) -> str | None:
if not isinstance(body, dict) or "Output" not in body:
return None
try:
return _BedrockServiceErrorBody.model_validate(body).Output.error_type
except ValidationError:
return None
def bedrock_answered_json(response: httpx.Response) -> bool:
@ -1098,10 +1111,23 @@ def is_bedrock_application_inference_profile_arn(model: str) -> bool:
return ":application-inference-profile/" in model
_BEDROCK_ARN_RESOURCES_NAMING_NO_MODEL: Final = (
":application-inference-profile/",
":provisioned-model/",
":imported-model/",
":custom-model-deployment/",
)
def bedrock_arn_names_no_model(model: str) -> bool:
return any(resource in model for resource in _BEDROCK_ARN_RESOURCES_NAMING_NO_MODEL)
def bedrock_capability_model(model: str, base_model: object) -> str:
"""The model id to look capabilities up by: the deployment's ``base_model`` for an
application inference profile ARN, since the ARN names no model, else ``model``."""
if isinstance(base_model, str) and base_model and is_bedrock_application_inference_profile_arn(model):
ARN whose resource id names no model (an application inference profile, a provisioned
or imported model, a custom model deployment), else ``model``."""
if isinstance(base_model, str) and base_model and bedrock_arn_names_no_model(model):
return strip_bedrock_routing_prefix(base_model)
return model
@ -1114,10 +1140,13 @@ def invoke_model_with_base_model_provider(model: str, base_model: object) -> str
not isinstance(base_model, str)
or not model_id.startswith("arn:")
or BedrockModelInfo.get_bedrock_route(model) != "invoke"
or BaseAWSLLM.get_bedrock_invoke_provider(model) is not None
or (not bedrock_arn_names_no_model(model_id) and BaseAWSLLM.get_bedrock_invoke_provider(model) is not None)
):
return model
provider: Final = BaseAWSLLM.get_bedrock_invoke_provider(strip_bedrock_routing_prefix(base_model))
base_model_id: Final = strip_bedrock_routing_prefix(base_model)
provider: Final = BaseAWSLLM.get_bedrock_invoke_provider(base_model_id) or (
"anthropic" if "claude" in base_model_id.lower() else None
)
return model if provider is None else f"invoke/{provider}/{model_id}"

View file

@ -5600,13 +5600,14 @@ def get_optional_params(
drop_params=bool(drop_params),
)
elif custom_llm_provider == "bedrock":
from litellm.llms.bedrock.common_utils import bedrock_capability_model
bedrock_model_info: Final[type[BedrockModelInfo]] = litellm_utils.BedrockModelInfo
bedrock_base_model: Final = bedrock_model_info.get_base_model(model)
capability_model: Final = bedrock_capability_model(model, base_model)
if bedrock_route == "converse" or bedrock_route == "converse_like":
from litellm.llms.bedrock.common_utils import bedrock_capability_model
optional_params = litellm.AmazonConverseConfig().map_openai_params(
model=bedrock_capability_model(model, base_model),
model=capability_model,
non_default_params=non_default_params,
optional_params=optional_params,
drop_params=bool(drop_params),
@ -5631,14 +5632,14 @@ def get_optional_params(
optional_params = litellm.AmazonAnthropicClaudeConfig(model_info=model_info).map_openai_params(
non_default_params=non_default_params,
optional_params=optional_params,
model=model,
model=capability_model,
drop_params=bool(drop_params),
)
elif provider_config is not None:
optional_params = provider_config.map_openai_params(
non_default_params=non_default_params,
optional_params=optional_params,
model=model,
model=capability_model,
drop_params=bool(drop_params),
)
if bedrock_route == "claude_platform":

View file

@ -1987,6 +1987,7 @@ def test_invoke_stream_decoder_carries_the_guardrail_trace_of_the_message_stop_c
_ARN_ACCOUNT: Final = "arn:aws:bedrock:us-east-1:123456789012"
_APPLICATION_PROFILE_ARN: Final = f"{_ARN_ACCOUNT}:application-inference-profile/a1b2c3d4e5f6"
_NOVA_LOOKING_PROFILE_ARN: Final = f"{_ARN_ACCOUNT}:application-inference-profile/nova1meta2ab"
_CLAUDE_INVOKE_ANSWER: Final = json.dumps(
{
"id": "msg_1",
@ -2079,6 +2080,12 @@ def _answer_text(response: object) -> str:
{"model_info": {"base_model": "us.anthropic.claude-haiku-5-5"}},
id="model-info-base-model",
),
pytest.param(
f"bedrock/invoke/{_APPLICATION_PROFILE_ARN}",
_APPLICATION_PROFILE_ARN,
{"base_model": "claude-haiku-5-5"},
id="claude-base-model-without-a-bedrock-prefix",
),
pytest.param(
f"bedrock/invoke/{_ARN_ACCOUNT}:custom-model/anthropic.claude-haiku-5-5/a1b2c3d4e5f6",
f"{_ARN_ACCOUNT}:custom-model/anthropic.claude-haiku-5-5/a1b2c3d4e5f6",
@ -2091,6 +2098,18 @@ def _answer_text(response: object) -> str:
{},
id="custom-model-under-unlisted-spec",
),
pytest.param(
f"bedrock/invoke/anthropic/{_NOVA_LOOKING_PROFILE_ARN}",
_NOVA_LOOKING_PROFILE_ARN,
{},
id="spec-over-provider-name-in-profile-id",
),
pytest.param(
f"bedrock/invoke/{_NOVA_LOOKING_PROFILE_ARN}",
_NOVA_LOOKING_PROFILE_ARN,
{"base_model": "bedrock/us.anthropic.claude-haiku-5-5"},
id="base-model-over-provider-name-in-profile-id",
),
],
)
def test_an_arn_in_the_invoke_model_string_reaches_bedrock_as_the_encoded_model_id(
@ -2147,8 +2166,8 @@ def _unknown_operation_upstream() -> httpx.MockTransport:
)
def _assert_unknown_operation_is_a_bad_request(error: litellm.BadRequestError) -> None:
assert error.status_code == 400
def _assert_unknown_operation_is_not_found(error: litellm.NotFoundError) -> None:
assert error.status_code == 404
assert "com.amazon.coral.service#UnknownOperationException" in error.message
@ -2159,10 +2178,14 @@ def _assert_unknown_operation_is_a_bad_request(error: litellm.BadRequestError) -
"bedrock/invoke/anthropic.claude-haiku-5-5",
"bedrock/invoke/mistral.mistral-large-2407-v1:0",
"bedrock/invoke/cohere.command-r-plus-v1:0",
"bedrock/invoke/us.amazon.nova-pro-v1:0",
"bedrock/converse/us.anthropic.claude-haiku-5-5",
],
)
def test_a_200_unknown_operation_body_is_a_bad_request(_aws_test_credentials: None, model: str, stream: bool) -> None:
with pytest.raises(litellm.BadRequestError) as exc_info:
def test_a_200_unknown_operation_body_is_a_not_found_error(
_aws_test_credentials: None, model: str, stream: bool
) -> None:
with pytest.raises(litellm.NotFoundError) as exc_info:
_answer_text(
litellm.completion(
model=model,
@ -2173,7 +2196,7 @@ def test_a_200_unknown_operation_body_is_a_bad_request(_aws_test_credentials: No
)
)
_assert_unknown_operation_is_a_bad_request(exc_info.value)
_assert_unknown_operation_is_not_found(exc_info.value)
async def _drain_async_unknown_operation_stream() -> None:
@ -2188,11 +2211,13 @@ async def _drain_async_unknown_operation_stream() -> None:
@pytest.mark.asyncio
async def test_a_200_unknown_operation_body_is_a_bad_request_on_an_async_stream(_aws_test_credentials: None) -> None:
with pytest.raises(litellm.BadRequestError) as exc_info:
async def test_a_200_unknown_operation_body_is_a_not_found_error_on_an_async_stream(
_aws_test_credentials: None,
) -> None:
with pytest.raises(litellm.NotFoundError) as exc_info:
await _drain_async_unknown_operation_stream()
_assert_unknown_operation_is_a_bad_request(exc_info.value)
_assert_unknown_operation_is_not_found(exc_info.value)
def test_a_tool_call_whose_arguments_carry_a_type_key_is_not_read_as_a_bedrock_error(
@ -2228,3 +2253,74 @@ def test_a_tool_call_whose_arguments_carry_a_type_key_is_not_read_as_a_bedrock_e
tool_calls: Final = response.choices[0].message.tool_calls
assert tool_calls is not None
assert json.loads(tool_calls[0].function.arguments) == arguments
def _sent_invoke_body(model: str, deployment: Mapping[str, object]) -> object:
sent: Final[list[object]] = [] # mutable-ok: the scripted upstream records each body it receives
def answer(request: httpx.Request) -> httpx.Response:
sent.append(json.loads(request.content))
return httpx.Response(
200, content=_CLAUDE_INVOKE_ANSWER, headers={"content-type": "application/json"}, request=request
)
_ = litellm.completion(
model=model,
messages=[{"role": "user", "content": "hi"}],
max_tokens=3000,
reasoning_effort="low",
client=HTTPHandler(client=httpx.Client(transport=httpx.MockTransport(answer))),
**deployment,
)
(body,) = sent
return body
@pytest.mark.usefixtures("local_model_cost_map")
@pytest.mark.parametrize(
"model",
[
f"bedrock/invoke/{_APPLICATION_PROFILE_ARN}",
f"bedrock/invoke/anthropic/{_APPLICATION_PROFILE_ARN}",
f"bedrock/invoke/{_ARN_ACCOUNT}:provisioned-model/nova1meta2ab",
],
ids=["arn", "arn-under-spec", "provisioned-arn"],
)
def test_an_arn_with_base_model_sends_the_body_the_base_model_would_send(
_aws_test_credentials: None, model: str
) -> None:
base_model: Final = "us.anthropic.claude-haiku-5-5"
arn_body: Final = _sent_invoke_body(model, {"base_model": f"bedrock/{base_model}"})
assert arn_body == _sent_invoke_body(f"bedrock/invoke/{base_model}", {})
_CONVERSE_ANSWER: Final = json.dumps(
{
"output": {"message": {"role": "assistant", "content": [{"text": "hi"}]}},
"stopReason": "end_turn",
"usage": {"inputTokens": 1, "outputTokens": 1, "totalTokens": 2},
"metrics": {"latencyMs": 1},
}
).encode()
def test_a_provider_spec_before_an_arn_on_converse_is_left_out_of_the_model_id(_aws_test_credentials: None) -> None:
expected_path: Final = f"/model/{urllib.parse.quote(_APPLICATION_PROFILE_ARN, safe='')}/converse"
def answer(request: httpx.Request) -> httpx.Response:
if request.url.raw_path.decode() != expected_path:
return httpx.Response(404, json={"message": f"unexpected path {request.url.raw_path}"}, request=request)
return httpx.Response(
200, content=_CONVERSE_ANSWER, headers={"content-type": "application/json"}, request=request
)
response: Final = litellm.completion(
model=f"bedrock/anthropic/{_APPLICATION_PROFILE_ARN}",
messages=[{"role": "user", "content": "hi"}],
max_tokens=10,
client=HTTPHandler(client=httpx.Client(transport=httpx.MockTransport(answer))),
)
assert _answer_text(response) == "hi"

View file

@ -4565,12 +4565,12 @@ _UNKNOWN_OPERATION_BODY: Final = {
@pytest.mark.usefixtures("local_model_cost_map")
@pytest.mark.parametrize(
("stream", "raised"),
[(False, litellm.BadRequestError), (True, BedrockError)],
[(False, litellm.NotFoundError), (True, BedrockError)],
ids=["non-stream", "stream"],
)
async def test_bedrock_messages_raise_a_400_when_bedrock_answers_200_with_an_unknown_operation_body(
async def test_bedrock_messages_raise_a_404_when_bedrock_answers_200_with_an_unknown_operation_body(
stream: bool,
raised: type[litellm.BadRequestError] | type[BedrockError],
raised: type[litellm.NotFoundError] | type[BedrockError],
) -> None:
bedrock: Final = ScriptedBedrockInvoke(replies=((200, _UNKNOWN_OPERATION_BODY),))
@ -4591,5 +4591,5 @@ async def test_bedrock_messages_raise_a_400_when_bedrock_answers_200_with_an_unk
with pytest.raises(raised) as error:
await _call_and_drain()
assert error.value.status_code == 400
assert error.value.status_code == 404
assert "UnknownOperationException" in str(error.value)

View file

@ -2063,10 +2063,26 @@ def test_event_decoder_reports_only_missing_aws_dependency(missing):
"arn:aws:bedrock:us-east-1:000000000000:application-inference-profile/profile-id",
),
("us.anthropic.claude-opus-4-8", "anthropic.claude-sonnet-4-5", "us.anthropic.claude-opus-4-8"),
(
"invoke/anthropic/arn:aws:bedrock:us-east-1:000000000000:provisioned-model/abc123",
"bedrock/us.anthropic.claude-opus-4-8",
"us.anthropic.claude-opus-4-8",
),
(
"arn:aws:bedrock:us-east-1:000000000000:inference-profile/us.anthropic.claude-opus-4-8",
"anthropic.claude-sonnet-4-5",
"arn:aws:bedrock:us-east-1:000000000000:inference-profile/us.anthropic.claude-opus-4-8",
),
],
ids=[
"profile-arn-with-base-model",
"profile-arn-without-base-model",
"named-model-keeps-its-name",
"provisioned-arn-with-base-model",
"system-profile-arn-keeps-the-model-it-names",
],
ids=["profile-arn-with-base-model", "profile-arn-without-base-model", "named-model-keeps-its-name"],
)
def test_bedrock_capability_model_reads_base_model_only_for_application_profile_arns(model, base_model, expected):
def test_bedrock_capability_model_reads_base_model_only_for_arns_that_name_no_model(model, base_model, expected):
from litellm.llms.bedrock.common_utils import bedrock_capability_model
assert bedrock_capability_model(model, base_model) == expected