diff --git a/python/packages/azure-ai/agent_framework_azure_ai/_client.py b/python/packages/azure-ai/agent_framework_azure_ai/_client.py index 1fc6c7c1c9..1e0ec17a41 100644 --- a/python/packages/azure-ai/agent_framework_azure_ai/_client.py +++ b/python/packages/azure-ai/agent_framework_azure_ai/_client.py @@ -486,21 +486,32 @@ def _remove_agent_level_run_options( runtime_structured_output = self._get_structured_output_signature(chat_options) if runtime_tools is not None or runtime_structured_output is not None: - tools_changed = runtime_tools is not None - structured_output_changed = runtime_structured_output is not None + tools_changed = False + structured_output_changed = False if self.warn_runtime_tools_and_structure_changed: + # We created the agent ourselves so we can compare tool sets. if runtime_tools is not None: tools_changed = self._extract_tool_names(runtime_tools) != self._created_agent_tool_names if runtime_structured_output is not None: structured_output_changed = ( runtime_structured_output != self._created_agent_structured_output_signature ) + else: + # Agent was not created by this client (e.g. use_latest_version + # or explicit agent_version). We have no creation-time baseline + # so only warn when non-empty tools or structured_output are + # supplied — an empty tool list is just the framework default + # and should not trigger a false-positive warning. + if runtime_tools: + tools_changed = True + if runtime_structured_output is not None: + structured_output_changed = True if tools_changed or structured_output_changed: logger.warning( - "AzureAIClient does not support runtime tools or structured_output overrides after agent creation. " - "Use AzureOpenAIResponsesClient instead." + "AzureAIClient does not support runtime tools or structured_output overrides " + "after agent creation. Use AzureOpenAIResponsesClient instead." ) agent_level_option_to_run_keys = { diff --git a/python/packages/azure-ai/tests/test_azure_ai_client.py b/python/packages/azure-ai/tests/test_azure_ai_client.py index f0246f40b2..7900d439a5 100644 --- a/python/packages/azure-ai/tests/test_azure_ai_client.py +++ b/python/packages/azure-ai/tests/test_azure_ai_client.py @@ -914,6 +914,79 @@ async def test_use_latest_version_existing_agent( assert client.agent_version == "2.5" +async def test_use_latest_version_no_spurious_warning_for_empty_tools( + mock_project_client: MagicMock, +) -> None: + """Test that use_latest_version=True does not emit a false-positive tools warning. + + When the agent is fetched via use_latest_version the framework may still + pass an empty runtime tools list (``[]``). The client should not warn + about a tool mismatch in this case because no user-supplied tools are + actually being overridden. Regression test for + https://github.com/microsoft/agent-framework/issues/4681 + """ + client = create_test_azure_ai_client(mock_project_client, agent_name="existing-agent", use_latest_version=True) + + # Mock existing agent + mock_existing_agent = MagicMock() + mock_existing_agent.name = "existing-agent" + mock_existing_agent.versions.latest.version = "2.5" + mock_project_client.agents.get = AsyncMock(return_value=mock_existing_agent) + + messages = [Message(role="user", contents=[Content.from_text(text="Hello")])] + + # Patch logger.warning across BOTH calls — neither should warn + with ( + patch( + "agent_framework.openai._responses_client.RawOpenAIResponsesClient._prepare_options", + return_value={"model": "test-model", "tools": []}, + ), + patch("agent_framework_azure_ai._client.logger.warning") as mock_warning, + ): + # First call fetches the latest version + await client._prepare_options(messages, {}) + # Subsequent call with empty tools + await client._prepare_options(messages, {}) + + mock_warning.assert_not_called() + + +async def test_use_latest_version_warns_for_non_empty_tools( + mock_project_client: MagicMock, +) -> None: + """Test that use_latest_version=True with non-empty tools DOES emit a warning. + + Companion to the empty-tools test above. When the user supplies actual + runtime tools while use_latest_version=True, the client should warn that + the tools differ from the agent's creation-time configuration. + """ + client = create_test_azure_ai_client(mock_project_client, agent_name="existing-agent", use_latest_version=True) + + # Mock existing agent + mock_existing_agent = MagicMock() + mock_existing_agent.name = "existing-agent" + mock_existing_agent.versions.latest.version = "2.5" + mock_project_client.agents.get = AsyncMock(return_value=mock_existing_agent) + + messages = [Message(role="user", contents=[Content.from_text(text="Hello")])] + + non_empty_tools = [{"type": "function", "function": {"name": "my_tool"}}] + + with ( + patch( + "agent_framework.openai._responses_client.RawOpenAIResponsesClient._prepare_options", + return_value={"model": "test-model", "tools": non_empty_tools}, + ), + patch("agent_framework_azure_ai._client.logger.warning") as mock_warning, + ): + # First call fetches the latest version + await client._prepare_options(messages, {}) + # Subsequent call with non-empty tools — SHOULD warn + await client._prepare_options(messages, {}) + + mock_warning.assert_called() + + async def test_use_latest_version_agent_not_found( mock_project_client: MagicMock, ) -> None: