diff --git a/litellm/__init__.py b/litellm/__init__.py index 56d516536e8a..83746b184f5a 100644 --- a/litellm/__init__.py +++ b/litellm/__init__.py @@ -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, diff --git a/litellm/_lazy_imports_registry.py b/litellm/_lazy_imports_registry.py index 17eb66092924..6d8040b5d0d4 100644 --- a/litellm/_lazy_imports_registry.py +++ b/litellm/_lazy_imports_registry.py @@ -219,6 +219,7 @@ "OpenAITextCompletionConfig", "GroqChatConfig", "BedrockMantleChatConfig", + "BedrockMantleResponsesAPIConfig", "A2AConfig", "GenAIHubOrchestrationConfig", "VoyageEmbeddingConfig", @@ -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", diff --git a/litellm/llms/bedrock_mantle/chat/transformation.py b/litellm/llms/bedrock_mantle/chat/transformation.py index 81a56030a5c5..16ac22f0eba0 100644 --- a/litellm/llms/bedrock_mantle/chat/transformation.py +++ b/litellm/llms/bedrock_mantle/chat/transformation.py @@ -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. """ @@ -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 diff --git a/litellm/llms/bedrock_mantle/responses/transformation.py b/litellm/llms/bedrock_mantle/responses/transformation.py new file mode 100644 index 000000000000..769226c8b4dc --- /dev/null +++ b/litellm/llms/bedrock_mantle/responses/transformation.py @@ -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"), + 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" + return f"{api_base}/openai/v1/responses" + + def supports_native_websocket(self) -> bool: + return False diff --git a/litellm/utils.py b/litellm/utils.py index a3a26c338b5e..a53a7b9fecdb 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -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 diff --git a/tests/test_litellm/llms/bedrock_mantle/test_bedrock_mantle_transformation.py b/tests/test_litellm/llms/bedrock_mantle/test_bedrock_mantle_transformation.py index 1725aa85d10f..68fb4a27c2d5 100644 --- a/tests/test_litellm/llms/bedrock_mantle/test_bedrock_mantle_transformation.py +++ b/tests/test_litellm/llms/bedrock_mantle/test_bedrock_mantle_transformation.py @@ -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: @@ -50,7 +54,7 @@ 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) @@ -58,7 +62,7 @@ def test_default_api_base_uses_aws_region(self, monkeypatch): 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) @@ -66,10 +70,10 @@ def test_default_api_base_fallback_to_us_east_1(self, monkeypatch): 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 @@ -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(