diff --git a/sdk/ai/azure-ai-projects/.env.template b/sdk/ai/azure-ai-projects/.env.template index 8a13f3002759..a216cefb4196 100644 --- a/sdk/ai/azure-ai-projects/.env.template +++ b/sdk/ai/azure-ai-projects/.env.template @@ -65,9 +65,6 @@ AZURE_AI_PROJECTS_TESTS_AI_SEARCH_PROJECT_CONNECTION_ID= AZURE_AI_PROJECTS_TESTS_AI_SEARCH_INDEX_NAME= AZURE_AI_PROJECTS_TESTS_MCP_PROJECT_CONNECTION_ID= -# Used in Image generation agent tools tests -AZURE_AI_PROJECTS_TESTS_IMAGE_MODEL_DEPLOYMENT_NAME= - # Used in Fine-tuning tests AZURE_AI_PROJECTS_TESTS_COMPLETED_OAI_MODEL_SFT_FINE_TUNING_JOB_ID= AZURE_AI_PROJECTS_TESTS_COMPLETED_OAI_MODEL_RFT_FINE_TUNING_JOB_ID= diff --git a/sdk/ai/azure-ai-projects/README.md b/sdk/ai/azure-ai-projects/README.md index 6730b13d07f9..b54326ce3d36 100644 --- a/sdk/ai/azure-ai-projects/README.md +++ b/sdk/ai/azure-ai-projects/README.md @@ -259,7 +259,7 @@ Generate images based on text prompts with customizable resolution, quality, and ```python -tool = ImageGenTool(quality="low", size="1024x1024") +tool = ImageGenTool(model="gpt-image-1-mini", quality="low", size="1024x1024") # type: ignore ``` diff --git a/sdk/ai/azure-ai-projects/assets.json b/sdk/ai/azure-ai-projects/assets.json index 44a105b81725..db6977c7810e 100644 --- a/sdk/ai/azure-ai-projects/assets.json +++ b/sdk/ai/azure-ai-projects/assets.json @@ -2,5 +2,5 @@ "AssetsRepo": "Azure/azure-sdk-assets", "AssetsRepoPrefixPath": "python", "TagPrefix": "python/ai/azure-ai-projects", - "Tag": "python/ai/azure-ai-projects_9e7a9707f6" + "Tag": "python/ai/azure-ai-projects_3c34f28cdc" } diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_image_generation.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_image_generation.py index 439840846e3a..9f5ed9e2e0e0 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_image_generation.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_image_generation.py @@ -31,9 +31,10 @@ used by the agent for understanding and responding to prompts. This is NOT the image generation model. NOTE: - - Image generation requires a separate "gpt-image-1" deployment which is specified in the - x-ms-oai-image-generation-deployment header when creating the response. - - AZURE_AI_MODEL_DEPLOYMENT_NAME should be set to your chat model (e.g., gpt-4o), NOT "gpt-image-1". + - Image generation requires a separate "gpt-image-1-mini" deployment which is specified when constructing + the `ImageGenTool`, as well as providing it in the `x-ms-oai-image-generation-deployment` header when + calling `.responses.create`. + - AZURE_AI_MODEL_DEPLOYMENT_NAME should be set to your chat model (e.g., gpt-4o), NOT "gpt-image-1-mini". - The generated image will be saved as "microsoft.png" in the current directory. """ @@ -56,7 +57,7 @@ ): # [START tool_declaration] - tool = ImageGenTool(quality="low", size="1024x1024") + tool = ImageGenTool(model="gpt-image-1-mini", quality="low", size="1024x1024") # type: ignore # [END tool_declaration] agent = project_client.agents.create_version( @@ -73,7 +74,7 @@ response = openai_client.responses.create( input="Generate an image of Microsoft logo.", extra_headers={ - "x-ms-oai-image-generation-deployment": "gpt-image-1" + "x-ms-oai-image-generation-deployment": "gpt-image-1-mini" }, # this is required at the moment for image generation extra_body={"agent": {"name": agent.name, "type": "agent_reference"}}, ) diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_image_generation_async.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_image_generation_async.py index 61504bc48ca6..fed9b22a4656 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_image_generation_async.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_image_generation_async.py @@ -30,8 +30,11 @@ the "Models + endpoints" tab in your Microsoft Foundry project. NOTE: - - Image generation must have "gpt-image-1" deployment specified in the header when creating response at this moment - - The generated image will be saved as "microsoft.png" in the current directory + - Image generation requires a separate "gpt-image-1-mini" deployment which is specified when constructing + the `ImageGenTool`, as well as providing it in the `x-ms-oai-image-generation-deployment` header when + calling `.responses.create`. + - AZURE_AI_MODEL_DEPLOYMENT_NAME should be set to your chat model (e.g., gpt-4o), NOT "gpt-image-1-mini". + - The generated image will be saved as "microsoft.png" in the current directory. """ import asyncio @@ -58,7 +61,7 @@ async def main(): definition=PromptAgentDefinition( model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], instructions="Generate images based on user prompts", - tools=[ImageGenTool(quality="low", size="1024x1024")], + tools=[ImageGenTool(model="gpt-image-1-mini", quality="low", size="1024x1024")], # type: ignore ), description="Agent for image generation.", ) @@ -67,7 +70,7 @@ async def main(): response = await openai_client.responses.create( input="Generate an image of Microsoft logo.", extra_headers={ - "x-ms-oai-image-generation-deployment": "gpt-image-1" + "x-ms-oai-image-generation-deployment": "gpt-image-1-mini" }, # this is required at the moment for image generation extra_body={"agent": {"name": agent.name, "type": "agent_reference"}}, ) diff --git a/sdk/ai/azure-ai-projects/tests/agents/test_agents_crud.py b/sdk/ai/azure-ai-projects/tests/agents/test_agents_crud.py index d900de2dfde8..a7c36085c528 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/test_agents_crud.py +++ b/sdk/ai/azure-ai-projects/tests/agents/test_agents_crud.py @@ -13,6 +13,8 @@ class TestAgentCrud(TestBase): + # To run this test: + # pytest tests\agents\test_agents_crud.py::TestAgentCrud::test_agents_crud -s @servicePreparer() @recorded_by_proxy() def test_agents_crud(self, **kwargs): diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_image_generation.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_image_generation.py index 7a8c34ed184f..b5d1e4517440 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_image_generation.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_image_generation.py @@ -9,7 +9,7 @@ import base64 import pytest from test_base import TestBase, servicePreparer -from devtools_testutils import is_live_and_not_recording +from devtools_testutils import recorded_by_proxy, RecordedTransport from azure.ai.projects.models import PromptAgentDefinition, ImageGenTool from azure.core.exceptions import ResourceNotFoundError @@ -17,10 +17,7 @@ class TestAgentImageGeneration(TestBase): @servicePreparer() - @pytest.mark.skipif( - condition=(not is_live_and_not_recording()), - reason="Skipped because we cannot record network calls with OpenAI client", - ) + @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) def test_agent_image_generation(self, **kwargs): """ Test agent with Image Generation tool. @@ -44,12 +41,9 @@ def test_agent_image_generation(self, **kwargs): DELETE /agents/{agent_name}/versions/{agent_version} project_client.agents.delete_version() """ - # Get the image model deployment name from environment variable - image_model_deployment = os.environ.get( - "AZURE_AI_PROJECTS_TESTS_IMAGE_MODEL_DEPLOYMENT_NAME", "gpt-image-1-mini" - ) - model = self.test_agents_params["model_deployment_name"] + image_model = self.test_agents_tools_params["image_generation_model_deployment_name"] + agent_name = "image-gen-agent" with ( self.create_client(operation_group="agents", **kwargs) as project_client, @@ -57,44 +51,39 @@ def test_agent_image_generation(self, **kwargs): ): # Check if the image model deployment exists in the project try: - deployment = project_client.deployments.get(image_model_deployment) + deployment = project_client.deployments.get(image_model) print(f"Image model deployment found: {deployment.name}") except ResourceNotFoundError: - pytest.skip(f"Image generation model '{image_model_deployment}' not available in this project") + pytest.fail(f"Image generation model '{image_model}' not available in this project") except Exception as e: - pytest.skip(f"Unable to verify image model deployment: {e}") + pytest.fail(f"Unable to verify image model deployment: {e}") # Disable retries for faster failure when service returns 500 openai_client.max_retries = 0 # Create agent with image generation tool agent = project_client.agents.create_version( - agent_name="image-gen-agent", + agent_name=agent_name, definition=PromptAgentDefinition( model=model, instructions="Generate images based on user prompts", - tools=[ImageGenTool(quality="low", size="1024x1024")], + tools=[ImageGenTool(model=image_model, quality="low", size="1024x1024")], # type: ignore ), description="Agent for testing image generation.", ) - print(f"Agent created (id: {agent.id}, name: {agent.name}, version: {agent.version})") - assert agent.id is not None - assert agent.name == "image-gen-agent" - assert agent.version is not None + self._validate_agent_version(agent, expected_name=agent_name) # Request image generation print("\nAsking agent to generate an image of a simple geometric shape...") response = openai_client.responses.create( input="Generate an image of a blue circle on a white background.", - extra_headers={ - "x-ms-oai-image-generation-deployment": image_model_deployment - }, # Required for image generation + extra_headers={"x-ms-oai-image-generation-deployment": image_model}, # Required for image generation extra_body={"agent": {"name": agent.name, "type": "agent_reference"}}, ) print(f"Response created (id: {response.id})") - assert response.id is not None + assert response.id assert response.output is not None assert len(response.output) > 0 @@ -127,6 +116,12 @@ def test_agent_image_generation(self, **kwargs): print("\n✓ Agent successfully generated and returned a valid image") + # Save the image to a file in the .assets directory (which is .gitignored) + os.makedirs(".assets", exist_ok=True) + with open(".assets/generated_image_sync.png", "wb") as f: + f.write(image_bytes) + print("✓ Image saved to .assets/generated_image_sync.png") + # Teardown project_client.agents.delete_version(agent_name=agent.name, agent_version=agent.version) print("Agent deleted") diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_image_generation_async.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_image_generation_async.py new file mode 100644 index 000000000000..702bfdc63a9f --- /dev/null +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_image_generation_async.py @@ -0,0 +1,108 @@ +# pylint: disable=too-many-lines,line-too-long,useless-suppression +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +# cSpell:disable + +import os +import base64 +import pytest +from test_base import TestBase, servicePreparer +from devtools_testutils.aio import recorded_by_proxy_async +from devtools_testutils import RecordedTransport +from azure.ai.projects.models import PromptAgentDefinition, ImageGenTool +from azure.core.exceptions import ResourceNotFoundError + + +class TestAgentImageGenerationAsync(TestBase): + + @servicePreparer() + @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) + async def test_agent_image_generation_async(self, **kwargs): + + model = self.test_agents_params["model_deployment_name"] + image_model = self.test_agents_tools_params["image_generation_model_deployment_name"] + agent_name = "image-gen-agent" + + async with ( + self.create_async_client(operation_group="agents", **kwargs) as project_client, + project_client.get_openai_client() as openai_client, + ): + # Check if the image model deployment exists in the project + try: + deployment = await project_client.deployments.get(image_model) + print(f"Image model deployment found: {deployment.name}") + except ResourceNotFoundError: + pytest.fail(f"Image generation model '{image_model}' not available in this project") + except Exception as e: + pytest.fail(f"Unable to verify image model deployment: {e}") + + # Disable retries for faster failure when service returns 500 + openai_client.max_retries = 0 + + # Create agent with image generation tool + agent = await project_client.agents.create_version( + agent_name=agent_name, + definition=PromptAgentDefinition( + model=model, + instructions="Generate images based on user prompts", + tools=[ImageGenTool(model=image_model, quality="low", size="1024x1024")], # type: ignore + ), + description="Agent for testing image generation.", + ) + print(f"Agent created (id: {agent.id}, name: {agent.name}, version: {agent.version})") + self._validate_agent_version(agent, expected_name=agent_name) + + # Request image generation + print("\nAsking agent to generate an image of a simple geometric shape...") + + response = await openai_client.responses.create( + input="Generate an image of a blue circle on a white background.", + extra_headers={"x-ms-oai-image-generation-deployment": image_model}, # Required for image generation + extra_body={"agent": {"name": agent.name, "type": "agent_reference"}}, + ) + + print(f"Response created (id: {response.id})") + assert response.id + assert response.output is not None + assert len(response.output) > 0 + + # Extract image data from response + image_data = [output.result for output in response.output if output.type == "image_generation_call"] + + # Verify image was generated + assert len(image_data) > 0, "Expected at least one image to be generated" + assert image_data[0], "Expected image data to be non-empty" + + print(f"✓ Image data received ({len(image_data[0])} base64 characters)") + + # Decode the base64 image + image_bytes = b"" + try: + image_bytes = base64.b64decode(image_data[0]) + assert len(image_bytes) > 0, "Decoded image should have content" + print(f"✓ Image decoded successfully ({len(image_bytes)} bytes)") + except Exception as e: + pytest.fail(f"Failed to decode base64 image data: {e}") + + # Verify it's a PNG image (check magic bytes) + # PNG files start with: 89 50 4E 47 (‰PNG) + assert image_bytes[:4] == b"\x89PNG", "Image does not appear to be a valid PNG" + print("✓ Image is a valid PNG") + + # Verify reasonable image size (should be > 1KB for a 1024x1024 image) + assert len(image_bytes) > 1024, f"Image seems too small ({len(image_bytes)} bytes)" + print(f"✓ Image size is reasonable ({len(image_bytes):,} bytes)") + + print("\n✓ Agent successfully generated and returned a valid image") + + # Save the image to a file in the .assets directory (which is .gitignored) + os.makedirs(".assets", exist_ok=True) + with open(".assets/generated_image_async.png", "wb") as f: + f.write(image_bytes) + print("✓ Image saved to .assets/generated_image_async.png") + + # Teardown + await project_client.agents.delete_version(agent_name=agent.name, agent_version=agent.version) + print("Agent deleted") diff --git a/sdk/ai/azure-ai-projects/tests/test_base.py b/sdk/ai/azure-ai-projects/tests/test_base.py index 5e26d3cc6e1d..2dc15f2a48f9 100644 --- a/sdk/ai/azure-ai-projects/tests/test_base.py +++ b/sdk/ai/azure-ai-projects/tests/test_base.py @@ -66,7 +66,6 @@ azure_ai_projects_tests_ai_search_project_connection_id="/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/sanitized-resource-group/providers/Microsoft.CognitiveServices/accounts/sanitized-account/projects/sanitized-project/connections/sanitized-ai-search-connection", azure_ai_projects_tests_ai_search_index_name="sanitized-index-name", azure_ai_projects_tests_mcp_project_connection_id="/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/sanitized-resource-group/providers/Microsoft.CognitiveServices/accounts/sanitized-account/projects/sanitized-project/connections/sanitized-mcp-connection", - azure_ai_projects_tests_image_model_deployment_name="gpt-image-1-mini", ) # Fine-tuning job type constants @@ -105,6 +104,10 @@ class TestBase(AzureRecordedTestCase): "agent_name": "agent-for-python-projects-sdk-testing", } + test_agents_tools_params = { + "image_generation_model_deployment_name": "gpt-image-1-mini", + } + test_inference_params = { "connection_name_api_key_auth": "connection1", "connection_name_entra_id_auth": "connection2", @@ -386,11 +389,15 @@ def _validate_agent_version( ) -> None: assert agent is not None assert isinstance(agent, AgentVersionDetails) - assert agent.id is not None + assert agent.id if expected_name: assert agent.name == expected_name + else: + assert agent.name if expected_version: assert agent.version == expected_version + else: + assert agent.version print(f"Agent version validated (id: {agent.id}, name: {agent.name}, version: {agent.version})") def _validate_agent( @@ -398,11 +405,15 @@ def _validate_agent( ) -> None: assert agent is not None assert isinstance(agent, AgentDetails) - assert agent.id is not None + assert agent.id if expected_name: assert agent.name == expected_name + else: + assert agent.name if expected_latest_version: assert agent.versions.latest.version == expected_latest_version + else: + assert agent.versions.latest.version print(f"Agent validated (id: {agent.id}, name: {agent.name}, latest version: {agent.versions.latest.version})") def _validate_conversation_item(