Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from ._agent_provider import AzureAIAgentsProvider
from ._chat_client import AzureAIAgentClient, AzureAIAgentOptions
from ._client import AzureAIClient
from ._client import AzureAIClient, AzureAIProjectAgentOptions
from ._project_provider import AzureAIProjectAgentProvider
from ._shared import AzureAISettings

Expand All @@ -18,6 +18,7 @@
"AzureAIAgentOptions",
"AzureAIAgentsProvider",
"AzureAIClient",
"AzureAIProjectAgentOptions",
"AzureAIProjectAgentProvider",
"AzureAISettings",
"__version__",
Expand Down
18 changes: 13 additions & 5 deletions python/packages/azure-ai/agent_framework_azure_ai/_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import sys
from collections.abc import Callable, Mapping, MutableMapping, MutableSequence, Sequence
from typing import TYPE_CHECKING, Any, ClassVar, Generic, TypedDict, TypeVar, cast
from typing import Any, ClassVar, Generic, TypedDict, TypeVar, cast

from agent_framework import (
AGENT_FRAMEWORK_USER_AGENT,
Expand All @@ -20,22 +20,21 @@
)
from agent_framework.exceptions import ServiceInitializationError
from agent_framework.observability import use_instrumentation
from agent_framework.openai import OpenAIResponsesOptions
from agent_framework.openai._responses_client import OpenAIBaseResponsesClient
from azure.ai.projects.aio import AIProjectClient
from azure.ai.projects.models import (
MCPTool,
PromptAgentDefinition,
PromptAgentDefinitionText,
RaiConfig,
)
from azure.core.credentials_async import AsyncTokenCredential
from azure.core.exceptions import ResourceNotFoundError
from pydantic import ValidationError

from ._shared import AzureAISettings, create_text_format_config

if TYPE_CHECKING:
from agent_framework.openai import OpenAIResponsesOptions

if sys.version_info >= (3, 13):
from typing import TypeVar # type: ignore # pragma: no cover
else:
Expand All @@ -52,10 +51,18 @@

logger = get_logger("agent_framework.azure")


class AzureAIProjectAgentOptions(OpenAIResponsesOptions):
"""Azure AI Project Agent options."""

rai_config: RaiConfig
"""Configuration for Responsible AI (RAI) content filtering and safety features."""


TAzureAIClientOptions = TypeVar(
"TAzureAIClientOptions",
bound=TypedDict, # type: ignore[valid-type]
default="OpenAIResponsesOptions",
default="AzureAIProjectAgentOptions",
covariant=True,
)

