# Copyright (c) Microsoft. All rights reserved. import os from collections.abc import AsyncIterator from contextlib import asynccontextmanager from typing import Annotated from unittest.mock import AsyncMock, MagicMock, patch import pytest from agent_framework import ( AgentRunResponse, AgentRunResponseUpdate, ChatAgent, ChatClientProtocol, ChatMessage, ChatOptions, Role, TextContent, ) from agent_framework.exceptions import ServiceInitializationError from azure.ai.projects.aio import AIProjectClient from azure.ai.projects.models import ( ResponseTextFormatConfigurationJsonSchema, ) from azure.identity.aio import AzureCliCredential from openai.types.responses.parsed_response import ParsedResponse from openai.types.responses.response import Response as OpenAIResponse from pydantic import BaseModel, ConfigDict, Field, ValidationError from agent_framework_azure_ai import AzureAIClient, AzureAISettings skip_if_azure_ai_integration_tests_disabled = pytest.mark.skipif( os.getenv("RUN_INTEGRATION_TESTS", "false").lower() != "true" or os.getenv("AZURE_AI_PROJECT_ENDPOINT", "") in ("", "https://test-project.cognitiveservices.azure.com/") or os.getenv("AZURE_AI_MODEL_DEPLOYMENT_NAME", "") == "", reason=( "No real AZURE_AI_PROJECT_ENDPOINT or AZURE_AI_MODEL_DEPLOYMENT_NAME provided; skipping integration tests." if os.getenv("RUN_INTEGRATION_TESTS", "false").lower() == "true" else "Integration tests are disabled." ), ) @asynccontextmanager async def temporary_chat_client(agent_name: str) -> AsyncIterator[AzureAIClient]: """Async context manager that creates an Azure AI agent and yields an `AzureAIClient`. The underlying agent version is cleaned up automatically after use. Tests can construct their own `ChatAgent` instances from the yielded client. """ endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] async with ( AzureCliCredential() as credential, AIProjectClient(endpoint=endpoint, credential=credential) as project_client, ): chat_client = AzureAIClient( project_client=project_client, agent_name=agent_name, ) try: yield chat_client finally: await project_client.agents.delete(agent_name=agent_name) def create_test_azure_ai_client( mock_project_client: MagicMock, agent_name: str | None = None, agent_version: str | None = None, conversation_id: str | None = None, azure_ai_settings: AzureAISettings | None = None, should_close_client: bool = False, use_latest_version: bool | None = None, ) -> AzureAIClient: """Helper function to create AzureAIClient instances for testing, bypassing normal validation.""" if azure_ai_settings is None: azure_ai_settings = AzureAISettings(env_file_path="test.env") # Create client instance directly client = object.__new__(AzureAIClient) # Set attributes directly client.project_client = mock_project_client client.credential = None client.agent_name = agent_name client.agent_version = agent_version client.agent_description = None 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 # Mock the OpenAI client attribute mock_openai_client = MagicMock() mock_openai_client.conversations = MagicMock() mock_openai_client.conversations.create = AsyncMock() client.client = mock_openai_client return client def test_azure_ai_settings_init(azure_ai_unit_test_env: dict[str, str]) -> None: """Test AzureAISettings initialization.""" settings = AzureAISettings() assert settings.project_endpoint == azure_ai_unit_test_env["AZURE_AI_PROJECT_ENDPOINT"] assert settings.model_deployment_name == azure_ai_unit_test_env["AZURE_AI_MODEL_DEPLOYMENT_NAME"] def test_azure_ai_settings_init_with_explicit_values() -> None: """Test AzureAISettings initialization with explicit values.""" settings = AzureAISettings( project_endpoint="https://custom-endpoint.com/", model_deployment_name="custom-model", ) assert settings.project_endpoint == "https://custom-endpoint.com/" assert settings.model_deployment_name == "custom-model" def test_azure_ai_client_init_with_project_client(mock_project_client: MagicMock) -> None: """Test AzureAIClient initialization with existing project_client.""" with patch("agent_framework_azure_ai._client.AzureAISettings") as mock_settings: mock_settings.return_value.project_endpoint = None mock_settings.return_value.model_deployment_name = "test-model" client = AzureAIClient( project_client=mock_project_client, agent_name="test-agent", agent_version="1.0", ) assert client.project_client is mock_project_client assert client.agent_name == "test-agent" assert client.agent_version == "1.0" assert not client._should_close_client # type: ignore assert isinstance(client, ChatClientProtocol) def test_azure_ai_client_init_auto_create_client( azure_ai_unit_test_env: dict[str, str], mock_azure_credential: MagicMock, ) -> None: """Test AzureAIClient initialization with auto-created project_client.""" with patch("agent_framework_azure_ai._client.AIProjectClient") as mock_ai_project_client: mock_project_client = MagicMock() mock_ai_project_client.return_value = mock_project_client client = AzureAIClient( project_endpoint=azure_ai_unit_test_env["AZURE_AI_PROJECT_ENDPOINT"], model_deployment_name=azure_ai_unit_test_env["AZURE_AI_MODEL_DEPLOYMENT_NAME"], credential=mock_azure_credential, agent_name="test-agent", ) assert client.project_client is mock_project_client assert client.agent_name == "test-agent" assert client._should_close_client # type: ignore # Verify AIProjectClient was called with correct parameters mock_ai_project_client.assert_called_once() def test_azure_ai_client_init_missing_project_endpoint() -> None: """Test AzureAIClient initialization when project_endpoint is missing and no project_client provided.""" with patch("agent_framework_azure_ai._client.AzureAISettings") as mock_settings: mock_settings.return_value.project_endpoint = None mock_settings.return_value.model_deployment_name = "test-model" with pytest.raises(ServiceInitializationError, match="Azure AI project endpoint is required"): AzureAIClient(credential=MagicMock()) def test_azure_ai_client_init_missing_credential(azure_ai_unit_test_env: dict[str, str]) -> None: """Test AzureAIClient.__init__ when credential is missing and no project_client provided.""" with pytest.raises( ServiceInitializationError, match="Azure credential is required when project_client is not provided" ): AzureAIClient( project_endpoint=azure_ai_unit_test_env["AZURE_AI_PROJECT_ENDPOINT"], model_deployment_name=azure_ai_unit_test_env["AZURE_AI_MODEL_DEPLOYMENT_NAME"], ) def test_azure_ai_client_init_validation_error(mock_azure_credential: MagicMock) -> None: """Test that ValidationError in AzureAISettings is properly handled.""" with patch("agent_framework_azure_ai._client.AzureAISettings") as mock_settings: mock_settings.side_effect = ValidationError.from_exception_data("test", []) with pytest.raises(ServiceInitializationError, match="Failed to create Azure AI settings"): AzureAIClient(credential=mock_azure_credential) async def test_azure_ai_client_get_agent_reference_or_create_existing_version( mock_project_client: MagicMock, ) -> None: """Test _get_agent_reference_or_create when agent_version is already provided.""" client = create_test_azure_ai_client(mock_project_client, agent_name="existing-agent", agent_version="1.0") agent_ref = await client._get_agent_reference_or_create({}, None) # type: ignore assert agent_ref == {"name": "existing-agent", "version": "1.0", "type": "agent_reference"} async def test_azure_ai_client_get_agent_reference_or_create_missing_agent_name( mock_project_client: MagicMock, ) -> None: """Test _get_agent_reference_or_create raises when agent_name is missing.""" client = create_test_azure_ai_client(mock_project_client, agent_name=None) with pytest.raises(ServiceInitializationError, match="Agent name is required"): await client._get_agent_reference_or_create({}, None) # type: ignore async def test_azure_ai_client_get_agent_reference_or_create_new_agent( mock_project_client: MagicMock, azure_ai_unit_test_env: dict[str, str], ) -> None: """Test _get_agent_reference_or_create when creating a new agent.""" azure_ai_settings = AzureAISettings(model_deployment_name=azure_ai_unit_test_env["AZURE_AI_MODEL_DEPLOYMENT_NAME"]) client = create_test_azure_ai_client( mock_project_client, agent_name="new-agent", azure_ai_settings=azure_ai_settings ) # Mock agent creation response mock_agent = MagicMock() mock_agent.name = "new-agent" mock_agent.version = "1.0" mock_project_client.agents.create_version = AsyncMock(return_value=mock_agent) run_options = {"model": azure_ai_settings.model_deployment_name} agent_ref = await client._get_agent_reference_or_create(run_options, None) # type: ignore assert agent_ref == {"name": "new-agent", "version": "1.0", "type": "agent_reference"} assert client.agent_name == "new-agent" assert client.agent_version == "1.0" async def test_azure_ai_client_get_agent_reference_missing_model( mock_project_client: MagicMock, ) -> None: """Test _get_agent_reference_or_create when model is missing for agent creation.""" client = create_test_azure_ai_client(mock_project_client, agent_name="test-agent") with pytest.raises(ServiceInitializationError, match="Model deployment name is required for agent creation"): await client._get_agent_reference_or_create({}, None) # type: ignore async def test_azure_ai_client_prepare_input_with_system_messages( mock_project_client: MagicMock, ) -> None: """Test _prepare_input converts system/developer messages to instructions.""" client = create_test_azure_ai_client(mock_project_client) messages = [ ChatMessage(role=Role.SYSTEM, contents=[TextContent(text="You are a helpful assistant.")]), ChatMessage(role=Role.USER, contents=[TextContent(text="Hello")]), ChatMessage(role=Role.ASSISTANT, contents=[TextContent(text="System response")]), ] result_messages, instructions = client._prepare_input(messages) # type: ignore assert len(result_messages) == 2 assert result_messages[0].role == Role.USER assert result_messages[1].role == Role.ASSISTANT assert instructions == "You are a helpful assistant." async def test_azure_ai_client_prepare_input_no_system_messages( mock_project_client: MagicMock, ) -> None: """Test _prepare_input with no system/developer messages.""" client = create_test_azure_ai_client(mock_project_client) messages = [ ChatMessage(role=Role.USER, contents=[TextContent(text="Hello")]), ChatMessage(role=Role.ASSISTANT, contents=[TextContent(text="Hi there!")]), ] result_messages, instructions = client._prepare_input(messages) # type: ignore assert len(result_messages) == 2 assert instructions is None async def test_azure_ai_client_prepare_options_basic(mock_project_client: MagicMock) -> None: """Test prepare_options basic functionality.""" client = create_test_azure_ai_client(mock_project_client, agent_name="test-agent", agent_version="1.0") 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.0", "type": "agent_reference"}, ), ): run_options = await client.prepare_options(messages, chat_options) assert "extra_body" in run_options 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", 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) mock_openai_client = MagicMock() mock_project_client.get_openai_client = MagicMock(return_value=mock_openai_client) await client.initialize_client() assert client.client is mock_openai_client mock_project_client.get_openai_client.assert_called_once() def test_azure_ai_client_update_agent_name_and_description(mock_project_client: MagicMock) -> None: """Test _update_agent_name_and_description method.""" client = create_test_azure_ai_client(mock_project_client) # Test updating agent name when current is None with patch.object(client, "_update_agent_name_and_description") as mock_update: mock_update.return_value = None client._update_agent_name_and_description("new-agent") # type: ignore mock_update.assert_called_once_with("new-agent") # Test behavior when agent name is updated assert client.agent_name is None # Should remain None since we didn't actually update client.agent_name = "test-agent" # Manually set for the test # Test with None input with patch.object(client, "_update_agent_name_and_description") as mock_update: mock_update.return_value = None client._update_agent_name_and_description(None) # type: ignore mock_update.assert_called_once_with(None) async def test_azure_ai_client_async_context_manager(mock_project_client: MagicMock) -> None: """Test async context manager functionality.""" client = create_test_azure_ai_client(mock_project_client, should_close_client=True) mock_project_client.close = AsyncMock() async with client as ctx_client: assert ctx_client is client # Should call close after exiting context mock_project_client.close.assert_called_once() async def test_azure_ai_client_close_method(mock_project_client: MagicMock) -> None: """Test close method.""" client = create_test_azure_ai_client(mock_project_client, should_close_client=True) mock_project_client.close = AsyncMock() await client.close() mock_project_client.close.assert_called_once() async def test_azure_ai_client_close_client_when_should_close_false(mock_project_client: MagicMock) -> None: """Test _close_client_if_needed when should_close_client is False.""" client = create_test_azure_ai_client(mock_project_client, should_close_client=False) mock_project_client.close = AsyncMock() await client._close_client_if_needed() # type: ignore # Should not call close when should_close_client is False mock_project_client.close.assert_not_called() async def test_azure_ai_client_agent_creation_with_instructions( mock_project_client: MagicMock, ) -> None: """Test agent creation with combined instructions.""" client = create_test_azure_ai_client(mock_project_client, agent_name="test-agent") # Mock agent creation response mock_agent = MagicMock() mock_agent.name = "test-agent" mock_agent.version = "1.0" mock_project_client.agents.create_version = AsyncMock(return_value=mock_agent) run_options = {"model": "test-model", "instructions": "Option instructions. "} messages_instructions = "Message instructions. " await client._get_agent_reference_or_create(run_options, messages_instructions) # type: ignore # Verify agent was created with combined instructions call_args = mock_project_client.agents.create_version.call_args assert call_args[1]["definition"].instructions == "Message instructions. Option instructions. " async def test_azure_ai_client_agent_creation_with_tools( mock_project_client: MagicMock, ) -> None: """Test agent creation with tools.""" client = create_test_azure_ai_client(mock_project_client, agent_name="test-agent") # Mock agent creation response mock_agent = MagicMock() mock_agent.name = "test-agent" mock_agent.version = "1.0" mock_project_client.agents.create_version = AsyncMock(return_value=mock_agent) test_tools = [{"type": "function", "function": {"name": "test_tool"}}] run_options = {"model": "test-model", "tools": test_tools} await client._get_agent_reference_or_create(run_options, None) # type: ignore # Verify agent was created with tools call_args = mock_project_client.agents.create_version.call_args assert call_args[1]["definition"].tools == test_tools async def test_azure_ai_client_use_latest_version_existing_agent( mock_project_client: MagicMock, ) -> None: """Test _get_agent_reference_or_create when use_latest_version=True and agent exists.""" client = create_test_azure_ai_client(mock_project_client, agent_name="existing-agent", use_latest_version=True) # Mock existing agent response 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) run_options = {"model": "test-model"} agent_ref = await client._get_agent_reference_or_create(run_options, None) # type: ignore # Verify existing agent was retrieved and used mock_project_client.agents.get.assert_called_once_with("existing-agent") mock_project_client.agents.create_version.assert_not_called() assert agent_ref == {"name": "existing-agent", "version": "2.5", "type": "agent_reference"} assert client.agent_name == "existing-agent" assert client.agent_version == "2.5" async def test_azure_ai_client_use_latest_version_agent_not_found( mock_project_client: MagicMock, ) -> None: """Test _get_agent_reference_or_create when use_latest_version=True but agent doesn't exist.""" from azure.core.exceptions import ResourceNotFoundError client = create_test_azure_ai_client(mock_project_client, agent_name="non-existing-agent", use_latest_version=True) # Mock ResourceNotFoundError when trying to retrieve agent mock_project_client.agents.get = AsyncMock(side_effect=ResourceNotFoundError("Agent not found")) # Mock agent creation response for fallback mock_created_agent = MagicMock() mock_created_agent.name = "non-existing-agent" mock_created_agent.version = "1.0" mock_project_client.agents.create_version = AsyncMock(return_value=mock_created_agent) run_options = {"model": "test-model"} agent_ref = await client._get_agent_reference_or_create(run_options, None) # type: ignore # Verify retrieval was attempted and creation was used as fallback mock_project_client.agents.get.assert_called_once_with("non-existing-agent") mock_project_client.agents.create_version.assert_called_once() assert agent_ref == {"name": "non-existing-agent", "version": "1.0", "type": "agent_reference"} assert client.agent_name == "non-existing-agent" assert client.agent_version == "1.0" async def test_azure_ai_client_use_latest_version_false( mock_project_client: MagicMock, ) -> None: """Test _get_agent_reference_or_create when use_latest_version=False (default behavior).""" client = create_test_azure_ai_client(mock_project_client, agent_name="test-agent", use_latest_version=False) # Mock agent creation response mock_created_agent = MagicMock() mock_created_agent.name = "test-agent" mock_created_agent.version = "1.0" mock_project_client.agents.create_version = AsyncMock(return_value=mock_created_agent) run_options = {"model": "test-model"} agent_ref = await client._get_agent_reference_or_create(run_options, None) # type: ignore # Verify retrieval was not attempted and creation was used directly mock_project_client.agents.get.assert_not_called() mock_project_client.agents.create_version.assert_called_once() assert agent_ref == {"name": "test-agent", "version": "1.0", "type": "agent_reference"} async def test_azure_ai_client_use_latest_version_with_existing_agent_version( mock_project_client: MagicMock, ) -> None: """Test that use_latest_version is ignored when agent_version is already provided.""" client = create_test_azure_ai_client( mock_project_client, agent_name="test-agent", agent_version="3.0", use_latest_version=True ) agent_ref = await client._get_agent_reference_or_create({}, None) # type: ignore # Verify neither retrieval nor creation was attempted since version is already set mock_project_client.agents.get.assert_not_called() mock_project_client.agents.create_version.assert_not_called() assert agent_ref == {"name": "test-agent", "version": "3.0", "type": "agent_reference"} class ResponseFormatModel(BaseModel): """Test Pydantic model for response format testing.""" name: str value: int description: str model_config = ConfigDict(extra="forbid") async def test_azure_ai_client_agent_creation_with_response_format( mock_project_client: MagicMock, ) -> None: """Test agent creation with response_format configuration.""" client = create_test_azure_ai_client(mock_project_client, agent_name="test-agent") # Mock agent creation response mock_agent = MagicMock() mock_agent.name = "test-agent" mock_agent.version = "1.0" mock_project_client.agents.create_version = AsyncMock(return_value=mock_agent) run_options = {"model": "test-model", "response_format": ResponseFormatModel} await client._get_agent_reference_or_create(run_options, None) # type: ignore # Verify agent was created with response format configuration call_args = mock_project_client.agents.create_version.call_args created_definition = call_args[1]["definition"] # Check that text format configuration was set assert hasattr(created_definition, "text") assert created_definition.text is not None # Check that the format is a ResponseTextFormatConfigurationJsonSchema assert hasattr(created_definition.text, "format") format_config = created_definition.text.format assert isinstance(format_config, ResponseTextFormatConfigurationJsonSchema) # Check the schema name matches the model class name assert format_config.name == "ResponseFormatModel" # Check that schema was generated correctly assert format_config.schema is not None schema = format_config.schema assert "properties" in schema assert "name" in schema["properties"] assert "value" in schema["properties"] assert "description" in schema["properties"] async def test_azure_ai_client_agent_creation_with_mapping_response_format( mock_project_client: MagicMock, ) -> None: """Test agent creation when response_format is provided as a mapping.""" client = create_test_azure_ai_client(mock_project_client, agent_name="test-agent") mock_agent = MagicMock() mock_agent.name = "test-agent" mock_agent.version = "1.0" mock_project_client.agents.create_version = AsyncMock(return_value=mock_agent) runtime_schema = { "title": "WeatherDigest", "type": "object", "properties": { "location": {"type": "string"}, "conditions": {"type": "string"}, "temperature_c": {"type": "number"}, "advisory": {"type": "string"}, }, "required": ["location", "conditions", "temperature_c", "advisory"], "additionalProperties": False, } run_options = { "model": "test-model", "response_format": { "type": "json_schema", "json_schema": { "name": runtime_schema["title"], "strict": True, "schema": runtime_schema, }, }, } await client._get_agent_reference_or_create(run_options, None) # type: ignore call_args = mock_project_client.agents.create_version.call_args created_definition = call_args[1]["definition"] assert hasattr(created_definition, "text") assert created_definition.text is not None format_config = created_definition.text.format assert isinstance(format_config, ResponseTextFormatConfigurationJsonSchema) assert format_config.name == runtime_schema["title"] assert format_config.schema == runtime_schema assert format_config.strict is True async def test_azure_ai_client_prepare_options_excludes_response_format( mock_project_client: MagicMock, ) -> None: """Test that prepare_options excludes response_format from final run options.""" client = create_test_azure_ai_client(mock_project_client, agent_name="test-agent", agent_version="1.0") 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", "response_format": ResponseFormatModel}, ), patch.object( client, "_get_agent_reference_or_create", return_value={"name": "test-agent", "version": "1.0", "type": "agent_reference"}, ), ): run_options = await client.prepare_options(messages, chat_options) # response_format should be excluded from final run options assert "response_format" not in run_options # But extra_body should contain agent reference assert "extra_body" in run_options assert run_options["extra_body"]["agent"]["name"] == "test-agent" async def test_azure_ai_client_prepare_options_with_resp_conversation_id( mock_project_client: MagicMock, ) -> None: """Test prepare_options with conversation ID starting with 'resp_'.""" client = create_test_azure_ai_client(mock_project_client, agent_name="test-agent", agent_version="1.0") messages = [ChatMessage(role=Role.USER, contents=[TextContent(text="Hello")])] chat_options = ChatOptions(conversation_id="resp_12345") with ( patch.object( client.__class__.__bases__[0], "prepare_options", return_value={"model": "test-model", "previous_response_id": "old_value", "conversation": "old_conv"}, ), patch.object( client, "_get_agent_reference_or_create", return_value={"name": "test-agent", "version": "1.0", "type": "agent_reference"}, ), ): run_options = await client.prepare_options(messages, chat_options) # Should set previous_response_id and remove conversation property assert run_options["previous_response_id"] == "resp_12345" assert "conversation" not in run_options async def test_azure_ai_client_prepare_options_with_conv_conversation_id( mock_project_client: MagicMock, ) -> None: """Test prepare_options with conversation ID starting with 'conv_'.""" client = create_test_azure_ai_client(mock_project_client, agent_name="test-agent", agent_version="1.0") messages = [ChatMessage(role=Role.USER, contents=[TextContent(text="Hello")])] chat_options = ChatOptions(conversation_id="conv_67890") with ( patch.object( client.__class__.__bases__[0], "prepare_options", return_value={"model": "test-model", "previous_response_id": "old_value", "conversation": "old_conv"}, ), patch.object( client, "_get_agent_reference_or_create", return_value={"name": "test-agent", "version": "1.0", "type": "agent_reference"}, ), ): run_options = await client.prepare_options(messages, chat_options) # Should set conversation and remove previous_response_id property assert run_options["conversation"] == "conv_67890" assert "previous_response_id" not in run_options async def test_azure_ai_client_prepare_options_with_client_conversation_id( mock_project_client: MagicMock, ) -> None: """Test prepare_options using client's default conversation ID when chat options don't have one.""" client = create_test_azure_ai_client( mock_project_client, agent_name="test-agent", agent_version="1.0", conversation_id="resp_client_default" ) messages = [ChatMessage(role=Role.USER, contents=[TextContent(text="Hello")])] chat_options = ChatOptions() # No conversation_id specified with ( patch.object( client.__class__.__bases__[0], "prepare_options", return_value={"model": "test-model", "previous_response_id": "old_value", "conversation": "old_conv"}, ), patch.object( client, "_get_agent_reference_or_create", return_value={"name": "test-agent", "version": "1.0", "type": "agent_reference"}, ), ): run_options = await client.prepare_options(messages, chat_options) # Should use client's default conversation_id and set previous_response_id assert run_options["previous_response_id"] == "resp_client_default" assert "conversation" not in run_options def test_get_conversation_id_with_store_true_and_conversation_id() -> None: """Test get_conversation_id returns conversation ID when store is True and conversation exists.""" client = create_test_azure_ai_client(MagicMock()) # Mock OpenAI response with conversation mock_response = MagicMock(spec=OpenAIResponse) mock_response.id = "resp_12345" mock_conversation = MagicMock() mock_conversation.id = "conv_67890" mock_response.conversation = mock_conversation result = client.get_conversation_id(mock_response, store=True) assert result == "conv_67890" def test_get_conversation_id_with_store_true_and_no_conversation() -> None: """Test get_conversation_id returns response ID when store is True and no conversation exists.""" client = create_test_azure_ai_client(MagicMock()) # Mock OpenAI response without conversation mock_response = MagicMock(spec=OpenAIResponse) mock_response.id = "resp_12345" mock_response.conversation = None result = client.get_conversation_id(mock_response, store=True) assert result == "resp_12345" def test_get_conversation_id_with_store_true_and_empty_conversation_id() -> None: """Test get_conversation_id returns response ID when store is True and conversation ID is empty.""" client = create_test_azure_ai_client(MagicMock()) # Mock OpenAI response with conversation but empty ID mock_response = MagicMock(spec=OpenAIResponse) mock_response.id = "resp_12345" mock_conversation = MagicMock() mock_conversation.id = "" mock_response.conversation = mock_conversation result = client.get_conversation_id(mock_response, store=True) assert result == "resp_12345" def test_get_conversation_id_with_store_false() -> None: """Test get_conversation_id returns None when store is False.""" client = create_test_azure_ai_client(MagicMock()) # Mock OpenAI response with conversation mock_response = MagicMock(spec=OpenAIResponse) mock_response.id = "resp_12345" mock_conversation = MagicMock() mock_conversation.id = "conv_67890" mock_response.conversation = mock_conversation result = client.get_conversation_id(mock_response, store=False) assert result is None def test_get_conversation_id_with_parsed_response_and_store_true() -> None: """Test get_conversation_id works with ParsedResponse when store is True.""" client = create_test_azure_ai_client(MagicMock()) # Mock ParsedResponse with conversation mock_response = MagicMock(spec=ParsedResponse[BaseModel]) mock_response.id = "resp_parsed_12345" mock_conversation = MagicMock() mock_conversation.id = "conv_parsed_67890" mock_response.conversation = mock_conversation result = client.get_conversation_id(mock_response, store=True) assert result == "conv_parsed_67890" def test_get_conversation_id_with_parsed_response_no_conversation() -> None: """Test get_conversation_id returns response ID with ParsedResponse when no conversation exists.""" client = create_test_azure_ai_client(MagicMock()) # Mock ParsedResponse without conversation mock_response = MagicMock(spec=ParsedResponse[BaseModel]) mock_response.id = "resp_parsed_12345" mock_response.conversation = None result = client.get_conversation_id(mock_response, store=True) assert result == "resp_parsed_12345" @pytest.fixture def mock_project_client() -> MagicMock: """Fixture that provides a mock AIProjectClient.""" mock_client = MagicMock() # Mock agents property mock_client.agents = MagicMock() mock_client.agents.create_version = AsyncMock() # Mock conversations property mock_client.conversations = MagicMock() mock_client.conversations.create = AsyncMock() # Mock telemetry property mock_client.telemetry = MagicMock() mock_client.telemetry.get_application_insights_connection_string = AsyncMock() # Mock get_openai_client method mock_client.get_openai_client = AsyncMock() # Mock close method mock_client.close = AsyncMock() return mock_client def get_weather( location: Annotated[str, Field(description="The location to get the weather for.")], ) -> str: """Get the weather for a given location.""" return f"The weather in {location} is sunny with a high of 25°C." @pytest.mark.flaky @skip_if_azure_ai_integration_tests_disabled async def test_azure_ai_chat_client_agent_basic_run() -> None: """Test ChatAgent basic run functionality with AzureAIClient.""" async with ( temporary_chat_client(agent_name="BasicRunAgent") as chat_client, ChatAgent(chat_client=chat_client) as agent, ): response = await agent.run("Hello! Please respond with 'Hello World' exactly.") # Validate response assert isinstance(response, AgentRunResponse) assert response.text is not None assert len(response.text) > 0 assert "Hello World" in response.text @pytest.mark.flaky @skip_if_azure_ai_integration_tests_disabled async def test_azure_ai_chat_client_agent_basic_run_streaming() -> None: """Test ChatAgent basic streaming functionality with AzureAIClient.""" async with ( temporary_chat_client(agent_name="BasicRunStreamingAgent") as chat_client, ChatAgent(chat_client=chat_client) as agent, ): full_message: str = "" async for chunk in agent.run_stream("Please respond with exactly: 'This is a streaming response test.'"): assert chunk is not None assert isinstance(chunk, AgentRunResponseUpdate) if chunk.text: full_message += chunk.text # Validate streaming response assert len(full_message) > 0 assert "streaming response test" in full_message.lower() @pytest.mark.flaky @skip_if_azure_ai_integration_tests_disabled async def test_azure_ai_chat_client_agent_with_tools() -> None: """Test ChatAgent tools with AzureAIClient.""" async with ( temporary_chat_client(agent_name="RunToolsAgent") as chat_client, ChatAgent(chat_client=chat_client, tools=[get_weather]) as agent, ): response = await agent.run("What's the weather like in Seattle?") # Validate response assert isinstance(response, AgentRunResponse) assert response.text is not None assert len(response.text) > 0 assert any(word in response.text.lower() for word in ["sunny", "25"])