style: apply black formatting to fix lint CI (LIT-3274) (#28639) (#28641)

* fix(bedrock): strip bedrock/ prefix and URL-encode ARNs in get_bedrock_model_id for invoke path

The invoke path (used by /v1/messages → Anthropic SDK / Claude Code) called
get_bedrock_model_id() which, when falling back to the raw model string, did
not strip the 'bedrock/' routing prefix and did not URL-encode ARNs.

For a model like:
  bedrock/arn:aws:bedrock:us-east-1:<ACCOUNT>:inference-profile/global.anthropic...

the URL built was:
  /model/bedrock/arn:aws:bedrock:…/invoke-with-response-stream  

Bedrock returned a JSON error body.  LiteLLM's AWSEventStreamDecoder passed
those bytes into botocore's EventStreamBuffer which expects binary event-stream
framing.  Checksum validation failed on the JSON prelude (0x223a7b22 == ':{"')
producing a misleading botocore.eventstream.ChecksumMismatch instead of the
actual Bedrock error.

Fix: strip 'bedrock/' (and 'invoke/') routing prefix from model string, then
URL-encode if the result is an ARN — matching what the converse path already
does in converse_handler.py.

Fixes: LIT-3274

* fix(bedrock): use strip_bedrock_routing_prefix to handle compound prefixes

Address greptile review: the original fix used a loop with break, so
bedrock/invoke/arn:... only stripped bedrock/ leaving invoke/arn:...
which is not an ARN → fell through to .replace('invoke/','',1) →
bare unencoded ARN → same malformed-URL bug.

strip_bedrock_routing_prefix() iterates without break, correctly
stripping bedrock/ then invoke/ in sequence. Also adds test case
for the compound-prefix scenario.

* style: apply black formatting to fix lint CI (LIT-3274)

---------

Co-authored-by: oss-agent-shin <ext-agent-shin@berri.ai>
Co-authored-by: LiteLLM Bot <bot@berri.ai>
This commit is contained in:
Krrish Dholakia
2026-05-22 12:10:37 -07:00
committed by GitHub
co-authored by oss-agent-shin LiteLLM Bot
parent 9600fda2cc
commit a3c953ed4e
2 changed files with 117 additions and 0 deletions
+18
View File
@@ -450,6 +450,24 @@ class BaseAWSLLM:
model_id = BaseAWSLLM.encode_model_id(model_id=model_id)
else:
model_id = model
# Strip LiteLLM routing prefixes (e.g. "bedrock/", "invoke/",
# "bedrock/invoke/", "bedrock/converse/") that are not part of the
# actual Bedrock model ID. The converse path already does this; the
# invoke path must do the same so that ARN models such as
# bedrock/arn:aws:bedrock:…:inference-profile/global.anthropic.…
# are not forwarded verbatim to the Bedrock API, which would produce
# a malformed URL and cause botocore's EventStreamBuffer to receive
# a JSON error body instead of a binary event-stream — surfaced as a
# misleading ChecksumMismatch (0x223a7b22 == ':{"').
# Use strip_bedrock_routing_prefix (no break) so compound prefixes
# like "bedrock/invoke/arn:..." are fully stripped in one call.
from litellm.llms.bedrock.common_utils import strip_bedrock_routing_prefix
model_id = 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)
return model_id
model_id = model_id.replace("invoke/", "", 1)
if provider == "llama" and "llama/" in model_id:
@@ -2112,3 +2112,102 @@ def test_is_already_running_as_role_ssl_verify_passed():
mock_boto3_client.assert_called_once_with(
"sts", verify="/path/to/ca-bundle.crt"
)
# ---------------------------------------------------------------------------
# LIT-3274: get_bedrock_model_id must strip "bedrock/" prefix and URL-encode
# ARNs for the invoke path (invoke-with-response-stream). Without this fix
# the Bedrock API receives a malformed URL, returns a JSON error body, and
# botocore's EventStreamBuffer raises ChecksumMismatch instead of the real
# error. 0x223a7b22 == ':{\"' — the start of a JSON object.
# ---------------------------------------------------------------------------
class TestGetBedrockModelIdArnHandling:
"""Unit tests for get_bedrock_model_id with inference-profile ARNs."""
ARN = "arn:aws:bedrock:us-east-1:086734376398:inference-profile/global.anthropic.claude-sonnet-4-5-20250929-v1:0"
def _call(self, model: str, optional_params: dict | None = None) -> str:
from litellm.llms.bedrock.base_aws_llm import BaseAWSLLM
provider = BaseAWSLLM.get_bedrock_invoke_provider(model)
return BaseAWSLLM.get_bedrock_model_id(
model=model,
provider=provider,
optional_params=optional_params or {},
)
def test_arn_with_bedrock_prefix_is_stripped_and_encoded(self):
"""bedrock/arn:... must not appear verbatim in the model_id."""
model_id = self._call(f"bedrock/{self.ARN}")
assert (
"bedrock/arn" not in model_id
), f"'bedrock/' prefix not stripped; got: {model_id}"
# Must be URL-encoded (colons → %3A)
assert "%3A" in model_id, f"ARN not URL-encoded; got: {model_id}"
assert "%2F" in model_id, f"ARN slashes not URL-encoded; got: {model_id}"
def test_arn_with_compound_bedrock_invoke_prefix_is_fully_stripped_and_encoded(
self,
):
"""bedrock/invoke/arn:... — compound prefix — must be fully stripped.
The old fix used ``break`` after the first matched prefix, so
``bedrock/invoke/arn:...`` would only strip ``bedrock/``, leaving
``invoke/arn:...``. The subsequent ``.replace('invoke/', '')`` call
then returned the bare unencoded ARN, reproducing the same
malformed-URL bug the fix aimed to prevent.
strip_bedrock_routing_prefix() has no break and handles this correctly.
"""
model_id = self._call(f"bedrock/invoke/{self.ARN}")
assert (
"invoke/" not in model_id
), f"'invoke/' prefix not stripped; got: {model_id}"
assert (
"bedrock/" not in model_id
), f"'bedrock/' prefix not stripped; got: {model_id}"
assert "%3A" in model_id, f"ARN not URL-encoded; got: {model_id}"
assert "%2F" in model_id, f"ARN slashes not URL-encoded; got: {model_id}"
def test_bare_arn_is_encoded(self):
"""Direct ARN without routing prefix must also be URL-encoded."""
model_id = self._call(self.ARN)
assert "%3A" in model_id, f"ARN not URL-encoded; got: {model_id}"
assert "%2F" in model_id, f"ARN slashes not URL-encoded; got: {model_id}"
def test_arn_url_matches_expected(self):
"""Full URL built from messages config must match expected encoded form."""
import urllib.parse
from litellm.llms.bedrock.messages.invoke_transformations.anthropic_claude3_transformation import (
AmazonAnthropicClaudeMessagesConfig,
)
config = AmazonAnthropicClaudeMessagesConfig()
url = config.get_complete_url(
api_base=None,
api_key=None,
model=f"bedrock/{self.ARN}",
optional_params={"aws_region_name": "us-east-1"},
litellm_params={},
stream=True,
)
encoded_arn = urllib.parse.quote(self.ARN, safe="")
expected = (
f"https://bedrock-runtime.us-east-1.amazonaws.com"
f"/model/{encoded_arn}/invoke-with-response-stream"
)
assert (
url == expected
), f"URL mismatch:\n got: {url}\n expected: {expected}"
def test_regular_model_id_unaffected(self):
"""Non-ARN model IDs must continue to work as before."""
model_id = self._call("anthropic.claude-3-sonnet-20240229-v1:0")
assert model_id == "anthropic.claude-3-sonnet-20240229-v1:0"
def test_invoke_prefixed_model_unaffected(self):
"""invoke/ prefix stripping still works after the fix."""
model_id = self._call("invoke/anthropic.claude-3-sonnet-20240229-v1:0")
assert model_id == "anthropic.claude-3-sonnet-20240229-v1:0"