From 6865bcf74896095880b86e344bc8c9decb55848c Mon Sep 17 00:00:00 2001 From: Dmytro Struk <13853051+dmytrostruk@users.noreply.github.com> Date: Tue, 25 Nov 2025 10:41:33 -0800 Subject: [PATCH 1/4] Added support for application endpoints in Azure AI client --- .../agent_framework_azure_ai/_client.py | 12 ++++-- .../azure-ai/tests/test_azure_ai_client.py | 38 ++++++++++++++++++ .../getting_started/agents/azure_ai/README.md | 1 + .../azure_ai_with_application_endpoint.py | 39 +++++++++++++++++++ 4 files changed, 87 insertions(+), 3 deletions(-) create mode 100644 python/samples/getting_started/agents/azure_ai/azure_ai_with_application_endpoint.py 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 cc00a34aa0..cab01e9e49 100644 --- a/python/packages/azure-ai/agent_framework_azure_ai/_client.py +++ b/python/packages/azure-ai/agent_framework_azure_ai/_client.py @@ -155,7 +155,11 @@ def __init__( self.credential = async_credential self.model_id = azure_ai_settings.model_deployment_name self.conversation_id = conversation_id - self._should_close_client = should_close_client # Track whether we should close client connection + + # Track whether the application endpoint is used + self._is_application_endpoint = "/applications/" in azure_ai_settings.project_endpoint + # Track whether we should close client connection + self._should_close_client = should_close_client async def setup_azure_ai_observability(self, enable_sensitive_data: bool | None = None) -> None: """Use this method to setup tracing in your Azure AI Project. @@ -316,9 +320,11 @@ async def prepare_options( """Take ChatOptions and create the specific options for Azure AI.""" prepared_messages, instructions = self._prepare_input(messages) run_options = await super().prepare_options(prepared_messages, chat_options, **kwargs) - agent_reference = await self._get_agent_reference_or_create(run_options, instructions) - run_options["extra_body"] = {"agent": agent_reference} + if not self._is_application_endpoint: + # Application-scoped response APIs do not support "agent" property. + agent_reference = await self._get_agent_reference_or_create(run_options, instructions) + run_options["extra_body"] = {"agent": agent_reference} conversation_id = chat_options.conversation_id or self.conversation_id 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 5d48e7b2be..b17a064858 100644 --- a/python/packages/azure-ai/tests/test_azure_ai_client.py +++ b/python/packages/azure-ai/tests/test_azure_ai_client.py @@ -305,6 +305,44 @@ async def test_azure_ai_client_prepare_options_basic(mock_project_client: MagicM assert run_options["extra_body"]["agent"]["name"] == "test-agent" +@pytest.mark.parametrize( + "endpoint,expects_agent", + [ + ("https://example.com/api/projects/my-project/applications/my-application/protocols", False), + ("https://example.com/api/projects/my-project", True), + ], +) +async def test_azure_ai_client_prepare_options_endpoint_behavior( + mock_azure_credential: MagicMock, endpoint: str, expects_agent: bool +) -> None: + client = AzureAIClient( + project_endpoint=endpoint, + model_deployment_name="test-model", + async_credential=mock_azure_credential, + agent_name="test-agent", + agent_version="1", + ) + + messages = [ChatMessage(role=Role.USER, contents=[TextContent(text="Hello")])] + chat_options = ChatOptions() + + with ( + patch.object(client.__class__.__bases__[0], "prepare_options", return_value={"model": "test-model"}), + patch.object( + client, + "_get_agent_reference_or_create", + return_value={"name": "test-agent", "version": "1", "type": "agent_reference"}, + ), + ): + run_options = await client.prepare_options(messages, chat_options) + + if expects_agent: + assert "extra_body" in run_options + assert run_options["extra_body"]["agent"]["name"] == "test-agent" + else: + assert "extra_body" not in run_options + + async def test_azure_ai_client_initialize_client(mock_project_client: MagicMock) -> None: """Test initialize_client method.""" client = create_test_azure_ai_client(mock_project_client) diff --git a/python/samples/getting_started/agents/azure_ai/README.md b/python/samples/getting_started/agents/azure_ai/README.md index 0513dab1df..17a944524a 100644 --- a/python/samples/getting_started/agents/azure_ai/README.md +++ b/python/samples/getting_started/agents/azure_ai/README.md @@ -16,6 +16,7 @@ This folder contains examples demonstrating different ways to create and use age | [`azure_ai_with_code_interpreter.py`](azure_ai_with_code_interpreter.py) | Shows how to use the `HostedCodeInterpreterTool` with Azure AI agents to write and execute Python code for mathematical problem solving and data analysis. | | [`azure_ai_with_existing_agent.py`](azure_ai_with_existing_agent.py) | Shows how to work with a pre-existing agent by providing the agent name and version to the Azure AI client. Demonstrates agent reuse patterns for production scenarios. | | [`azure_ai_with_existing_conversation.py`](azure_ai_with_existing_conversation.py) | Demonstrates how to use an existing conversation created on the service side with Azure AI agents. Shows two approaches: specifying conversation ID at the client level and using AgentThread with an existing conversation ID. | +| [`azure_ai_with_application_endpoint.py`](azure_ai_with_application_endpoint.py) | Demonstrates calling the Azure AI application-scoped endpoint. | | [`azure_ai_with_explicit_settings.py`](azure_ai_with_explicit_settings.py) | Shows how to create an agent with explicitly configured `AzureAIClient` settings, including project endpoint, model deployment, and credentials rather than relying on environment variable defaults. | | [`azure_ai_with_file_search.py`](azure_ai_with_file_search.py) | Shows how to use the `HostedFileSearchTool` with Azure AI agents to upload files, create vector stores, and enable agents to search through uploaded documents to answer user questions. | | [`azure_ai_with_hosted_mcp.py`](azure_ai_with_hosted_mcp.py) | Shows how to integrate hosted Model Context Protocol (MCP) tools with Azure AI Agent. | diff --git a/python/samples/getting_started/agents/azure_ai/azure_ai_with_application_endpoint.py b/python/samples/getting_started/agents/azure_ai/azure_ai_with_application_endpoint.py new file mode 100644 index 0000000000..969953ac4a --- /dev/null +++ b/python/samples/getting_started/agents/azure_ai/azure_ai_with_application_endpoint.py @@ -0,0 +1,39 @@ +# Copyright (c) Microsoft. All rights reserved. + +import asyncio +import os + +from agent_framework import ChatAgent +from agent_framework.azure import AzureAIClient +from azure.ai.projects.aio import AIProjectClient +from azure.identity.aio import AzureCliCredential + +""" +Azure AI Agent with Application Endpoint Example + +This sample demonstrates working with pre-existing Azure AI Agents by providing +application endpoint instead of project endpoint. +""" + + +async def main() -> None: + # Create the client + async with ( + AzureCliCredential() as credential, + # Endpoint here should be application endpoint with format: + # /api/projects//applications//protocols + AIProjectClient(endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], credential=credential) as project_client, + ChatAgent( + chat_client=AzureAIClient( + project_client=project_client, + ), + ) as agent, + ): + query = "How are you?" + print(f"User: {query}") + result = await agent.run(query) + print(f"Agent: {result}\n") + + +if __name__ == "__main__": + asyncio.run(main()) From 464683e381860d9c475b0a8ad51b55c503e9f7c3 Mon Sep 17 00:00:00 2001 From: Dmytro Struk <13853051+dmytrostruk@users.noreply.github.com> Date: Tue, 25 Nov 2025 10:53:00 -0800 Subject: [PATCH 2/4] Fixed tests --- python/packages/azure-ai/agent_framework_azure_ai/_client.py | 4 +++- python/packages/azure-ai/tests/test_azure_ai_client.py | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) 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 cab01e9e49..95c930f2c0 100644 --- a/python/packages/azure-ai/agent_framework_azure_ai/_client.py +++ b/python/packages/azure-ai/agent_framework_azure_ai/_client.py @@ -157,7 +157,9 @@ def __init__( self.conversation_id = conversation_id # Track whether the application endpoint is used - self._is_application_endpoint = "/applications/" in azure_ai_settings.project_endpoint + self._is_application_endpoint = ( + azure_ai_settings.project_endpoint is not None and "/applications/" in azure_ai_settings.project_endpoint + ) # Track whether we should close client connection self._should_close_client = should_close_client 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 b17a064858..29b5f578c4 100644 --- a/python/packages/azure-ai/tests/test_azure_ai_client.py +++ b/python/packages/azure-ai/tests/test_azure_ai_client.py @@ -87,6 +87,7 @@ def create_test_azure_ai_client( client.use_latest_version = use_latest_version client.model_id = azure_ai_settings.model_deployment_name client.conversation_id = conversation_id + client._is_application_endpoint = False # type: ignore client._should_close_client = should_close_client # type: ignore client.additional_properties = {} client.middleware = None From 9a244ef38c104c2aaa316d08926e124d4aa911e7 Mon Sep 17 00:00:00 2001 From: Dmytro Struk <13853051+dmytrostruk@users.noreply.github.com> Date: Tue, 25 Nov 2025 10:56:42 -0800 Subject: [PATCH 3/4] Update python/samples/getting_started/agents/azure_ai/azure_ai_with_application_endpoint.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../agents/azure_ai/azure_ai_with_application_endpoint.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/samples/getting_started/agents/azure_ai/azure_ai_with_application_endpoint.py b/python/samples/getting_started/agents/azure_ai/azure_ai_with_application_endpoint.py index 969953ac4a..89bb77af11 100644 --- a/python/samples/getting_started/agents/azure_ai/azure_ai_with_application_endpoint.py +++ b/python/samples/getting_started/agents/azure_ai/azure_ai_with_application_endpoint.py @@ -21,7 +21,7 @@ async def main() -> None: async with ( AzureCliCredential() as credential, # Endpoint here should be application endpoint with format: - # /api/projects//applications//protocols + # /api/projects//applications//protocols AIProjectClient(endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], credential=credential) as project_client, ChatAgent( chat_client=AzureAIClient( From 00b48479f63cb470c4c8c9ec898d12b0d141021e Mon Sep 17 00:00:00 2001 From: Dmytro Struk <13853051+dmytrostruk@users.noreply.github.com> Date: Tue, 25 Nov 2025 11:14:06 -0800 Subject: [PATCH 4/4] Addressed comments --- .../agent_framework_azure_ai/_client.py | 4 +- .../azure-ai/tests/test_azure_ai_client.py | 42 ++++++++++++++++++- 2 files changed, 42 insertions(+), 4 deletions(-) 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 95c930f2c0..67bd0bae6a 100644 --- a/python/packages/azure-ai/agent_framework_azure_ai/_client.py +++ b/python/packages/azure-ai/agent_framework_azure_ai/_client.py @@ -157,9 +157,7 @@ def __init__( self.conversation_id = conversation_id # Track whether the application endpoint is used - self._is_application_endpoint = ( - azure_ai_settings.project_endpoint is not None and "/applications/" in azure_ai_settings.project_endpoint - ) + self._is_application_endpoint = "/applications/" in project_client._config.endpoint # type: ignore # Track whether we should close client connection self._should_close_client = should_close_client 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 29b5f578c4..8a31abcf35 100644 --- a/python/packages/azure-ai/tests/test_azure_ai_client.py +++ b/python/packages/azure-ai/tests/test_azure_ai_client.py @@ -313,7 +313,7 @@ async def test_azure_ai_client_prepare_options_basic(mock_project_client: MagicM ("https://example.com/api/projects/my-project", True), ], ) -async def test_azure_ai_client_prepare_options_endpoint_behavior( +async def test_azure_ai_client_prepare_options_with_application_endpoint( mock_azure_credential: MagicMock, endpoint: str, expects_agent: bool ) -> None: client = AzureAIClient( @@ -344,6 +344,46 @@ async def test_azure_ai_client_prepare_options_endpoint_behavior( assert "extra_body" not in run_options +@pytest.mark.parametrize( + "endpoint,expects_agent", + [ + ("https://example.com/api/projects/my-project/applications/my-application/protocols", False), + ("https://example.com/api/projects/my-project", True), + ], +) +async def test_azure_ai_client_prepare_options_with_application_project_client( + mock_project_client: MagicMock, endpoint: str, expects_agent: bool +) -> None: + mock_project_client._config = MagicMock() + mock_project_client._config.endpoint = endpoint + + client = AzureAIClient( + project_client=mock_project_client, + model_deployment_name="test-model", + agent_name="test-agent", + agent_version="1", + ) + + messages = [ChatMessage(role=Role.USER, contents=[TextContent(text="Hello")])] + chat_options = ChatOptions() + + with ( + patch.object(client.__class__.__bases__[0], "prepare_options", return_value={"model": "test-model"}), + patch.object( + client, + "_get_agent_reference_or_create", + return_value={"name": "test-agent", "version": "1", "type": "agent_reference"}, + ), + ): + run_options = await client.prepare_options(messages, chat_options) + + if expects_agent: + assert "extra_body" in run_options + assert run_options["extra_body"]["agent"]["name"] == "test-agent" + else: + assert "extra_body" not in run_options + + async def test_azure_ai_client_initialize_client(mock_project_client: MagicMock) -> None: """Test initialize_client method.""" client = create_test_azure_ai_client(mock_project_client)