Python: add(azure-ai): support reasoning config for AzureAIClient (#3403)

* add(azure-ai): support reasoning config for AzureAIClient

* Update sample

* Merge main

* improvements

* improve sample
This commit is contained in:
Evan Mattson
2026-01-23 18:37:23 +09:00
committed by GitHub
Unverified
parent a97bc322ac
commit 7b8777d1fc
5 changed files with 152 additions and 7 deletions
@@ -22,12 +22,7 @@ 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.ai.projects.models import MCPTool, PromptAgentDefinition, PromptAgentDefinitionText, RaiConfig, Reasoning
from azure.core.credentials_async import AsyncTokenCredential
from azure.core.exceptions import ResourceNotFoundError
from pydantic import ValidationError
@@ -51,12 +46,15 @@ else:
logger = get_logger("agent_framework.azure")
class AzureAIProjectAgentOptions(OpenAIResponsesOptions):
class AzureAIProjectAgentOptions(OpenAIResponsesOptions, total=False):
"""Azure AI Project Agent options."""
rai_config: RaiConfig
"""Configuration for Responsible AI (RAI) content filtering and safety features."""
reasoning: Reasoning # type: ignore[misc]
"""Configuration for enabling reasoning capabilities (requires azure.ai.projects.models.Reasoning)."""
TAzureAIClientOptions = TypeVar(
"TAzureAIClientOptions",
@@ -343,6 +341,10 @@ class AzureAIClient(OpenAIBaseResponsesClient[TAzureAIClientOptions], Generic[TA
args["temperature"] = run_options["temperature"]
if "top_p" in run_options:
args["top_p"] = run_options["top_p"]
if "reasoning" in run_options:
args["reasoning"] = run_options["reasoning"]
if "rai_config" in run_options:
args["rai_config"] = run_options["rai_config"]
# response_format is accessed from chat_options or additional_properties
# since the base class excludes it from run_options
@@ -408,6 +410,7 @@ class AzureAIClient(OpenAIBaseResponsesClient[TAzureAIClientOptions], Generic[TA
"top_p",
"text",
"text_format",
"reasoning",
]
for property in exclude:
@@ -195,6 +195,7 @@ class AzureAIProjectAgentProvider(Generic[TOptions_co]):
opts = dict(default_options) if default_options else {}
response_format = opts.get("response_format")
rai_config = opts.get("rai_config")
reasoning = opts.get("reasoning")
args: dict[str, Any] = {"model": resolved_model}
@@ -206,6 +207,8 @@ class AzureAIProjectAgentProvider(Generic[TOptions_co]):
)
if rai_config:
args["rai_config"] = rai_config
if reasoning:
args["reasoning"] = reasoning
# Normalize tools and separate MCP tools from other tools
normalized_tools = normalize_tools(tools)
@@ -251,6 +251,50 @@ async def test_provider_create_agent_with_rai_config(
assert definition.rai_config is mock_rai_config
async def test_provider_create_agent_with_reasoning(
mock_project_client: MagicMock,
azure_ai_unit_test_env: dict[str, str],
) -> None:
"""Test AzureAIProjectAgentProvider.create_agent passes reasoning 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-5.2"
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 Reasoning-like object
mock_reasoning = MagicMock()
mock_reasoning.effort = "medium"
mock_reasoning.summary = "concise"
# Call create_agent with reasoning in default_options
await provider.create_agent(
name="test-agent",
model="gpt-5.2",
default_options={"reasoning": mock_reasoning},
)
# Verify reasoning was passed to PromptAgentDefinition
call_args = mock_project_client.agents.create_version.call_args
definition = call_args[1]["definition"]
assert definition.reasoning is mock_reasoning
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)
@@ -36,6 +36,7 @@ This folder contains examples demonstrating different ways to create and use age
| [`azure_ai_with_memory_search.py`](azure_ai_with_memory_search.py) | Shows how to use memory search functionality with Azure AI agents for conversation persistence. Demonstrates creating memory stores and enabling agents to search through conversation history. |
| [`azure_ai_with_microsoft_fabric.py`](azure_ai_with_microsoft_fabric.py) | Shows how to use Microsoft Fabric with Azure AI agents to query Fabric data sources and provide responses based on data analysis. Requires a Microsoft Fabric connection configured in your Azure AI project. |
| [`azure_ai_with_openapi.py`](azure_ai_with_openapi.py) | Shows how to integrate OpenAPI specifications with Azure AI agents using dictionary-based tool configuration. Demonstrates using external REST APIs for dynamic data lookup. |
| [`azure_ai_with_reasoning.py`](azure_ai_with_reasoning.py) | Shows how to enable reasoning for a model that supports it. |
| [`azure_ai_with_web_search.py`](azure_ai_with_web_search.py) | Shows how to use the `HostedWebSearchTool` with Azure AI agents to perform web searches and retrieve up-to-date information from the internet. |
## Environment Variables
@@ -0,0 +1,94 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
from agent_framework.azure import AzureAIProjectAgentProvider
from azure.ai.projects.models import Reasoning
from azure.identity.aio import AzureCliCredential
"""
Azure AI Agent with Reasoning Example
Demonstrates how to enable reasoning capabilities using the Reasoning option.
Shows both non-streaming and streaming approaches, including how to access
reasoning content (type="text_reasoning") separately from answer content.
Requires a reasoning-capable model (e.g., gpt-5.2) deployed in your Azure AI Project configured
as `AZURE_AI_MODEL_DEPLOYMENT_NAME` in your environment.
"""
async def non_streaming_example() -> None:
"""Example of non-streaming response (get the complete result at once)."""
print("=== Non-streaming Response Example ===")
# For authentication, run `az login` command in terminal or replace AzureCliCredential with preferred
# authentication option.
async with (
AzureCliCredential() as credential,
AzureAIProjectAgentProvider(credential=credential) as provider,
):
agent = await provider.create_agent(
name="ReasoningWeatherAgent",
instructions="You are a helpful weather agent who likes to understand the underlying physics.",
default_options={"reasoning": Reasoning(effort="medium", summary="concise")},
)
query = "How does the Bernoulli effect work?"
print(f"User: {query}")
result = await agent.run(query)
for msg in result.messages:
for content in msg.contents:
if content.type == "text_reasoning":
print(f"[Reasoning]: {content.text}")
elif content.type == "text":
print(f"[Answer]: {content.text}")
print()
async def streaming_example() -> None:
"""Example of streaming response (get results as they are generated)."""
print("=== Streaming Response Example ===")
# For authentication, run `az login` command in terminal or replace AzureCliCredential with preferred
# authentication option.
async with (
AzureCliCredential() as credential,
AzureAIProjectAgentProvider(credential=credential) as provider,
):
agent = await provider.create_agent(
name="ReasoningWeatherAgent",
instructions="You are a helpful weather agent who likes to understand the underlying physics.",
default_options={"reasoning": Reasoning(effort="medium", summary="concise")},
)
query = "Help explain how air updrafts work?"
print(f"User: {query}")
shown_reasoning_label = False
shown_text_label = False
async for chunk in agent.run_stream(query):
for content in chunk.contents:
if content.type == "text_reasoning":
if not shown_reasoning_label:
print("[Reasoning]: ", end="", flush=True)
shown_reasoning_label = True
print(content.text, end="", flush=True)
elif content.type == "text":
if not shown_text_label:
print("\n\n[Answer]: ", end="", flush=True)
shown_text_label = True
print(content.text, end="", flush=True)
print("\n")
async def main() -> None:
print("=== Azure AI Agent with Reasoning Example ===")
# await non_streaming_example()
await streaming_example()
if __name__ == "__main__":
asyncio.run(main())