Skip to content
Closed
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
3 changes: 3 additions & 0 deletions litellm/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1680,6 +1680,9 @@ def set_global_gitlab_config(config: Dict[str, Any]) -> None:
from .llms.bedrock_mantle.chat.transformation import (
BedrockMantleChatConfig as BedrockMantleChatConfig,
)
from .llms.bedrock_mantle.responses.transformation import (
BedrockMantleResponsesAPIConfig as BedrockMantleResponsesAPIConfig,
)
from .llms.a2a.chat.transformation import A2AConfig as A2AConfig
from .llms.voyage.embedding.transformation import (
VoyageEmbeddingConfig as VoyageEmbeddingConfig,
Expand Down
5 changes: 5 additions & 0 deletions litellm/_lazy_imports_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@
"OpenAITextCompletionConfig",
"GroqChatConfig",
"BedrockMantleChatConfig",
"BedrockMantleResponsesAPIConfig",
"A2AConfig",
"GenAIHubOrchestrationConfig",
"VoyageEmbeddingConfig",
Expand Down Expand Up @@ -886,6 +887,10 @@
".llms.bedrock_mantle.chat.transformation",
"BedrockMantleChatConfig",
),
"BedrockMantleResponsesAPIConfig": (
".llms.bedrock_mantle.responses.transformation",
"BedrockMantleResponsesAPIConfig",
),
"A2AConfig": (".llms.a2a.chat.transformation", "A2AConfig"),
"GenAIHubOrchestrationConfig": (
".llms.sap.chat.transformation",
Expand Down
4 changes: 2 additions & 2 deletions litellm/llms/bedrock_mantle/chat/transformation.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

API docs: https://docs.aws.amazon.com/bedrock/latest/userguide/bedrock-mantle.html

Base URL: https://bedrock-mantle.{region}.api.aws/v1
Base URL: https://bedrock-mantle.{region}.api.aws/openai/v1
Auth: AWS Bedrock API key as Bearer token (set via BEDROCK_MANTLE_API_KEY env var)
or region-aware key via BEDROCK_MANTLE_{REGION}_API_KEY.
"""
Expand Down Expand Up @@ -43,7 +43,7 @@ def _get_openai_compatible_provider_info(
api_base = (
api_base
or get_secret_str("BEDROCK_MANTLE_API_BASE")
or f"https://bedrock-mantle.{region}.api.aws/v1"
or f"https://bedrock-mantle.{region}.api.aws/openai/v1"
)
dynamic_api_key = api_key or get_secret_str("BEDROCK_MANTLE_API_KEY")
return api_base, dynamic_api_key
Expand Down
60 changes: 60 additions & 0 deletions litellm/llms/bedrock_mantle/responses/transformation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
"""
Amazon Bedrock Mantle - OpenAI-compatible Responses API.

Routes /v1/responses to the provider's native Responses endpoint instead of
the chat-completions translation fallback.
"""

from typing import Optional, cast

from litellm.llms.bedrock_mantle.chat.transformation import BedrockMantleChatConfig
from litellm.llms.openai.responses.transformation import OpenAIResponsesAPIConfig
from litellm.types.router import GenericLiteLLMParams
from litellm.types.utils import LlmProviders


class BedrockMantleResponsesAPIConfig(OpenAIResponsesAPIConfig):
@property
def custom_llm_provider(self) -> LlmProviders:
return LlmProviders.BEDROCK_MANTLE

def validate_environment(
self,
headers: dict,
model: str,
litellm_params: Optional[GenericLiteLLMParams],
) -> dict:
litellm_params = litellm_params or GenericLiteLLMParams()
_, api_key = BedrockMantleChatConfig()._get_openai_compatible_provider_info(
api_base=litellm_params.api_base,
api_key=litellm_params.api_key,
)
if api_key:
headers["Authorization"] = f"Bearer {api_key}"
return headers

def get_complete_url(
self,
api_base: Optional[str],
litellm_params: dict,
) -> str:
(
resolved_api_base,
_,
) = BedrockMantleChatConfig()._get_openai_compatible_provider_info(
api_base=api_base or litellm_params.get("api_base"),

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.

High: Bedrock Mantle key can be sent to caller-controlled api_base

An authenticated proxy caller can POST to /v1/responses with a Bedrock Mantle model and an api_base pointing at their server while omitting api_key. validate_environment() still attaches BEDROCK_MANTLE_API_KEY, and this URL builder sends the request to the caller's host, exposing the provider bearer token. Only use operator-configured base URLs when using the environment credential, or require/allowlist per-request api_base before attaching the proxy's Bedrock Mantle key.

api_key=litellm_params.get("api_key"),
)
api_base = cast(str, resolved_api_base).rstrip("/")
if api_base.endswith("/responses"):
return api_base
if api_base.endswith("/openai/v1"):
return f"{api_base}/responses"
if api_base.endswith("/openai"):
return f"{api_base}/v1/responses"
if api_base.endswith("/v1"):
return f"{api_base}/responses"
Comment on lines +55 to +56

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.

P1 /v1 fallback produces wrong Bedrock Mantle URL

When BEDROCK_MANTLE_API_BASE is set to a URL ending in /v1 (e.g. https://bedrock-mantle.us-east-1.api.aws/v1 — the old default format before this PR's fix), get_complete_url returns {base}/responses instead of the correct /openai/v1/responses path. The Bedrock Mantle Responses endpoint lives at /openai/v1/responses, so any user whose BEDROCK_MANTLE_API_BASE still carries the old /v1 suffix will receive a 404 when calling responses. A dedicated test for this case (api_base ending in /v1) is also missing.

return f"{api_base}/openai/v1/responses"

def supports_native_websocket(self) -> bool:
return False
2 changes: 2 additions & 0 deletions litellm/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -8865,6 +8865,8 @@ def _get_python_responses_api_config(
return litellm.OpenRouterResponsesAPIConfig()
elif litellm.LlmProviders.HOSTED_VLLM == provider:
return litellm.HostedVLLMResponsesAPIConfig()
elif litellm.LlmProviders.BEDROCK_MANTLE == provider:
return litellm.BedrockMantleResponsesAPIConfig()
return None

@staticmethod
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@

import litellm
from litellm.llms.bedrock_mantle.chat.transformation import BedrockMantleChatConfig
from litellm.llms.bedrock_mantle.responses.transformation import (
BedrockMantleResponsesAPIConfig,
)
from litellm.types.utils import LlmProviders
from litellm.utils import ProviderConfigManager


class TestBedrockMantleProviderRegistration:
Expand Down Expand Up @@ -50,26 +54,26 @@ def test_default_api_base_uses_env_region(self, monkeypatch):
monkeypatch.delenv("BEDROCK_MANTLE_API_BASE", raising=False)
cfg = BedrockMantleChatConfig()
api_base, _ = cfg._get_openai_compatible_provider_info(None, None)
assert api_base == "https://bedrock-mantle.eu-west-1.api.aws/v1"
assert api_base == "https://bedrock-mantle.eu-west-1.api.aws/openai/v1"

def test_default_api_base_uses_aws_region(self, monkeypatch):
monkeypatch.delenv("BEDROCK_MANTLE_REGION", raising=False)
monkeypatch.delenv("BEDROCK_MANTLE_API_BASE", raising=False)
monkeypatch.setenv("AWS_REGION", "ap-northeast-1")
cfg = BedrockMantleChatConfig()
api_base, _ = cfg._get_openai_compatible_provider_info(None, None)
assert api_base == "https://bedrock-mantle.ap-northeast-1.api.aws/v1"
assert api_base == "https://bedrock-mantle.ap-northeast-1.api.aws/openai/v1"

def test_default_api_base_fallback_to_us_east_1(self, monkeypatch):
monkeypatch.delenv("BEDROCK_MANTLE_REGION", raising=False)
monkeypatch.delenv("BEDROCK_MANTLE_API_BASE", raising=False)
monkeypatch.delenv("AWS_REGION", raising=False)
cfg = BedrockMantleChatConfig()
api_base, _ = cfg._get_openai_compatible_provider_info(None, None)
assert api_base == "https://bedrock-mantle.us-east-1.api.aws/v1"
assert api_base == "https://bedrock-mantle.us-east-1.api.aws/openai/v1"

def test_custom_api_base_overrides_default(self, monkeypatch):
custom_base = "https://bedrock-mantle.us-west-2.api.aws/v1"
custom_base = "https://bedrock-mantle.us-west-2.api.aws/openai/v1"
cfg = BedrockMantleChatConfig()
api_base, _ = cfg._get_openai_compatible_provider_info(custom_base, None)
assert api_base == custom_base
Expand All @@ -96,6 +100,29 @@ def test_get_supported_openai_params(self):
assert "max_tokens" in params


class TestBedrockMantleResponsesConfig:
def test_responses_complete_url(self, monkeypatch):
monkeypatch.setenv("BEDROCK_MANTLE_REGION", "us-east-1")
cfg = BedrockMantleResponsesAPIConfig()
url = cfg.get_complete_url(api_base=None, litellm_params={})
assert url == "https://bedrock-mantle.us-east-1.api.aws/openai/v1/responses"

def test_responses_complete_url_bare_api_base(self, monkeypatch):
monkeypatch.setenv(
"BEDROCK_MANTLE_API_BASE", "https://bedrock-mantle.us-east-1.api.aws"
)
cfg = BedrockMantleResponsesAPIConfig()
url = cfg.get_complete_url(api_base=None, litellm_params={})
assert url == "https://bedrock-mantle.us-east-1.api.aws/openai/v1/responses"

def test_provider_config_manager_registers_responses(self):
cfg = ProviderConfigManager.get_provider_responses_api_config(
provider="bedrock_mantle",
model="gpt-5.5",
)
assert isinstance(cfg, BedrockMantleResponsesAPIConfig)


class TestBedrockMantleProviderResolution:
def test_get_llm_provider_resolves_correctly(self):
model, provider, _, _ = litellm.get_llm_provider(
Expand Down
Loading