From cf3ca0710f16f0ca95888c23012b22ebc98e2d79 Mon Sep 17 00:00:00 2001 From: kimnamu Date: Wed, 23 Sep 2026 15:37:18 +0900 Subject: [PATCH] Python: Support OpenAI models (GPT-6 Sol/Luna, GPT-5.x, gpt-oss) in the Bedrock chat connector BedrockModelProvider had no "openai" member, so any openai.* model ID (us.openai.gpt-6-sol, global.openai.gpt-6-luna, openai.gpt-oss-120b-1:0) failed with "does not contain a valid model provider name", and BEDROCK_MODEL_PROVIDER=openai failed validation. Once routed, every request was still rejected because the default empty stop list was sent as inferenceConfig.stopSequences=[]; OpenAI models on Bedrock reject the stopSequences field. Only send it when stop is set. Co-Authored-By: Claude --- .../services/bedrock_chat_completion.py | 2 +- .../model_provider/bedrock_model_provider.py | 3 +++ .../services/model_provider/bedrock_openai.py | 21 +++++++++++++++++++ .../services/test_bedrock_chat_completion.py | 18 ++++++++++++++-- .../test_bedrock_model_provider_utils.py | 6 ++++++ 5 files changed, 47 insertions(+), 3 deletions(-) create mode 100644 python/semantic_kernel/connectors/ai/bedrock/services/model_provider/bedrock_openai.py diff --git a/python/semantic_kernel/connectors/ai/bedrock/services/bedrock_chat_completion.py b/python/semantic_kernel/connectors/ai/bedrock/services/bedrock_chat_completion.py index 77a73fcc800a..cecfb01ab1cb 100644 --- a/python/semantic_kernel/connectors/ai/bedrock/services/bedrock_chat_completion.py +++ b/python/semantic_kernel/connectors/ai/bedrock/services/bedrock_chat_completion.py @@ -211,7 +211,7 @@ def _prepare_settings_for_request( "maxTokens": settings.max_tokens, "temperature": settings.temperature, "topP": settings.top_p, - "stopSequences": settings.stop, + "stopSequences": settings.stop or None, }), "additionalModelRequestFields": get_chat_completion_additional_model_request_fields( self.ai_model_id, settings, model_provider=self.bedrock_model_provider diff --git a/python/semantic_kernel/connectors/ai/bedrock/services/model_provider/bedrock_model_provider.py b/python/semantic_kernel/connectors/ai/bedrock/services/model_provider/bedrock_model_provider.py index 00a10febcf86..8a6840e7c379 100644 --- a/python/semantic_kernel/connectors/ai/bedrock/services/model_provider/bedrock_model_provider.py +++ b/python/semantic_kernel/connectors/ai/bedrock/services/model_provider/bedrock_model_provider.py @@ -16,6 +16,7 @@ bedrock_cohere, bedrock_meta_llama, bedrock_mistralai, + bedrock_openai, ) from semantic_kernel.contents.streaming_text_content import StreamingTextContent from semantic_kernel.contents.text_content import TextContent @@ -33,6 +34,7 @@ class BedrockModelProvider(Enum): COHERE = "cohere" META = "meta" MISTRALAI = "mistral" + OPENAI = "openai" @classmethod def to_model_provider(cls, model_id: str) -> "BedrockModelProvider": @@ -118,6 +120,7 @@ def parse_streaming_text_completion_response( BedrockModelProvider.AI21LABS: bedrock_ai21_labs.get_chat_completion_additional_model_request_fields, BedrockModelProvider.META: bedrock_meta_llama.get_chat_completion_additional_model_request_fields, BedrockModelProvider.MISTRALAI: bedrock_mistralai.get_chat_completion_additional_model_request_fields, + BedrockModelProvider.OPENAI: bedrock_openai.get_chat_completion_additional_model_request_fields, } diff --git a/python/semantic_kernel/connectors/ai/bedrock/services/model_provider/bedrock_openai.py b/python/semantic_kernel/connectors/ai/bedrock/services/model_provider/bedrock_openai.py new file mode 100644 index 000000000000..dc6e71f89e40 --- /dev/null +++ b/python/semantic_kernel/connectors/ai/bedrock/services/model_provider/bedrock_openai.py @@ -0,0 +1,21 @@ +# Copyright (c) Microsoft. All rights reserved. + +from typing import Any + +from semantic_kernel.connectors.ai.bedrock.bedrock_prompt_execution_settings import BedrockChatPromptExecutionSettings + +# region Chat Completion + + +def get_chat_completion_additional_model_request_fields( + settings: BedrockChatPromptExecutionSettings, +) -> dict[str, Any] | None: + """Get the additional model request fields for chat completion for OpenAI models. + + OpenAI models do not need additional model request fields. + https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-openai.html + """ + return None + + +# endregion diff --git a/python/tests/unit/connectors/ai/bedrock/services/test_bedrock_chat_completion.py b/python/tests/unit/connectors/ai/bedrock/services/test_bedrock_chat_completion.py index efa702a43813..d152603edc98 100644 --- a/python/tests/unit/connectors/ai/bedrock/services/test_bedrock_chat_completion.py +++ b/python/tests/unit/connectors/ai/bedrock/services/test_bedrock_chat_completion.py @@ -169,6 +169,7 @@ def test_prepare_system_message_for_request(mock_client, bedrock_unit_test_env, "ai21.jamba", "meta.llama", "mistral.ai", + "openai.gpt", ], ) @patch.object(boto3, "client", return_value=Mock()) @@ -183,7 +184,17 @@ def test_prepare_settings_for_request(mock_client, model_id, chat_history) -> No assert parsed_settings["messages"] == bedrock_chat_completion._prepare_chat_history_for_request(chat_history) assert parsed_settings["system"] == bedrock_chat_completion._prepare_system_messages_for_request(chat_history) assert isinstance(parsed_settings["inferenceConfig"], dict) - assert all([parsed_settings["inferenceConfig"].values()]) + assert parsed_settings["inferenceConfig"] == {} + + +@patch.object(boto3, "client", return_value=Mock()) +def test_prepare_settings_for_request_with_stop(mock_client, chat_history) -> None: + """Test that stop sequences are sent only when they are set""" + bedrock_chat_completion = BedrockChatCompletion(model_id="anthropic.claude") + settings = BedrockChatPromptExecutionSettings(stop=["world"]) + parsed_settings = bedrock_chat_completion._prepare_settings_for_request(chat_history, settings) + + assert parsed_settings["inferenceConfig"] == {"stopSequences": ["world"]} @pytest.mark.parametrize( @@ -213,7 +224,7 @@ def test_prepare_settings_for_request_with_application_inference_profile(mock_cl assert parsed_settings["messages"] == bedrock_chat_completion._prepare_chat_history_for_request(chat_history) assert parsed_settings["system"] == bedrock_chat_completion._prepare_system_messages_for_request(chat_history) assert isinstance(parsed_settings["inferenceConfig"], dict) - assert all([parsed_settings["inferenceConfig"].values()]) + assert parsed_settings["inferenceConfig"] == {} # endregion @@ -232,6 +243,7 @@ def test_prepare_settings_for_request_with_application_inference_profile(mock_cl "ai21.jamba", "meta.llama", "mistral.ai", + "openai.gpt", ], ) async def test_bedrock_chat_completion( @@ -288,6 +300,7 @@ async def test_bedrock_chat_completion( "ai21.jamba", "meta.llama", "mistral.ai", + "openai.gpt", ], ) async def test_bedrock_streaming_chat_completion( @@ -339,6 +352,7 @@ async def test_bedrock_streaming_chat_completion( "ai21.jamba", "meta.llama", "mistral.ai", + "openai.gpt", ], ) async def test_bedrock_streaming_chat_completion_invalid_event( diff --git a/python/tests/unit/connectors/ai/bedrock/services/test_bedrock_model_provider_utils.py b/python/tests/unit/connectors/ai/bedrock/services/test_bedrock_model_provider_utils.py index ef279db2521d..5f4d202288a5 100644 --- a/python/tests/unit/connectors/ai/bedrock/services/test_bedrock_model_provider_utils.py +++ b/python/tests/unit/connectors/ai/bedrock/services/test_bedrock_model_provider_utils.py @@ -157,6 +157,12 @@ def test_inference_profile_with_bedrock_model() -> None: eu_meta_inference_profile = "eu.meta.llama3-2-3b-instruct-v1:0" assert BedrockModelProvider.to_model_provider(eu_meta_inference_profile) == BedrockModelProvider.META + us_openai_inference_profile = "us.openai.gpt-6-sol" + assert BedrockModelProvider.to_model_provider(us_openai_inference_profile) == BedrockModelProvider.OPENAI + + global_openai_inference_profile = "global.openai.gpt-6-luna" + assert BedrockModelProvider.to_model_provider(global_openai_inference_profile) == BedrockModelProvider.OPENAI + unknown_inference_profile = "unknown" with pytest.raises(ValueError, match="Model ID unknown does not contain a valid model provider name."): BedrockModelProvider.to_model_provider(unknown_inference_profile)