Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions litellm/llms/bedrock/chat/mantle/transformation.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from litellm.llms.bedrock.chat.invoke_transformations.anthropic_claude3_transformation import (
AmazonAnthropicClaudeConfig,
)
from litellm.llms.bedrock.common_utils import build_mantle_messages_url
from litellm.types.llms.openai import AllMessageValues

if TYPE_CHECKING:
Expand All @@ -21,10 +22,6 @@
else:
LiteLLMLoggingObj = Any

MANTLE_ENDPOINT_TEMPLATE = (
"https://bedrock-mantle.{region}.api.aws/anthropic/v1/messages"
)


class AmazonMantleConfig(AmazonAnthropicClaudeConfig):
"""
Expand All @@ -46,7 +43,13 @@ def get_complete_url(
stream: Optional[bool] = None,
) -> str:
region = self._get_aws_region_name(optional_params=optional_params, model=model)
return MANTLE_ENDPOINT_TEMPLATE.format(region=region)
return build_mantle_messages_url(
api_base=api_base,
aws_bedrock_runtime_endpoint=optional_params.get(
"aws_bedrock_runtime_endpoint"
),
region=region,
)

def validate_environment(
self,
Expand Down
25 changes: 25 additions & 0 deletions litellm/llms/bedrock/common_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -622,6 +622,31 @@ def strip_bedrock_throughput_suffix(model: str) -> str:
return model


MANTLE_MESSAGES_PATH = "/anthropic/v1/messages"


def build_mantle_messages_url(
api_base: Optional[str],
aws_bedrock_runtime_endpoint: Optional[str],
region: str,
) -> str:
"""Build the bedrock-mantle Anthropic /messages URL.

Honors an explicit endpoint override (``api_base``, then
``aws_bedrock_runtime_endpoint``) so private VPC / VPCE / GovCloud Mantle
endpoints are reachable; otherwise falls back to the public regional host.
The mantle messages path is appended unless the override already carries it,
so callers can pass either the host or the full messages URL.
"""
override = api_base or aws_bedrock_runtime_endpoint
Comment thread
veria-ai[bot] marked this conversation as resolved.
if override:
base = override.rstrip("/")
if base.endswith(MANTLE_MESSAGES_PATH):
return base
return f"{base}{MANTLE_MESSAGES_PATH}"
return f"https://bedrock-mantle.{region}.api.aws{MANTLE_MESSAGES_PATH}"


def get_bedrock_base_model(model: str) -> str:
"""
Get the base model from the given model name.
Expand Down
13 changes: 8 additions & 5 deletions litellm/llms/bedrock/messages/mantle_transformation.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple

from litellm.llms.bedrock.common_utils import build_mantle_messages_url
from litellm.llms.bedrock.messages.invoke_transformations.anthropic_claude3_transformation import (
AmazonAnthropicClaudeMessagesConfig,
)
Expand All @@ -20,10 +21,6 @@
else:
LiteLLMLoggingObj = Any

MANTLE_ENDPOINT_TEMPLATE = (
"https://bedrock-mantle.{region}.api.aws/anthropic/v1/messages"
)


