From 24d10e9ed2104c8086388d90f7e983d1fd850ad9 Mon Sep 17 00:00:00 2001 From: Ali Zulfiqar Date: Sat, 31 Oct 2026 16:02:18 +0500 Subject: [PATCH] Python: Send the NVIDIA nvext setting in extra_body NvidiaChatPromptExecutionSettings.nvext was dumped as a top-level argument of chat.completions.create(), which rejects it with a TypeError, so every request that set nvext failed. Move it into extra_body and merge it with the guided_json that response_format puts there, and drop the handler's copy of extra_body["nvext"], which would overwrite the merged value. --- .../nvidia_prompt_execution_settings.py | 14 +++++-- .../ai/nvidia/services/nvidia_handler.py | 7 ---- .../services/test_nvidia_chat_completion.py | 40 +++++++++++++++++++ 3 files changed, 51 insertions(+), 10 deletions(-) diff --git a/python/semantic_kernel/connectors/ai/nvidia/prompt_execution_settings/nvidia_prompt_execution_settings.py b/python/semantic_kernel/connectors/ai/nvidia/prompt_execution_settings/nvidia_prompt_execution_settings.py index 85f2d49dce05..e5cad1103464 100644 --- a/python/semantic_kernel/connectors/ai/nvidia/prompt_execution_settings/nvidia_prompt_execution_settings.py +++ b/python/semantic_kernel/connectors/ai/nvidia/prompt_execution_settings/nvidia_prompt_execution_settings.py @@ -65,9 +65,17 @@ class NvidiaChatPromptExecutionSettings(NvidiaPromptExecutionSettings): nvext: dict[str, Any] | None = None def prepare_settings_dict(self, **kwargs) -> dict[str, Any]: - """Override only for embeddings to exclude input_type and truncate.""" - return self.model_dump( - exclude={"service_id", "extension_data", "structured_json_response", "response_format"}, + """Prepare the settings dict, moving nvext into extra_body. + + The OpenAI client only sends nvext as part of extra_body. Keys already in extra_body["nvext"], + such as the guided_json set from response_format, take precedence over the same keys in nvext. + """ + settings_dict = self.model_dump( + exclude={"service_id", "extension_data", "structured_json_response", "response_format", "nvext"}, exclude_none=True, by_alias=True, ) + if self.nvext: + extra_body = settings_dict.setdefault("extra_body", {}) + extra_body["nvext"] = self.nvext | extra_body.get("nvext", {}) + return settings_dict diff --git a/python/semantic_kernel/connectors/ai/nvidia/services/nvidia_handler.py b/python/semantic_kernel/connectors/ai/nvidia/services/nvidia_handler.py index 0f7efff01703..49765c4a3a40 100644 --- a/python/semantic_kernel/connectors/ai/nvidia/services/nvidia_handler.py +++ b/python/semantic_kernel/connectors/ai/nvidia/services/nvidia_handler.py @@ -66,13 +66,6 @@ async def _send_chat_completion_request( """Send a request to the NVIDIA chat completion endpoint.""" try: settings_dict = settings.prepare_settings_dict() - - # Handle structured output if nvext is present in extra_body - if settings.extra_body and "nvext" in settings.extra_body: - if "extra_body" not in settings_dict: - settings_dict["extra_body"] = {} - settings_dict["extra_body"]["nvext"] = settings.extra_body["nvext"] - response = await self.client.chat.completions.create(**settings_dict) self.store_usage(response) return response diff --git a/python/tests/unit/connectors/ai/nvidia/services/test_nvidia_chat_completion.py b/python/tests/unit/connectors/ai/nvidia/services/test_nvidia_chat_completion.py index a4008f9a1c05..eb2560b443cf 100644 --- a/python/tests/unit/connectors/ai/nvidia/services/test_nvidia_chat_completion.py +++ b/python/tests/unit/connectors/ai/nvidia/services/test_nvidia_chat_completion.py @@ -136,6 +136,46 @@ class TestModel(BaseModel): assert "nvext" in call_args["extra_body"] assert "guided_json" in call_args["extra_body"]["nvext"] + @pytest.mark.asyncio + @patch.object(AsyncCompletions, "create", new_callable=AsyncMock) + async def test_nvext_is_sent_in_extra_body(self, mock_create, nvidia_unit_test_env): + """Test that the nvext setting is sent in extra_body, not as a create() argument.""" + mock_create.return_value = _create_mock_chat_completion() + + service = NvidiaChatCompletion() + chat_history = ChatHistory() + chat_history.add_user_message("Hello") + settings = NvidiaChatPromptExecutionSettings(nvext={"guided_choice": ["yes", "no"]}) + + await service.get_chat_message_contents(chat_history, settings) + + call_args = mock_create.call_args[1] + assert "nvext" not in call_args + assert call_args["extra_body"]["nvext"] == {"guided_choice": ["yes", "no"]} + + @pytest.mark.asyncio + @patch.object(AsyncCompletions, "create", new_callable=AsyncMock) + async def test_nvext_is_merged_with_structured_output(self, mock_create, nvidia_unit_test_env): + """Test that nvext keys are kept next to the guided_json set from response_format.""" + + class TestModel(BaseModel): + name: str + + mock_create.return_value = _create_mock_chat_completion('{"name": "test"}') + + service = NvidiaChatCompletion() + chat_history = ChatHistory() + chat_history.add_user_message("Give me structured data") + settings = NvidiaChatPromptExecutionSettings(nvext={"repetition_penalty": 1.1}, response_format=TestModel) + + await service.get_chat_message_contents(chat_history, settings) + + call_args = mock_create.call_args[1] + assert call_args["extra_body"]["nvext"] == { + "repetition_penalty": 1.1, + "guided_json": TestModel.model_json_schema(), + } + @pytest.mark.asyncio @patch.object(AsyncCompletions, "create", new_callable=AsyncMock) async def test_error_handling(self, mock_create, nvidia_unit_test_env):