fix(bedrock): route application-inference-profile ARNs to converse (#18258) (#31098)

A bare application-inference-profile ARN passed as bedrock/arn:... fell
through to the invoke route, which cannot derive a provider from the
opaque profile id and raised 'Unknown provider=None'. The converse route
needs no provider, so detect these ARNs in get_bedrock_route and route
them to converse, matching the behavior of the already-documented
bedrock/converse/arn:... workaround.

Explicit invoke/ prefixes still win, and they remain a dead end for these
ARNs by design (no provider derivable). System-defined inference-profile
ARNs that embed a known model, and other opaque ARN types
(provisioned-model, imported-model, custom-model-deployment) that are
frequently invoke-only, are deliberately left on their current routes;
tests guard both boundaries.
This commit is contained in:
Kent 2026-06-24 18:38:20 +08:00 • committed by GitHub
parent 63741e9698
commit 6893df16fb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 74 additions and 0 deletions

View file

@ -601,6 +601,15 @@ def extract_model_name_from_bedrock_arn(model: str) -> str:
return model
def is_bedrock_application_inference_profile_arn(model: str) -> bool:
"""
An application inference profile ARN ends in an opaque id with no provider
substring, so the invoke path cannot resolve a provider from it. Such ARNs
must use the converse route, which needs no provider.
"""
return ":application-inference-profile/" in model
def strip_bedrock_routing_prefix(model: str) -> str:
"""Strip LiteLLM routing prefixes from model name."""
for prefix in ["bedrock/", "converse/", "invoke/", "openai/", "nova-2/", "nova/"]:
@ -916,6 +925,9 @@ class BedrockModelInfo(BaseLLMModelInfo):
) or _model_after_bedrock.startswith("nova/"):
return "converse"
if is_bedrock_application_inference_profile_arn(model):
return "converse"
base_model = BedrockModelInfo.get_base_model(model)
alt_model = BedrockModelInfo.get_non_litellm_routing_model_name(model=model)
if (

View file

@ -158,6 +158,68 @@ def test_deepseek_cris():
assert bedrock_route == "converse"
def test_application_inference_profile_arn_routes_to_converse():
"""
Regression for #18258: a bare application-inference-profile ARN passed as
`bedrock/arn:...` must route to converse. The ARN ends in an opaque id with
no provider substring, so the invoke path cannot build a provider-native
body and raises "Unknown provider=None". Converse needs no provider, so it
is the correct route.
"""
route = BedrockModelInfo.get_bedrock_route(
model="bedrock/arn:aws:bedrock:us-west-2:123412341234:application-inference-profile/a1b2c3"
)
assert route == "converse"
def test_explicit_invoke_prefix_wins_over_application_inference_profile_arn():
"""
An explicit invoke/ prefix is respected even for an application-inference-profile
ARN; only the bare `bedrock/arn:...` form is auto-routed to converse. The
explicit invoke path remains a dead end for these ARNs (no provider can be
derived, so completion raises "Unknown provider=None") by design: a caller
that explicitly asks for invoke gets invoke. The auto-route only rescues the
documented bare form.
"""
from litellm.llms.bedrock.base_aws_llm import BaseAWSLLM
model = "bedrock/invoke/arn:aws:bedrock:us-west-2:123412341234:application-inference-profile/a1b2c3"
assert BedrockModelInfo.get_bedrock_route(model) == "invoke"
assert BaseAWSLLM.get_bedrock_invoke_provider(model) is None
def test_system_defined_inference_profile_arn_still_routes_to_converse():
"""
A system-defined cross-region inference-profile ARN embeds a known model, so
get_base_model resolves it and it already routes to converse. Guards that the
application-inference-profile fix does not change this working case.
"""
route = BedrockModelInfo.get_bedrock_route(
model="bedrock/arn:aws:bedrock:us-east-1:123:inference-profile/us.anthropic.claude-3-5-sonnet-20240620-v1:0"
)
assert route == "converse"
def test_other_opaque_arn_types_still_route_to_invoke():
"""
Only application-inference-profile ARNs are auto-routed to converse. Other
opaque ARNs (provisioned-model, imported-model, custom-model-deployment)
also yield no invoke provider, but they are frequently invoke-only with
provider-specific body formats, so routing them to converse could break
them. Guards the deliberate scope against an over-broad "any opaque ARN ->
converse" generalization.
"""
for arn_segment in (
"provisioned-model/abcdefgh1234",
"imported-model/abcdefgh1234",
"custom-model-deployment/abcdefgh1234",
):
route = BedrockModelInfo.get_bedrock_route(
model=f"bedrock/arn:aws:bedrock:us-east-1:123412341234:{arn_segment}"
)
assert route == "invoke", f"{arn_segment} should stay on invoke route"
def test_govcloud_cross_region_inference_prefix():
"""
Test that GovCloud models with cross-region inference prefix (us-gov.) are parsed correctly