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..67bd0bae6a 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 project_client._config.endpoint # type: ignore + # 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..8a31abcf35 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 @@ -305,6 +306,84 @@ 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_with_application_endpoint( + 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 + + +@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) 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..89bb77af11 --- /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())