Skip to content

Commit c1ffdf1

Browse files
authored
Python: add(azure-ai): support reasoning config for AzureAIClient (microsoft#3403)
* add(azure-ai): support reasoning config for AzureAIClient * Update sample * Merge main * improvements * improve sample
1 parent 09461af commit c1ffdf1

5 files changed

Lines changed: 152 additions & 7 deletions

File tree

python/packages/azure-ai/agent_framework_azure_ai/_client.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,7 @@
2222
from agent_framework.openai import OpenAIResponsesOptions
2323
from agent_framework.openai._responses_client import OpenAIBaseResponsesClient
2424
from azure.ai.projects.aio import AIProjectClient
25-
from azure.ai.projects.models import (
26-
MCPTool,
27-
PromptAgentDefinition,
28-
PromptAgentDefinitionText,
29-
RaiConfig,
30-
)
25+
from azure.ai.projects.models import MCPTool, PromptAgentDefinition, PromptAgentDefinitionText, RaiConfig, Reasoning
3126
from azure.core.credentials_async import AsyncTokenCredential
3227
from azure.core.exceptions import ResourceNotFoundError
3328
from pydantic import ValidationError
@@ -51,12 +46,15 @@
5146
logger = get_logger("agent_framework.azure")
5247

5348

54-
class AzureAIProjectAgentOptions(OpenAIResponsesOptions):
49+
class AzureAIProjectAgentOptions(OpenAIResponsesOptions, total=False):
5550
"""Azure AI Project Agent options."""
5651

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

55+
reasoning: Reasoning # type: ignore[misc]
56+
"""Configuration for enabling reasoning capabilities (requires azure.ai.projects.models.Reasoning)."""
57+
6058

6159
TAzureAIClientOptions = TypeVar(
6260
"TAzureAIClientOptions",
@@ -343,6 +341,10 @@ async def _get_agent_reference_or_create(
343341
args["temperature"] = run_options["temperature"]
344342
if "top_p" in run_options:
345343
args["top_p"] = run_options["top_p"]
344+
if "reasoning" in run_options:
345+
args["reasoning"] = run_options["reasoning"]
346+
if "rai_config" in run_options:
347+
args["rai_config"] = run_options["rai_config"]
346348

347349
# response_format is accessed from chat_options or additional_properties
348350
# since the base class excludes it from run_options
@@ -408,6 +410,7 @@ async def _prepare_options(
408410
"top_p",
409411
"text",
410412
"text_format",
413+
"reasoning",
411414
]
412415

413416
for property in exclude:

python/packages/azure-ai/agent_framework_azure_ai/_project_provider.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ async def create_agent(
195195
opts = dict(default_options) if default_options else {}
196196
response_format = opts.get("response_format")
197197
rai_config = opts.get("rai_config")
198+
reasoning = opts.get("reasoning")
198199

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

@@ -206,6 +207,8 @@ async def create_agent(
206207
)
207208
if rai_config:
208209
args["rai_config"] = rai_config
210+
if reasoning:
211+
args["reasoning"] = reasoning
209212

210213
# Normalize tools and separate MCP tools from other tools
211214
normalized_tools = normalize_tools(tools)

python/packages/azure-ai/tests/test_provider.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,50 @@ async def test_provider_create_agent_with_rai_config(
251251
assert definition.rai_config is mock_rai_config
252252

253253

254+
async def test_provider_create_agent_with_reasoning(
255+
mock_project_client: MagicMock,
256+
azure_ai_unit_test_env: dict[str, str],
257+
) -> None:
258+
"""Test AzureAIProjectAgentProvider.create_agent passes reasoning from default_options."""
259+
with patch("agent_framework_azure_ai._project_provider.AzureAISettings") as mock_settings:
260+
mock_settings.return_value.project_endpoint = azure_ai_unit_test_env["AZURE_AI_PROJECT_ENDPOINT"]
261+
mock_settings.return_value.model_deployment_name = azure_ai_unit_test_env["AZURE_AI_MODEL_DEPLOYMENT_NAME"]
262+
263+
provider = AzureAIProjectAgentProvider(project_client=mock_project_client)
264+
265+
# Mock agent creation response
266+
mock_agent_version = MagicMock(spec=AgentVersionDetails)
267+
mock_agent_version.id = "agent-id"
268+
mock_agent_version.name = "test-agent"
269+
mock_agent_version.version = "1.0"
270+
mock_agent_version.description = None
271+
mock_agent_version.definition = MagicMock(spec=PromptAgentDefinition)
272+
mock_agent_version.definition.model = "gpt-5.2"
273+
mock_agent_version.definition.instructions = None
274+
mock_agent_version.definition.temperature = None
275+
mock_agent_version.definition.top_p = None
276+
mock_agent_version.definition.tools = []
277+
278+
mock_project_client.agents.create_version = AsyncMock(return_value=mock_agent_version)
279+
280+
# Create a mock Reasoning-like object
281+
mock_reasoning = MagicMock()
282+
mock_reasoning.effort = "medium"
283+
mock_reasoning.summary = "concise"
284+
285+
# Call create_agent with reasoning in default_options
286+
await provider.create_agent(
287+
name="test-agent",
288+
model="gpt-5.2",
289+
default_options={"reasoning": mock_reasoning},
290+
)
291+
292+
# Verify reasoning was passed to PromptAgentDefinition
293+
call_args = mock_project_client.agents.create_version.call_args
294+
definition = call_args[1]["definition"]
295+
assert definition.reasoning is mock_reasoning
296+
297+
254298
async def test_provider_get_agent_with_name(mock_project_client: MagicMock) -> None:
255299
"""Test AzureAIProjectAgentProvider.get_agent with name parameter."""
256300
provider = AzureAIProjectAgentProvider(project_client=mock_project_client)

python/samples/getting_started/agents/azure_ai/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ This folder contains examples demonstrating different ways to create and use age
3636
| [`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. |
3737
| [`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. |
3838
| [`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. |
39+
| [`azure_ai_with_reasoning.py`](azure_ai_with_reasoning.py) | Shows how to enable reasoning for a model that supports it. |
3940
| [`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. |
4041

4142
## Environment Variables
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# Copyright (c) Microsoft. All rights reserved.
2+
3+
import asyncio
4+
5+
from agent_framework.azure import AzureAIProjectAgentProvider
6+
from azure.ai.projects.models import Reasoning
7+
from azure.identity.aio import AzureCliCredential
8+
9+
"""
10+
Azure AI Agent with Reasoning Example
11+
12+
Demonstrates how to enable reasoning capabilities using the Reasoning option.
13+
Shows both non-streaming and streaming approaches, including how to access
14+
reasoning content (type="text_reasoning") separately from answer content.
15+
16+
Requires a reasoning-capable model (e.g., gpt-5.2) deployed in your Azure AI Project configured
17+
as `AZURE_AI_MODEL_DEPLOYMENT_NAME` in your environment.
18+
"""
19+
20+
21+
async def non_streaming_example() -> None:
22+
"""Example of non-streaming response (get the complete result at once)."""
23+
print("=== Non-streaming Response Example ===")
24+
25+
# For authentication, run `az login` command in terminal or replace AzureCliCredential with preferred
26+
# authentication option.
27+
async with (
28+
AzureCliCredential() as credential,
29+
AzureAIProjectAgentProvider(credential=credential) as provider,
30+
):
31+
agent = await provider.create_agent(
32+
name="ReasoningWeatherAgent",
33+
instructions="You are a helpful weather agent who likes to understand the underlying physics.",
34+
default_options={"reasoning": Reasoning(effort="medium", summary="concise")},
35+
)
36+
37+
query = "How does the Bernoulli effect work?"
38+
print(f"User: {query}")
39+
result = await agent.run(query)
40+
41+
for msg in result.messages:
42+
for content in msg.contents:
43+
if content.type == "text_reasoning":
44+
print(f"[Reasoning]: {content.text}")
45+
elif content.type == "text":
46+
print(f"[Answer]: {content.text}")
47+
print()
48+
49+
50+
async def streaming_example() -> None:
51+
"""Example of streaming response (get results as they are generated)."""
52+
print("=== Streaming Response Example ===")
53+
54+
# For authentication, run `az login` command in terminal or replace AzureCliCredential with preferred
55+
# authentication option.
56+
async with (
57+
AzureCliCredential() as credential,
58+
AzureAIProjectAgentProvider(credential=credential) as provider,
59+
):
60+
agent = await provider.create_agent(
61+
name="ReasoningWeatherAgent",
62+
instructions="You are a helpful weather agent who likes to understand the underlying physics.",
63+
default_options={"reasoning": Reasoning(effort="medium", summary="concise")},
64+
)
65+
66+
query = "Help explain how air updrafts work?"
67+
print(f"User: {query}")
68+
69+
shown_reasoning_label = False
70+
shown_text_label = False
71+
async for chunk in agent.run_stream(query):
72+
for content in chunk.contents:
73+
if content.type == "text_reasoning":
74+
if not shown_reasoning_label:
75+
print("[Reasoning]: ", end="", flush=True)
76+
shown_reasoning_label = True
77+
print(content.text, end="", flush=True)
78+
elif content.type == "text":
79+
if not shown_text_label:
80+
print("\n\n[Answer]: ", end="", flush=True)
81+
shown_text_label = True
82+
print(content.text, end="", flush=True)
83+
print("\n")
84+
85+
86+
async def main() -> None:
87+
print("=== Azure AI Agent with Reasoning Example ===")
88+
89+
# await non_streaming_example()
90+
await streaming_example()
91+
92+
93+
if __name__ == "__main__":
94+
asyncio.run(main())

0 commit comments

Comments
 (0)