Python: Added rai_config to Azure AI agent creation (#3265)

* Add kwargs to create_agent method

* Added test for kwargs

* Addressed comment

* Added doc string
This commit is contained in:
Dmytro Struk
2026-01-19 04:51:27 -08:00
committed by GitHub
Unverified
parent f87e55ba33
commit 915df3b404
8 changed files with 135 additions and 13 deletions
@@ -4,7 +4,7 @@ import importlib.metadata
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
@@ -18,6 +18,7 @@ __all__ = [
"AzureAIAgentOptions",
"AzureAIAgentsProvider",
"AzureAIClient",
"AzureAIProjectAgentOptions",
"AzureAIProjectAgentProvider",
"AzureAISettings",
"__version__",
@@ -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,
@@ -20,12 +20,14 @@ from agent_framework import (
)
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
@@ -33,9 +35,6 @@ 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:
@@ -52,10 +51,18 @@ else:
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,
)
@@ -397,6 +404,7 @@ class AzureAIClient(OpenAIBaseResponsesClient[TAzureAIClientOptions], Generic[TA
"model",
"tools",
"response_format",
"rai_config",
"temperature",
"top_p",
"text",
@@ -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,
@@ -26,12 +26,9 @@ from azure.ai.projects.models import (
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:
@@ -46,7 +43,7 @@ logger = get_logger("agent_framework.azure")
TOptions_co = TypeVar(
"TOptions_co",
bound=TypedDict, # type: ignore[valid-type]
default="OpenAIResponsesOptions",
default="AzureAIProjectAgentOptions",
covariant=True,
)
@@ -193,9 +190,10 @@ class AzureAIProjectAgentProvider(Generic[TOptions_co]):
"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}
@@ -205,6 +203,8 @@ class AzureAIProjectAgentProvider(Generic[TOptions_co]):
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)
@@ -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)
@@ -9,6 +9,7 @@ _IMPORTS: dict[str, tuple[str, str]] = {
"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"),
@@ -4,6 +4,7 @@ from agent_framework_azure_ai import (
AzureAIAgentClient,
AzureAIAgentsProvider,
AzureAIClient,
AzureAIProjectAgentOptions,
AzureAIProjectAgentProvider,
AzureAISettings,
)
@@ -28,6 +29,7 @@ __all__ = [
"AzureAIAgentClient",
"AzureAIAgentsProvider",
"AzureAIClient",
"AzureAIProjectAgentOptions",
"AzureAIProjectAgentProvider",
"AzureAISearchContextProvider",
"AzureAISearchSettings",
@@ -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. |
@@ -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())