Files
agent-framework/python/packages/azure-ai/tests/test_provider.py
Eduard van Valkenburg 5e056b672e Python: [BREAKING] Python: Provider-leading client design & OpenAI package extraction (#4818)
* Python: Provider-leading client design & OpenAI package extraction

Major refactoring of the Python Agent Framework client architecture:

- Extract OpenAI clients into new `agent-framework-openai` package
- Core package no longer depends on openai, azure-identity, azure-ai-projects
- Rename clients for discoverability: OpenAIResponsesClient → OpenAIChatClient,
  OpenAIChatClient → OpenAIChatCompletionClient
- Unify `model_id`/`deployment_name`/`model_deployment_name` → `model` param
- New FoundryChatClient for Azure AI Foundry Responses API
- New FoundryAgent/FoundryAgentClient for connecting to pre-configured Foundry agents
- Remove OpenAIBase/OpenAIConfigMixin from non-deprecated client MRO
- Deprecate AzureOpenAI* clients, AzureAIClient, OpenAIAssistantsClient
- Reorganize samples: azure_openai+azure_ai+azure_ai_agent → azure/
- ADR-0020: Provider-Leading Client Design

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: missing Agent imports in samples, .model_id → .model in foundry_local sample

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: CI failures — mypy errors, coverage targets, sample imports

- azure-ai mypy: add type ignores for TypedDict total=, model arg, forward ref
- Coverage: replace core.azure/openai targets with openai package target
- project_provider: add type annotation for opts dict

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: populate openai .pyi stub, fix broken README links, coverage targets

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fixes

* updated observabilitty

* reset azure init.pyi

* fix errors

* updated adr number

* fix foundry local

* fixed not renamed docstrings and comments, and added deprecated markers to old classes

* fix tests and pyprojects

* fix test vars

* updated function tests

* update durable

* updated test setup for functions

* Fix Foundry auth in workflow samples

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Stabilize Python integration workflows

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Update hosting samples for Foundry

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Trigger full CI rerun

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Trigger CI rerun again

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* trigger rerun

* trigger rerun

* fix for litellm

* undo durabletask changes

* Move Foundry APIs into foundry namespace

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix Foundry pyproject formatting

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Split provider samples by Foundry surface

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Restore hosting sample requirements

Also fix the Foundry Local sample link after the provider sample move.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* updated tests

* udpated foundry integration tests

* removed dist from azurefunctions tests

* Use separate Foundry clients for concurrent agents

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix client setup in azfunc and durable

* disabled two tests

* updated setup for some function and durable tests

* improved azure openai setup with new clients

* ignore deprecated

* fixes

* skip 11

* remove openai assistants int tests

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-25 09:56:29 +00:00

683 lines
28 KiB
Python

# Copyright (c) Microsoft. All rights reserved.
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from agent_framework import Agent, FunctionTool
from agent_framework._mcp import MCPTool
from azure.ai.projects.models import (
AgentVersionDetails,
PromptAgentDefinition,
)
from azure.ai.projects.models import (
FunctionTool as AzureFunctionTool,
)
from agent_framework_azure_ai import AzureAIProjectAgentProvider
@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
@pytest.fixture
def mock_azure_credential() -> MagicMock:
"""Fixture that provides a mock Azure credential."""
return MagicMock()
@pytest.fixture
def azure_ai_unit_test_env(monkeypatch: pytest.MonkeyPatch) -> dict[str, str]:
"""Fixture that sets up Azure AI environment variables for unit testing."""
env_vars = {
"AZURE_AI_PROJECT_ENDPOINT": "https://test-project.cognitiveservices.azure.com/",
"AZURE_AI_MODEL_DEPLOYMENT_NAME": "test-model-deployment",
}
for key, value in env_vars.items():
monkeypatch.setenv(key, value)
return env_vars
def test_provider_init_with_project_client(mock_project_client: MagicMock) -> None:
"""Test AzureAIProjectAgentProvider initialization with existing project_client."""
provider = AzureAIProjectAgentProvider(project_client=mock_project_client)
assert provider._project_client is mock_project_client # type: ignore
assert not provider._should_close_client # type: ignore
def test_provider_init_with_credential_and_endpoint(
azure_ai_unit_test_env: dict[str, str],
mock_azure_credential: MagicMock,
) -> None:
"""Test AzureAIProjectAgentProvider initialization with credential and endpoint."""
with patch("agent_framework_azure_ai._project_provider.AIProjectClient") as mock_ai_project_client:
mock_client = MagicMock()
mock_ai_project_client.return_value = mock_client
provider = AzureAIProjectAgentProvider(
project_endpoint=azure_ai_unit_test_env["AZURE_AI_PROJECT_ENDPOINT"],
credential=mock_azure_credential,
)
assert provider._project_client is mock_client # type: ignore
assert provider._should_close_client # type: ignore
# Verify AIProjectClient was called with correct parameters
mock_ai_project_client.assert_called_once()
def test_provider_init_missing_endpoint() -> None:
"""Test AzureAIProjectAgentProvider initialization when endpoint is missing."""
with patch("agent_framework_azure_ai._project_provider.load_settings") as mock_load_settings:
mock_load_settings.return_value = {"project_endpoint": None, "model_deployment_name": "test-model"}
with pytest.raises(ValueError, match="Azure AI project endpoint is required"):
AzureAIProjectAgentProvider(credential=MagicMock())
def test_provider_init_missing_credential(azure_ai_unit_test_env: dict[str, str]) -> None:
"""Test AzureAIProjectAgentProvider initialization when credential is missing."""
with pytest.raises(ValueError, match="Azure credential is required when project_client is not provided"):
AzureAIProjectAgentProvider(
project_endpoint=azure_ai_unit_test_env["AZURE_AI_PROJECT_ENDPOINT"],
)
async def test_provider_create_agent(
mock_project_client: MagicMock,
azure_ai_unit_test_env: dict[str, str],
) -> None:
"""Test AzureAIProjectAgentProvider.create_agent method."""
with patch("agent_framework_azure_ai._project_provider.load_settings") as mock_load_settings:
mock_load_settings.return_value = {
"project_endpoint": azure_ai_unit_test_env["AZURE_AI_PROJECT_ENDPOINT"],
"model_deployment_name": azure_ai_unit_test_env["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
}
provider = AzureAIProjectAgentProvider(project_client=mock_project_client)
# Mock agent creation response
mock_agent_version = MagicMock(spec=AgentVersionDetails)
mock_agent_version.id = "agent-id"
mock_agent_version.name = "test-agent"
mock_agent_version.version = "1.0"
mock_agent_version.description = "Test Agent"
mock_agent_version.definition = MagicMock(spec=PromptAgentDefinition)
mock_agent_version.definition.model = "gpt-4"
mock_agent_version.definition.instructions = "Test instructions"
mock_agent_version.definition.temperature = 0.7
mock_agent_version.definition.top_p = 0.9
mock_agent_version.definition.tools = []
mock_project_client.agents.create_version = AsyncMock(return_value=mock_agent_version)
agent = await provider.create_agent(
name="test-agent",
model="gpt-4",
instructions="Test instructions",
description="Test Agent",
)
assert isinstance(agent, Agent)
assert agent.name == "test-agent"
mock_project_client.agents.create_version.assert_called_once()
async def test_provider_create_agent_with_env_model(
mock_project_client: MagicMock,
azure_ai_unit_test_env: dict[str, str],
) -> None:
"""Test AzureAIProjectAgentProvider.create_agent uses model from env var."""
with patch("agent_framework_azure_ai._project_provider.load_settings") as mock_load_settings:
mock_load_settings.return_value = {
"project_endpoint": azure_ai_unit_test_env["AZURE_AI_PROJECT_ENDPOINT"],
"model_deployment_name": azure_ai_unit_test_env["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
}
provider = AzureAIProjectAgentProvider(project_client=mock_project_client)
# Mock agent creation response
mock_agent_version = MagicMock(spec=AgentVersionDetails)
mock_agent_version.id = "agent-id"
mock_agent_version.name = "test-agent"
mock_agent_version.version = "1.0"
mock_agent_version.description = None
mock_agent_version.definition = MagicMock(spec=PromptAgentDefinition)
mock_agent_version.definition.model = azure_ai_unit_test_env["AZURE_AI_MODEL_DEPLOYMENT_NAME"]
mock_agent_version.definition.instructions = None
mock_agent_version.definition.temperature = None
mock_agent_version.definition.top_p = None
mock_agent_version.definition.tools = []
mock_project_client.agents.create_version = AsyncMock(return_value=mock_agent_version)
# Call without model parameter - should use env var
agent = await provider.create_agent(name="test-agent")
assert isinstance(agent, Agent)
# Verify the model from env var was used
call_args = mock_project_client.agents.create_version.call_args
assert call_args[1]["definition"].model == azure_ai_unit_test_env["AZURE_AI_MODEL_DEPLOYMENT_NAME"]
async def test_provider_create_agent_missing_model(mock_project_client: MagicMock) -> None:
"""Test AzureAIProjectAgentProvider.create_agent raises when model is missing."""
with patch("agent_framework_azure_ai._project_provider.load_settings") as mock_load_settings:
mock_load_settings.return_value = {"project_endpoint": "https://test.com", "model_deployment_name": None}
provider = AzureAIProjectAgentProvider(project_client=mock_project_client)
with pytest.raises(ValueError, match="Model deployment name is required"):
await provider.create_agent(name="test-agent")
async def test_provider_create_agent_with_rai_config(
mock_project_client: MagicMock,
azure_ai_unit_test_env: dict[str, str],
) -> None:
"""Test AzureAIProjectAgentProvider.create_agent passes rai_config from default_options."""
with patch("agent_framework_azure_ai._project_provider.load_settings") as mock_load_settings:
mock_load_settings.return_value = {
"project_endpoint": azure_ai_unit_test_env["AZURE_AI_PROJECT_ENDPOINT"],
"model_deployment_name": azure_ai_unit_test_env["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
}
provider = AzureAIProjectAgentProvider(project_client=mock_project_client)
# Mock agent creation response
mock_agent_version = MagicMock(spec=AgentVersionDetails)
mock_agent_version.id = "agent-id"
mock_agent_version.name = "test-agent"
mock_agent_version.version = "1.0"
mock_agent_version.description = None
mock_agent_version.definition = MagicMock(spec=PromptAgentDefinition)
mock_agent_version.definition.model = "gpt-4"
mock_agent_version.definition.instructions = None
mock_agent_version.definition.temperature = None
mock_agent_version.definition.top_p = None
mock_agent_version.definition.tools = []
mock_project_client.agents.create_version = AsyncMock(return_value=mock_agent_version)
# Create a mock RaiConfig-like object
mock_rai_config = MagicMock()
mock_rai_config.rai_policy_name = "policy-name"
# Call create_agent with rai_config in default_options
await provider.create_agent(
name="test-agent",
model="gpt-4",
default_options={"rai_config": mock_rai_config},
)
# Verify rai_config was passed to PromptAgentDefinition
call_args = mock_project_client.agents.create_version.call_args
definition = call_args[1]["definition"]
assert definition.rai_config is mock_rai_config
async def test_provider_create_agent_with_reasoning(
mock_project_client: MagicMock,
azure_ai_unit_test_env: dict[str, str],
) -> None:
"""Test AzureAIProjectAgentProvider.create_agent passes reasoning from default_options."""
with patch("agent_framework_azure_ai._project_provider.load_settings") as mock_load_settings:
mock_load_settings.return_value = {
"project_endpoint": azure_ai_unit_test_env["AZURE_AI_PROJECT_ENDPOINT"],
"model_deployment_name": azure_ai_unit_test_env["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
}
provider = AzureAIProjectAgentProvider(project_client=mock_project_client)
# Mock agent creation response
mock_agent_version = MagicMock(spec=AgentVersionDetails)
mock_agent_version.id = "agent-id"
mock_agent_version.name = "test-agent"
mock_agent_version.version = "1.0"
mock_agent_version.description = None
mock_agent_version.definition = MagicMock(spec=PromptAgentDefinition)
mock_agent_version.definition.model = "gpt-5.2"
mock_agent_version.definition.instructions = None
mock_agent_version.definition.temperature = None
mock_agent_version.definition.top_p = None
mock_agent_version.definition.tools = []
mock_project_client.agents.create_version = AsyncMock(return_value=mock_agent_version)
# Create a mock Reasoning-like object
mock_reasoning = MagicMock()
mock_reasoning.effort = "medium"
mock_reasoning.summary = "concise"
# Call create_agent with reasoning in default_options
await provider.create_agent(
name="test-agent",
model="gpt-5.2",
default_options={"reasoning": mock_reasoning},
)
# Verify reasoning was passed to PromptAgentDefinition
call_args = mock_project_client.agents.create_version.call_args
definition = call_args[1]["definition"]
assert definition.reasoning is mock_reasoning
async def test_provider_get_agent_with_name(mock_project_client: MagicMock) -> None:
"""Test AzureAIProjectAgentProvider.get_agent with name parameter."""
provider = AzureAIProjectAgentProvider(project_client=mock_project_client)
# Mock agent response
mock_agent_version = MagicMock(spec=AgentVersionDetails)
mock_agent_version.id = "agent-id"
mock_agent_version.name = "test-agent"
mock_agent_version.version = "1.0"
mock_agent_version.description = "Test Agent"
mock_agent_version.definition = MagicMock(spec=PromptAgentDefinition)
mock_agent_version.definition.model = "gpt-4"
mock_agent_version.definition.instructions = "Test instructions"
mock_agent_version.definition.temperature = None
mock_agent_version.definition.top_p = None
mock_agent_version.definition.tools = []
mock_agent_object = MagicMock()
mock_agent_object.versions.latest = mock_agent_version
mock_project_client.agents = AsyncMock()
mock_project_client.agents.get.return_value = mock_agent_object
agent = await provider.get_agent(name="test-agent")
assert isinstance(agent, Agent)
assert agent.name == "test-agent"
mock_project_client.agents.get.assert_called_with(agent_name="test-agent")
async def test_provider_get_agent_with_reference(mock_project_client: MagicMock) -> None:
"""Test AzureAIProjectAgentProvider.get_agent with reference parameter."""
provider = AzureAIProjectAgentProvider(project_client=mock_project_client)
# Mock agent response
mock_agent_version = MagicMock(spec=AgentVersionDetails)
mock_agent_version.id = "agent-id"
mock_agent_version.name = "test-agent"
mock_agent_version.version = "1.0"
mock_agent_version.description = "Test Agent"
mock_agent_version.definition = MagicMock(spec=PromptAgentDefinition)
mock_agent_version.definition.model = "gpt-4"
mock_agent_version.definition.instructions = "Test instructions"
mock_agent_version.definition.temperature = None
mock_agent_version.definition.top_p = None
mock_agent_version.definition.tools = []
mock_project_client.agents = AsyncMock()
mock_project_client.agents.get_version.return_value = mock_agent_version
agent_reference = {"name": "test-agent", "version": "1.0"}
agent = await provider.get_agent(reference=agent_reference)
assert isinstance(agent, Agent)
assert agent.name == "test-agent"
mock_project_client.agents.get_version.assert_called_with(agent_name="test-agent", agent_version="1.0")
async def test_provider_get_agent_missing_parameters(mock_project_client: MagicMock) -> None:
"""Test AzureAIProjectAgentProvider.get_agent raises when no identifier provided."""
provider = AzureAIProjectAgentProvider(project_client=mock_project_client)
with pytest.raises(ValueError, match="Either name or reference must be provided"):
await provider.get_agent()
async def test_provider_get_agent_missing_function_tools(mock_project_client: MagicMock) -> None:
"""Test AzureAIProjectAgentProvider.get_agent raises when required tools are missing."""
provider = AzureAIProjectAgentProvider(project_client=mock_project_client)
# Mock agent with function tools
mock_agent_version = MagicMock(spec=AgentVersionDetails)
mock_agent_version.id = "agent-id"
mock_agent_version.name = "test-agent"
mock_agent_version.version = "1.0"
mock_agent_version.description = None
mock_agent_version.definition = MagicMock(spec=PromptAgentDefinition)
mock_agent_version.definition.tools = [
AzureFunctionTool(name="test_tool", parameters=[], strict=True, description="Test tool")
]
mock_agent_object = MagicMock()
mock_agent_object.versions.latest = mock_agent_version
mock_project_client.agents = AsyncMock()
mock_project_client.agents.get.return_value = mock_agent_object
with pytest.raises(
ValueError, match="The following prompt agent definition required tools were not provided: test_tool"
):
await provider.get_agent(name="test-agent")
def test_provider_as_agent(mock_project_client: MagicMock) -> None:
"""Test AzureAIProjectAgentProvider.as_agent method."""
provider = AzureAIProjectAgentProvider(project_client=mock_project_client)
# Create mock agent version
mock_agent_version = MagicMock(spec=AgentVersionDetails)
mock_agent_version.id = "agent-id"
mock_agent_version.name = "test-agent"
mock_agent_version.version = "1.0"
mock_agent_version.description = "Test Agent"
mock_agent_version.definition = MagicMock(spec=PromptAgentDefinition)
mock_agent_version.definition.model = "gpt-4"
mock_agent_version.definition.instructions = "Test instructions"
mock_agent_version.definition.temperature = 0.7
mock_agent_version.definition.top_p = 0.9
mock_agent_version.definition.tools = []
with patch("agent_framework_azure_ai._project_provider.AzureAIClient") as mock_azure_ai_client:
agent = provider.as_agent(mock_agent_version)
assert isinstance(agent, Agent)
assert agent.name == "test-agent"
assert agent.description == "Test Agent"
# Verify AzureAIClient was called with correct parameters
mock_azure_ai_client.assert_called_once()
call_kwargs = mock_azure_ai_client.call_args[1]
assert call_kwargs["project_client"] is mock_project_client
assert call_kwargs["agent_name"] == "test-agent"
assert call_kwargs["agent_version"] == "1.0"
assert call_kwargs["agent_description"] == "Test Agent"
assert call_kwargs["model_deployment_name"] == "gpt-4"
def test_provider_merge_tools_skips_function_tool_dicts(mock_project_client: MagicMock) -> None:
"""Test that _merge_tools skips function tool dicts but keeps other hosted tools."""
provider = AzureAIProjectAgentProvider(project_client=mock_project_client)
# Create a mock FunctionTool to provide as implementation
mock_ai_function = create_mock_ai_function("my_function", "My function description")
# Definition tools include a function tool (dict) and an MCP tool
definition_tools = [
{"type": "function", "name": "my_function", "parameters": {}}, # Should be skipped
{"type": "mcp", "server_label": "my_mcp", "server_url": "http://localhost:8080"}, # Should be converted
]
# Call _merge_tools with user-provided function implementation
merged = provider._merge_tools(definition_tools, [mock_ai_function]) # type: ignore
# Should have 2 items: the converted MCP dict and the user-provided FunctionTool
assert len(merged) == 2
# Check that the function tool dict was NOT included (it was skipped)
function_dicts = [t for t in merged if isinstance(t, dict) and t.get("type") == "function"]
assert len(function_dicts) == 0
# Check that the MCP tool was converted to dict
mcp_tools = [t for t in merged if isinstance(t, dict) and t.get("type") == "mcp"]
assert len(mcp_tools) == 1
assert mcp_tools[0]["server_label"] == "my_mcp"
# Check that the user-provided FunctionTool was included
ai_functions = [t for t in merged if isinstance(t, FunctionTool)]
assert len(ai_functions) == 1
assert ai_functions[0].name == "my_function"
async def test_provider_context_manager(mock_project_client: MagicMock) -> None:
"""Test AzureAIProjectAgentProvider async context manager."""
with patch("agent_framework_azure_ai._project_provider.AIProjectClient") as mock_ai_project_client:
mock_client = MagicMock()
mock_client.close = AsyncMock()
mock_ai_project_client.return_value = mock_client
with patch("agent_framework_azure_ai._project_provider.load_settings") as mock_load_settings:
mock_load_settings.return_value = {
"project_endpoint": "https://test.com",
"model_deployment_name": "test-model",
}
async with AzureAIProjectAgentProvider(credential=MagicMock()) as provider:
assert provider._project_client is mock_client # type: ignore
# Should call close after exiting context
mock_client.close.assert_called_once()
async def test_provider_context_manager_with_provided_client(mock_project_client: MagicMock) -> None:
"""Test AzureAIProjectAgentProvider context manager doesn't close provided client."""
mock_project_client.close = AsyncMock()
async with AzureAIProjectAgentProvider(project_client=mock_project_client) as provider:
assert provider._project_client is mock_project_client # type: ignore
# Should NOT call close when client was provided
mock_project_client.close.assert_not_called()
async def test_provider_close_method(mock_project_client: MagicMock) -> None:
"""Test AzureAIProjectAgentProvider.close method."""
with patch("agent_framework_azure_ai._project_provider.AIProjectClient") as mock_ai_project_client:
mock_client = MagicMock()
mock_client.close = AsyncMock()
mock_ai_project_client.return_value = mock_client
with patch("agent_framework_azure_ai._project_provider.load_settings") as mock_load_settings:
mock_load_settings.return_value = {
"project_endpoint": "https://test.com",
"model_deployment_name": "test-model",
}
provider = AzureAIProjectAgentProvider(credential=MagicMock())
await provider.close()
mock_client.close.assert_called_once()
def test_create_text_format_config_sets_strict_for_pydantic_models() -> None:
"""Test that create_text_format_config sets strict=True for Pydantic models."""
from pydantic import BaseModel
from agent_framework_azure_ai._shared import create_text_format_config
class TestSchema(BaseModel):
subject: str
summary: str
result = create_text_format_config(TestSchema)
# Verify strict=True is set
assert result["strict"] is True
assert result["name"] == "TestSchema"
assert "schema" in result
class MockMCPTool(MCPTool): # pyright: ignore[reportGeneralTypeIssues]
"""A mock MCPTool subclass for testing that passes isinstance checks.
Note: This intentionally does NOT call super().__init__() because MCPTool's
constructor requires MCP server connection parameters that aren't needed for
unit testing. We only need isinstance(obj, MCPTool) to return True.
"""
def __init__(self, functions: list[FunctionTool] | None = None) -> None:
self.name = "MockMCPTool"
self.description = "A mock MCP tool for testing"
self.is_connected = False
self._mock_functions = functions or []
self._connect_called = False
@property
def functions(self) -> list[FunctionTool]:
return self._mock_functions
async def connect(self, *, reset: bool = False) -> None:
self._connect_called = True
self.is_connected = True
@pytest.fixture
def mock_mcp_tool() -> MockMCPTool:
"""Fixture that provides a mock MCPTool."""
mock_functions = [
create_mock_ai_function("mcp_function_1", "First MCP function"),
create_mock_ai_function("mcp_function_2", "Second MCP function"),
]
return MockMCPTool(functions=mock_functions)
def create_mock_ai_function(name: str, description: str = "A mock function") -> FunctionTool:
"""Create a real FunctionTool for testing."""
def mock_func(arg: str) -> str:
return f"Result from {name}: {arg}"
return FunctionTool(func=mock_func, name=name, description=description, approval_mode="never_require")
async def test_provider_create_agent_with_mcp_tool(
mock_project_client: MagicMock,
azure_ai_unit_test_env: dict[str, str],
mock_mcp_tool: "MockMCPTool",
) -> None:
"""Test that create_agent connects MCP tools and passes discovered functions to Azure AI."""
# Patch normalize_tools to return tools as-is in a list (avoids callable check)
def mock_normalize_tools(tools):
if tools is None:
return []
if isinstance(tools, list):
return tools
return [tools]
with (
patch("agent_framework_azure_ai._project_provider.load_settings") as mock_load_settings,
patch("agent_framework_azure_ai._project_provider.to_azure_ai_tools") as mock_to_azure_tools,
patch("agent_framework_azure_ai._project_provider.normalize_tools", side_effect=mock_normalize_tools),
):
mock_load_settings.return_value = {
"project_endpoint": azure_ai_unit_test_env["AZURE_AI_PROJECT_ENDPOINT"],
"model_deployment_name": azure_ai_unit_test_env["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
}
mock_to_azure_tools.return_value = [{"type": "function", "name": "mcp_function_1"}]
provider = AzureAIProjectAgentProvider(project_client=mock_project_client)
# Mock agent creation response
mock_agent_version = MagicMock(spec=AgentVersionDetails)
mock_agent_version.id = "agent-id"
mock_agent_version.name = "test-agent"
mock_agent_version.version = "1.0"
mock_agent_version.description = "Test Agent"
mock_agent_version.definition = MagicMock(spec=PromptAgentDefinition)
mock_agent_version.definition.model = "gpt-4"
mock_agent_version.definition.instructions = "Test instructions"
mock_agent_version.definition.tools = []
mock_project_client.agents.create_version = AsyncMock(return_value=mock_agent_version)
# Call create_agent with MCP tool
await provider.create_agent(
name="test-agent",
model="gpt-4",
instructions="Test instructions",
tools=mock_mcp_tool,
)
# Verify MCP tool was connected
assert mock_mcp_tool._connect_called is True
assert mock_mcp_tool.is_connected is True
# Verify to_azure_ai_tools was called with the discovered MCP functions
mock_to_azure_tools.assert_called_once()
tools_passed = mock_to_azure_tools.call_args[0][0]
assert len(tools_passed) == 2
assert tools_passed[0].name == "mcp_function_1"
assert tools_passed[1].name == "mcp_function_2"
async def test_provider_create_agent_with_mcp_and_regular_tools(
mock_project_client: MagicMock,
azure_ai_unit_test_env: dict[str, str],
mock_mcp_tool: "MockMCPTool",
) -> None:
"""Test that create_agent handles both MCP tools and regular FunctionTools."""
# Create a regular FunctionTool
regular_function = create_mock_ai_function("regular_function", "A regular function")
# Patch normalize_tools to return tools as-is in a list (avoids callable check)
def mock_normalize_tools(tools):
if tools is None:
return []
if isinstance(tools, list):
return tools
return [tools]
with (
patch("agent_framework_azure_ai._project_provider.load_settings") as mock_load_settings,
patch("agent_framework_azure_ai._project_provider.to_azure_ai_tools") as mock_to_azure_tools,
patch("agent_framework_azure_ai._project_provider.normalize_tools", side_effect=mock_normalize_tools),
):
mock_load_settings.return_value = {
"project_endpoint": azure_ai_unit_test_env["AZURE_AI_PROJECT_ENDPOINT"],
"model_deployment_name": azure_ai_unit_test_env["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
}
mock_to_azure_tools.return_value = []
provider = AzureAIProjectAgentProvider(project_client=mock_project_client)
# Mock agent creation response
mock_agent_version = MagicMock(spec=AgentVersionDetails)
mock_agent_version.id = "agent-id"
mock_agent_version.name = "test-agent"
mock_agent_version.version = "1.0"
mock_agent_version.description = None
mock_agent_version.definition = MagicMock(spec=PromptAgentDefinition)
mock_agent_version.definition.model = "gpt-4"
mock_agent_version.definition.instructions = None
mock_agent_version.definition.tools = []
mock_project_client.agents.create_version = AsyncMock(return_value=mock_agent_version)
# Pass both MCP tool and regular function
await provider.create_agent(
name="test-agent",
model="gpt-4",
tools=[mock_mcp_tool, regular_function],
)
# Verify to_azure_ai_tools was called with:
# - The regular FunctionTool (1)
# - The 2 discovered MCP functions
mock_to_azure_tools.assert_called_once()
tools_passed = mock_to_azure_tools.call_args[0][0]
assert len(tools_passed) == 3 # 1 regular + 2 MCP functions
# Verify the regular function is in the list
tool_names = [t.name for t in tools_passed]
assert "regular_function" in tool_names
assert "mcp_function_1" in tool_names
assert "mcp_function_2" in tool_names