Expand Down Expand Up @@ -397,6 +404,7 @@ async def _prepare_options(
"model",
"tools",
"response_format",
"rai_config",
"temperature",
"top_p",
"text",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import sys
from collections.abc import Callable, MutableMapping, Sequence
from typing import TYPE_CHECKING, Any, Generic, TypedDict
from typing import Any, Generic, TypedDict

from agent_framework import (
AGENT_FRAMEWORK_USER_AGENT,
Expand All @@ -26,12 +26,9 @@
from azure.core.credentials_async import AsyncTokenCredential
from pydantic import ValidationError

from ._client import AzureAIClient
from ._client import AzureAIClient, AzureAIProjectAgentOptions
from ._shared import AzureAISettings, create_text_format_config, from_azure_ai_tools, to_azure_ai_tools

if TYPE_CHECKING:
from agent_framework.openai import OpenAIResponsesOptions

if sys.version_info >= (3, 13):
from typing import Self, TypeVar # pragma: no cover
else:
Expand All @@ -46,7 +43,7 @@
TOptions_co = TypeVar(
"TOptions_co",
bound=TypedDict, # type: ignore[valid-type]
default="OpenAIResponsesOptions",
default="AzureAIProjectAgentOptions",
covariant=True,
)

Expand Down Expand Up @@ -193,9 +190,10 @@ async def create_agent(
"or set 'AZURE_AI_MODEL_DEPLOYMENT_NAME' environment variable."
)

# Extract response_format from default_options if present
# Extract options from default_options if present
opts = dict(default_options) if default_options else {}
response_format = opts.get("response_format")
rai_config = opts.get("rai_config")

args: dict[str, Any] = {"model": resolved_model}

Expand All @@ -205,6 +203,8 @@ async def create_agent(
args["text"] = PromptAgentDefinitionText(
format=create_text_format_config(response_format) # type: ignore[arg-type]
)
if rai_config:
args["rai_config"] = rai_config

# Normalize tools once and reuse for both Azure AI API and ChatAgent
normalized_tools = normalize_tools(tools)
Expand Down
43 changes: 43 additions & 0 deletions python/packages/azure-ai/tests/test_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,49 @@ async def test_provider_create_agent_missing_model(mock_project_client: MagicMoc
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.AzureAISettings") as mock_settings:
mock_settings.return_value.project_endpoint = azure_ai_unit_test_env["AZURE_AI_PROJECT_ENDPOINT"]
mock_settings.return_value.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_get_agent_with_name(mock_project_client: MagicMock) -> None:
"""Test AzureAIProjectAgentProvider.get_agent with name parameter."""
provider = AzureAIProjectAgentProvider(project_client=mock_project_client)
Expand Down
1 change: 1 addition & 0 deletions python/packages/core/agent_framework/azure/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"AgentResponseCallbackProtocol": ("agent_framework_azurefunctions", "agent-framework-azurefunctions"),
"AzureAIAgentClient": ("agent_framework_azure_ai", "agent-framework-azure-ai"),
"AzureAIAgentOptions": ("agent_framework_azure_ai", "agent-framework-azure-ai"),
"AzureAIProjectAgentOptions": ("agent_framework_azure_ai", "agent-framework-azure-ai"),
"AzureAIClient": ("agent_framework_azure_ai", "agent-framework-azure-ai"),
"AzureAIProjectAgentProvider": ("agent_framework_azure_ai", "agent-framework-azure-ai"),
"AzureAISearchContextProvider": ("agent_framework_azure_ai_search", "agent-framework-azure-ai-search"),
Expand Down
2 changes: 2 additions & 0 deletions python/packages/core/agent_framework/azure/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ from agent_framework_azure_ai import (
AzureAIAgentClient,
AzureAIAgentsProvider,
AzureAIClient,
AzureAIProjectAgentOptions,
AzureAIProjectAgentProvider,
AzureAISettings,
)
Expand All @@ -28,6 +29,7 @@ __all__ = [
"AzureAIAgentClient",
"AzureAIAgentsProvider",
"AzureAIClient",
"AzureAIProjectAgentOptions",
"AzureAIProjectAgentProvider",
"AzureAISearchContextProvider",
"AzureAISearchSettings",
Expand Down
1 change: 1 addition & 0 deletions python/samples/getting_started/agents/azure_ai/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ This folder contains examples demonstrating different ways to create and use age
| [`azure_ai_with_code_interpreter.py`](azure_ai_with_code_interpreter.py) | Shows how to use the `HostedCodeInterpreterTool` with Azure AI agents to write and execute Python code for mathematical problem solving and data analysis. |
| [`azure_ai_with_code_interpreter_file_generation.py`](azure_ai_with_code_interpreter_file_generation.py) | Shows how to retrieve file IDs from code interpreter generated files using both streaming and non-streaming approaches. |
| [`azure_ai_with_code_interpreter_file_download.py`](azure_ai_with_code_interpreter_file_download.py) | Shows how to download files generated by code interpreter using the OpenAI containers API. |
| [`azure_ai_with_content_filtering.py`](azure_ai_with_content_filtering.py) | Shows how to enable content filtering (RAI policy) on Azure AI agents using `RaiConfig`. Requires creating an RAI policy in Azure AI Foundry portal first. |
| [`azure_ai_with_existing_agent.py`](azure_ai_with_existing_agent.py) | Shows how to work with a pre-existing agent by providing the agent name and version to the Azure AI client. Demonstrates agent reuse patterns for production scenarios. |
| [`azure_ai_with_existing_conversation.py`](azure_ai_with_existing_conversation.py) | Demonstrates how to use an existing conversation created on the service side with Azure AI agents. Shows two approaches: specifying conversation ID at the client level and using AgentThread with an existing conversation ID. |
| [`azure_ai_with_application_endpoint.py`](azure_ai_with_application_endpoint.py) | Demonstrates calling the Azure AI application-scoped endpoint. |
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Copyright (c) Microsoft. All rights reserved.

import asyncio

from agent_framework.azure import AzureAIProjectAgentProvider
from azure.ai.projects.models import RaiConfig
from azure.identity.aio import AzureCliCredential

"""
Azure AI Agent with Content Filtering (RAI Policy) Example

This sample demonstrates how to enable content filtering on Azure AI agents using RaiConfig.

Prerequisites:
1. Create an RAI Policy in Azure AI Foundry portal:
- Go to Azure AI Foundry > Your Project > Guardrails + Controls > Content Filters
- Create a new content filter or use an existing one
- Note the policy name

2. Set environment variables:
- AZURE_AI_PROJECT_ENDPOINT: Your Azure AI Foundry project endpoint
- AZURE_AI_MODEL_DEPLOYMENT_NAME: Your model deployment name

3. Run `az login` to authenticate
"""


async def main() -> None:
print("=== Azure AI Agent with Content Filtering ===\n")

# Replace with your RAI policy from Azure AI Foundry portal
rai_policy_name = (
"/subscriptions/{subscriptionId}/resourceGroups/{resourceGroup}/providers/"
"Microsoft.CognitiveServices/accounts/{accountName}/raiPolicies/{policyName}"
)

async with (
AzureCliCredential() as credential,
AzureAIProjectAgentProvider(credential=credential) as provider,
):
# Create agent with content filtering enabled via default_options
agent = await provider.create_agent(
name="ContentFilteredAgent",
instructions="You are a helpful assistant.",
default_options={"rai_config": RaiConfig(rai_policy_name=rai_policy_name)},
)

# Test with a normal query
query = "What is the capital of France?"
print(f"User: {query}")
result = await agent.run(query)
print(f"Agent: {result}\n")

# Test with a query that might trigger content filtering
# (depending on your RAI policy configuration)
query2 = "Tell me something inappropriate."
print(f"User: {query2}")
try:
result2 = await agent.run(query2)
print(f"Agent: {result2}\n")
except Exception as e:
print(f"Content filter triggered: {e}\n")


if __name__ == "__main__":
asyncio.run(main())
Loading