diff --git a/python/packages/openai/agent_framework_openai/_chat_completion_client.py b/python/packages/openai/agent_framework_openai/_chat_completion_client.py index d64933fae8..fee47e54da 100644 --- a/python/packages/openai/agent_framework_openai/_chat_completion_client.py +++ b/python/packages/openai/agent_framework_openai/_chat_completion_client.py @@ -85,6 +85,14 @@ logger = logging.getLogger("agent_framework.openai") +# Error message shared with tests — extracted to a constant to keep the +# implementation and its assertions in sync. +_AZURE_WEB_SEARCH_UNSUPPORTED_MSG = ( + "Web search is not supported by the Azure OpenAI Chat Completions API. " + "Use agent_framework.openai.OpenAIChatClient (Responses API) for " + "web search support on Azure." +) + DEFAULT_AZURE_OPENAI_CHAT_COMPLETION_API_VERSION = "2024-12-01-preview" ResponseModelBoundT = TypeVar("ResponseModelBoundT", bound=BaseModel) @@ -373,6 +381,7 @@ def __init__( else: self.default_headers = None self.instruction_role = instruction_role + self._use_azure_client = use_azure_client if use_azure_client: self.OTEL_PROVIDER_NAME = "azure.ai.openai" # type: ignore[misc] @@ -589,6 +598,12 @@ def _prepare_tools_for_openai( Converts FunctionTool to JSON schema format. Web search tools are routed to web_search_options parameter. All other tools pass through unchanged. + Note: + Azure OpenAI Chat Completions API does not support ``web_search_options``. + When configured with an Azure endpoint, passing web search tools raises + :class:`ValueError`. Use :class:`~agent_framework.openai.OpenAIChatClient` + (Responses API) for web search support on Azure. + Args: tools: Tool(s) to prepare. @@ -603,6 +618,8 @@ def _prepare_tools_for_openai( elif isinstance(tool, MutableMapping): typed_tool = cast(MutableMapping[str, Any], tool) if typed_tool.get("type") == "web_search": + if self._use_azure_client: + raise ValueError(_AZURE_WEB_SEARCH_UNSUPPORTED_MSG) # Web search is handled via web_search_options, not tools array web_search_options = {k: v for k, v in typed_tool.items() if k != "type"} else: diff --git a/python/packages/openai/tests/openai/test_openai_chat_completion_client.py b/python/packages/openai/tests/openai/test_openai_chat_completion_client.py index 2489eeaf23..8183678a5c 100644 --- a/python/packages/openai/tests/openai/test_openai_chat_completion_client.py +++ b/python/packages/openai/tests/openai/test_openai_chat_completion_client.py @@ -3,6 +3,7 @@ import inspect import json import os +import re from typing import Any, cast from unittest.mock import MagicMock, patch @@ -28,6 +29,9 @@ from pytest import param from agent_framework_openai import OpenAIChatCompletionClient, RawOpenAIChatCompletionClient +from agent_framework_openai._chat_completion_client import ( + _AZURE_WEB_SEARCH_UNSUPPORTED_MSG, +) from agent_framework_openai._exceptions import OpenAIContentFilterException skip_if_openai_integration_tests_disabled = pytest.mark.skipif( @@ -1252,6 +1256,39 @@ def test_prepare_tools_with_web_search_no_location( assert result["web_search_options"] == {} +def test_prepare_tools_with_web_search_on_azure_raises( + openai_unit_test_env: dict[str, str], +) -> None: + """Test that web search raises ValueError when configured with Azure endpoint.""" + client = OpenAIChatCompletionClient( + azure_endpoint="https://test.openai.azure.com", + model="gpt-4o-mini", + api_key="test-key", + ) + + web_search_tool = OpenAIChatCompletionClient.get_web_search_tool() + + with pytest.raises( + ValueError, + match=re.escape(_AZURE_WEB_SEARCH_UNSUPPORTED_MSG), + ): + client._prepare_tools_for_openai([web_search_tool]) + + +def test_prepare_tools_with_web_search_on_openai_allowed( + openai_unit_test_env: dict[str, str], +) -> None: + """Test that web search works normally on non-Azure client.""" + client = OpenAIChatCompletionClient() + + web_search_tool = OpenAIChatCompletionClient.get_web_search_tool() + + result = client._prepare_tools_for_openai([web_search_tool]) + + # Non-Azure client should include web_search_options + assert "web_search_options" in result + + def test_prepare_options_with_instructions( openai_unit_test_env: dict[str, str], ) -> None: