Skip to content

Commit 915c749

Browse files
dmytrostrukCopilotmoonbox3
authored
Python: [Feature Branch] Added use_latest_version parameter to AzureAIClient (#1959)
* Added use_latest_version parameter to AzureAIClient * Added unit tests * Update python/samples/getting_started/agents/azure_ai/azure_ai_use_latest_version.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update python/packages/azure-ai/agent_framework_azure_ai/_client.py Co-authored-by: Evan Mattson <35585003+moonbox3@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Evan Mattson <35585003+moonbox3@users.noreply.github.com>
1 parent ce4b5fe commit 915c749

3 files changed

Lines changed: 176 additions & 1 deletion

File tree

python/packages/azure-ai/agent_framework_azure_ai/_client.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ def __init__(
5858
project_endpoint: str | None = None,
5959
model_deployment_name: str | None = None,
6060
async_credential: AsyncTokenCredential | None = None,
61+
use_latest_version: bool | None = None,
6162
env_file_path: str | None = None,
6263
env_file_encoding: str | None = None,
6364
**kwargs: Any,
@@ -76,6 +77,8 @@ def __init__(
7677
model_deployment_name: The model deployment name to use for agent creation.
7778
Can also be set via environment variable AZURE_AI_MODEL_DEPLOYMENT_NAME.
7879
async_credential: Azure async credential to use for authentication.
80+
use_latest_version: Boolean flag that indicates whether to use latest agent version
81+
if it exists in the service.
7982
env_file_path: Path to environment file for loading settings.
8083
env_file_encoding: Encoding of the environment file.
8184
kwargs: Additional keyword arguments passed to the parent class.
@@ -139,6 +142,7 @@ def __init__(
139142
# Initialize instance variables
140143
self.agent_name = agent_name
141144
self.agent_version = agent_version
145+
self.use_latest_version = use_latest_version
142146
self.project_client = project_client
143147
self.credential = async_credential
144148
self.model_id = azure_ai_settings.model_deployment_name
@@ -188,8 +192,19 @@ async def _get_agent_reference_or_create(
188192
"""
189193
agent_name = self.agent_name or "UnnamedAgent"
190194

191-
# If no agent_version is provided, create a new agent
195+
# If no agent_version is provided, either use latest version or create a new agent:
192196
if self.agent_version is None:
197+
# Try to use latest version if requested and agent exists
198+
if self.use_latest_version:
199+
try:
200+
existing_agent = await self.project_client.agents.retrieve(agent_name)
201+
self.agent_name = existing_agent.name
202+
self.agent_version = existing_agent.versions.latest.version
203+
return {"name": self.agent_name, "version": self.agent_version, "type": "agent_reference"}
204+
except ResourceNotFoundError:
205+
# Agent doesn't exist, fall through to creation logic
206+
pass
207+
193208
if "model" not in run_options or not run_options["model"]:
194209
raise ServiceInitializationError(
195210
"Model deployment name is required for agent creation, "

python/packages/azure-ai/tests/test_azure_ai_client.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ def create_test_azure_ai_client(
2323
conversation_id: str | None = None,
2424
azure_ai_settings: AzureAISettings | None = None,
2525
should_close_client: bool = False,
26+
use_latest_version: bool | None = None,
2627
) -> AzureAIClient:
2728
"""Helper function to create AzureAIClient instances for testing, bypassing normal validation."""
2829
if azure_ai_settings is None:
@@ -36,6 +37,7 @@ def create_test_azure_ai_client(
3637
client.credential = None
3738
client.agent_name = agent_name
3839
client.agent_version = agent_version
40+
client.use_latest_version = use_latest_version
3941
client.model_id = azure_ai_settings.model_deployment_name
4042
client.conversation_id = conversation_id
4143
client._should_close_client = should_close_client # type: ignore
@@ -437,6 +439,98 @@ async def test_azure_ai_client_agent_creation_with_tools(
437439
assert call_args[1]["definition"].tools == test_tools
438440

439441

442+
async def test_azure_ai_client_use_latest_version_existing_agent(
443+
mock_project_client: MagicMock,
444+
) -> None:
445+
"""Test _get_agent_reference_or_create when use_latest_version=True and agent exists."""
446+
client = create_test_azure_ai_client(mock_project_client, agent_name="existing-agent", use_latest_version=True)
447+
448+
# Mock existing agent response
449+
mock_existing_agent = MagicMock()
450+
mock_existing_agent.name = "existing-agent"
451+
mock_existing_agent.versions.latest.version = "2.5"
452+
mock_project_client.agents.retrieve = AsyncMock(return_value=mock_existing_agent)
453+
454+
run_options = {"model": "test-model"}
455+
agent_ref = await client._get_agent_reference_or_create(run_options, None) # type: ignore
456+
457+
# Verify existing agent was retrieved and used
458+
mock_project_client.agents.retrieve.assert_called_once_with("existing-agent")
459+
mock_project_client.agents.create_version.assert_not_called()
460+
461+
assert agent_ref == {"name": "existing-agent", "version": "2.5", "type": "agent_reference"}
462+
assert client.agent_name == "existing-agent"
463+
assert client.agent_version == "2.5"
464+
465+
466+
async def test_azure_ai_client_use_latest_version_agent_not_found(
467+
mock_project_client: MagicMock,
468+
) -> None:
469+
"""Test _get_agent_reference_or_create when use_latest_version=True but agent doesn't exist."""
470+
from azure.core.exceptions import ResourceNotFoundError
471+
472+
client = create_test_azure_ai_client(mock_project_client, agent_name="non-existing-agent", use_latest_version=True)
473+
474+
# Mock ResourceNotFoundError when trying to retrieve agent
475+
mock_project_client.agents.retrieve = AsyncMock(side_effect=ResourceNotFoundError("Agent not found"))
476+
477+
# Mock agent creation response for fallback
478+
mock_created_agent = MagicMock()
479+
mock_created_agent.name = "non-existing-agent"
480+
mock_created_agent.version = "1.0"
481+
mock_project_client.agents.create_version = AsyncMock(return_value=mock_created_agent)
482+
483+
run_options = {"model": "test-model"}
484+
agent_ref = await client._get_agent_reference_or_create(run_options, None) # type: ignore
485+
486+
# Verify retrieval was attempted and creation was used as fallback
487+
mock_project_client.agents.retrieve.assert_called_once_with("non-existing-agent")
488+
mock_project_client.agents.create_version.assert_called_once()
489+
490+
assert agent_ref == {"name": "non-existing-agent", "version": "1.0", "type": "agent_reference"}
491+
assert client.agent_name == "non-existing-agent"
492+
assert client.agent_version == "1.0"
493+
494+
495+
async def test_azure_ai_client_use_latest_version_false(
496+
mock_project_client: MagicMock,
497+
) -> None:
498+
"""Test _get_agent_reference_or_create when use_latest_version=False (default behavior)."""
499+
client = create_test_azure_ai_client(mock_project_client, agent_name="test-agent", use_latest_version=False)
500+
501+
# Mock agent creation response
502+
mock_created_agent = MagicMock()
503+
mock_created_agent.name = "test-agent"
504+
mock_created_agent.version = "1.0"
505+
mock_project_client.agents.create_version = AsyncMock(return_value=mock_created_agent)
506+
507+
run_options = {"model": "test-model"}
508+
agent_ref = await client._get_agent_reference_or_create(run_options, None) # type: ignore
509+
510+
# Verify retrieval was not attempted and creation was used directly
511+
mock_project_client.agents.retrieve.assert_not_called()
512+
mock_project_client.agents.create_version.assert_called_once()
513+
514+
assert agent_ref == {"name": "test-agent", "version": "1.0", "type": "agent_reference"}
515+
516+
517+
async def test_azure_ai_client_use_latest_version_with_existing_agent_version(
518+
mock_project_client: MagicMock,
519+
) -> None:
520+
"""Test that use_latest_version is ignored when agent_version is already provided."""
521+
client = create_test_azure_ai_client(
522+
mock_project_client, agent_name="test-agent", agent_version="3.0", use_latest_version=True
523+
)
524+
525+
agent_ref = await client._get_agent_reference_or_create({}, None) # type: ignore
526+
527+
# Verify neither retrieval nor creation was attempted since version is already set
528+
mock_project_client.agents.retrieve.assert_not_called()
529+
mock_project_client.agents.create_version.assert_not_called()
530+
531+
assert agent_ref == {"name": "test-agent", "version": "3.0", "type": "agent_reference"}
532+
533+
440534
@pytest.fixture
441535
def mock_project_client() -> MagicMock:
442536
"""Fixture that provides a mock AIProjectClient."""
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# Copyright (c) Microsoft. All rights reserved.
2+
3+
import asyncio
4+
from random import randint
5+
from typing import Annotated
6+
7+
from agent_framework.azure import AzureAIClient
8+
from azure.identity.aio import AzureCliCredential
9+
from pydantic import Field
10+
11+
"""
12+
Azure AI Agent Basic Example
13+
14+
This sample demonstrates how to reuse the latest version of an existing agent instead of creating a new agent version on each instantiation.
15+
The first call creates a new agent, while subsequent calls with `use_latest_version=True` reuse the latest agent version.
16+
"""
17+
18+
19+
def get_weather(
20+
location: Annotated[str, Field(description="The location to get the weather for.")],
21+
) -> str:
22+
"""Get the weather for a given location."""
23+
conditions = ["sunny", "cloudy", "rainy", "stormy"]
24+
return f"The weather in {location} is {conditions[randint(0, 3)]} with a high of {randint(10, 30)}°C."
25+
26+
27+
async def main() -> None:
28+
# For authentication, run `az login` command in terminal or replace AzureCliCredential with preferred
29+
# authentication option.
30+
async with AzureCliCredential() as credential:
31+
async with (
32+
AzureAIClient(
33+
async_credential=credential,
34+
).create_agent(
35+
name="MyWeatherAgent",
36+
instructions="You are a helpful weather agent.",
37+
tools=get_weather,
38+
) as agent,
39+
):
40+
# First query will create a new agent
41+
query = "What's the weather like in Seattle?"
42+
print(f"User: {query}")
43+
result = await agent.run(query)
44+
print(f"Agent: {result}\n")
45+
46+
# Create a new agent instance
47+
async with (
48+
AzureAIClient(
49+
async_credential=credential,
50+
# This parameter will allow to re-use latest agent version
51+
# instead of creating a new one
52+
use_latest_version=True,
53+
).create_agent(
54+
name="MyWeatherAgent",
55+
instructions="You are a helpful weather agent.",
56+
tools=get_weather,
57+
) as agent,
58+
):
59+
query = "What's the weather like in Tokyo?"
60+
print(f"User: {query}")
61+
result = await agent.run(query)
62+
print(f"Agent: {result}\n")
63+
64+
65+
if __name__ == "__main__":
66+
asyncio.run(main())

0 commit comments

Comments
 (0)