class AmazonMantleMessagesConfig(AmazonAnthropicClaudeMessagesConfig):
"""
Expand All @@ -43,7 +40,13 @@ def get_complete_url(
stream: Optional[bool] = None,
) -> str:
region = self._get_aws_region_name(optional_params=optional_params, model=model)
return MANTLE_ENDPOINT_TEMPLATE.format(region=region)
return build_mantle_messages_url(
api_base=api_base,
aws_bedrock_runtime_endpoint=optional_params.get(
"aws_bedrock_runtime_endpoint"
),
region=region,
)

def validate_anthropic_messages_environment(
self,
Expand Down
100 changes: 100 additions & 0 deletions tests/test_litellm/llms/bedrock/test_mantle.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,74 @@ def test_mantle_messages_url_construction():
assert url == "https://bedrock-mantle.us-east-1.api.aws/anthropic/v1/messages"


_VPC_ENDPOINT = "https://vpce-0a1b2c3d.bedrock-mantle.us-gov-west-1.vpce.amazonaws.com"


def test_mantle_chat_url_honors_api_base_host():
config = AmazonMantleConfig()
url = config.get_complete_url(
api_base=_VPC_ENDPOINT,
api_key=None,
model="mantle/anthropic.claude-mythos-preview",
optional_params={"aws_region_name": "us-gov-west-1"},
litellm_params={},
)
assert url == f"{_VPC_ENDPOINT}/anthropic/v1/messages"


def test_mantle_chat_url_honors_api_base_full_path_without_duplication():
config = AmazonMantleConfig()
full = f"{_VPC_ENDPOINT}/anthropic/v1/messages"
url = config.get_complete_url(
api_base=full,
api_key=None,
model="mantle/anthropic.claude-mythos-preview",
optional_params={"aws_region_name": "us-gov-west-1"},
litellm_params={},
)
assert url == full


def test_mantle_messages_url_honors_api_base_host():
config = AmazonMantleMessagesConfig()
url = config.get_complete_url(
api_base=_VPC_ENDPOINT,
api_key=None,
model="mantle/anthropic.claude-mythos-preview",
optional_params={"aws_region_name": "us-gov-west-1"},
litellm_params={},
)
assert url == f"{_VPC_ENDPOINT}/anthropic/v1/messages"
assert "api.aws" not in url


def test_mantle_messages_url_honors_api_base_with_trailing_slash():
config = AmazonMantleMessagesConfig()
url = config.get_complete_url(
api_base=f"{_VPC_ENDPOINT}/",
api_key=None,
model="mantle/anthropic.claude-mythos-preview",
optional_params={"aws_region_name": "us-gov-west-1"},
litellm_params={},
)
assert url == f"{_VPC_ENDPOINT}/anthropic/v1/messages"


def test_mantle_messages_url_honors_aws_bedrock_runtime_endpoint():
config = AmazonMantleMessagesConfig()
url = config.get_complete_url(
api_base=None,
api_key=None,
model="mantle/anthropic.claude-mythos-preview",
optional_params={
"aws_region_name": "us-gov-west-1",
"aws_bedrock_runtime_endpoint": _VPC_ENDPOINT,
},
litellm_params={},
)
assert url == f"{_VPC_ENDPOINT}/anthropic/v1/messages"
Comment thread
greptile-apps[bot] marked this conversation as resolved.


def test_mantle_transform_request_strips_prefix_and_adds_model():
config = AmazonMantleConfig()
request = config.transform_request(
Expand Down Expand Up @@ -247,3 +315,35 @@ async def mock_post(self, url, data=None, headers=None, **kwargs):
assert requests[0]["path"] == "/anthropic/v1/messages"
assert requests[0]["headers"]["anthropic-workspace"] == "proj_abc123def456"
assert "aws_bedrock_project_id" not in requests[0]["body"]


@pytest.mark.asyncio
async def test_mantle_anthropic_messages_routes_to_vpc_api_base():
import litellm

urls = []

async def mock_post(self, url, data=None, headers=None, **kwargs):
urls.append(str(url))
return _anthropic_response(str(url))

try:
with patch(
"litellm.llms.custom_httpx.http_handler.AsyncHTTPHandler.post",
new=mock_post,
):
await litellm.anthropic_messages(
model="bedrock/mantle/anthropic.claude-mythos-preview",
messages=[{"role": "user", "content": "hello"}],
max_tokens=10,
api_base=_VPC_ENDPOINT,
aws_access_key_id="fake-key",
aws_secret_access_key="fake-secret",
aws_region_name="us-gov-west-1",
)
finally:
await litellm.close_litellm_async_clients()

assert len(urls) == 1
assert urls[0] == f"{_VPC_ENDPOINT}/anthropic/v1/messages"
assert "api.aws" not in urls[0]
Loading