Skip to content
Merged
3 changes: 0 additions & 3 deletions sdk/ai/azure-ai-projects/.env.template
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
2 changes: 1 addition & 1 deletion sdk/ai/azure-ai-projects/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ Generate images based on text prompts with customizable resolution, quality, and
<!-- SNIPPET:sample_agent_image_generation.tool_declaration -->

```python
tool = ImageGenTool(quality="low", size="1024x1024")
tool = ImageGenTool(model="gpt-image-1-mini", quality="low", size="1024x1024") # type: ignore
```

<!-- END SNIPPET -->
Expand Down
2 changes: 1 addition & 1 deletion sdk/ai/azure-ai-projects/assets.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
"""

Expand All @@ -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(
Expand All @@ -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"}},
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.",
)
Expand All @@ -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"}},
)
Expand Down
2 changes: 2 additions & 0 deletions sdk/ai/azure-ai-projects/tests/agents/test_agents_crud.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,15 @@
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


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.
Expand All @@ -44,57 +41,49 @@ 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,
project_client.get_openai_client() as openai_client,
):
# 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

Expand Down Expand Up @@ -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")
Original file line number Diff line number Diff line change
@@ -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")
17 changes: 14 additions & 3 deletions sdk/ai/azure-ai-projects/tests/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -386,23 +389,31 @@ 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(
self, agent: AgentDetails, expected_name: Optional[str] = None, expected_latest_version: Optional[str] = None
) -> 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(
Expand Down