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 @@ -358,6 +358,7 @@ def _to_chat_agent_from_details(
agent_name=details.name,
agent_version=details.version,
agent_description=details.description,
model_deployment_name=details.definition.model,
)

# Merge tools: hosted tools from definition + user-provided function tools
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -501,6 +501,7 @@ def create_text_format_config(
return ResponseTextFormatConfigurationJsonSchema(
name=response_format.__name__,
schema=schema,
strict=True,
)

if isinstance(response_format, Mapping):
Expand Down
36 changes: 32 additions & 4 deletions python/packages/azure-ai/tests/test_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -317,11 +317,21 @@ def test_provider_as_agent(mock_project_client: MagicMock) -> None:
mock_agent_version.definition.top_p = 0.9
mock_agent_version.definition.tools = []

agent = provider.as_agent(mock_agent_version)
with patch("agent_framework_azure_ai._project_provider.AzureAIClient") as mock_azure_ai_client:
agent = provider.as_agent(mock_agent_version)

assert isinstance(agent, ChatAgent)
assert agent.name == "test-agent"
assert agent.description == "Test Agent"
assert isinstance(agent, ChatAgent)
assert agent.name == "test-agent"
assert agent.description == "Test Agent"

# Verify AzureAIClient was called with correct parameters
mock_azure_ai_client.assert_called_once()
call_kwargs = mock_azure_ai_client.call_args[1]
assert call_kwargs["project_client"] is mock_project_client
assert call_kwargs["agent_name"] == "test-agent"
assert call_kwargs["agent_version"] == "1.0"
assert call_kwargs["agent_description"] == "Test Agent"
assert call_kwargs["model_deployment_name"] == "gpt-4"


async def test_provider_context_manager(mock_project_client: MagicMock) -> None:
Expand Down Expand Up @@ -370,6 +380,24 @@ async def test_provider_close_method(mock_project_client: MagicMock) -> None:
mock_client.close.assert_called_once()


def test_create_text_format_config_sets_strict_for_pydantic_models() -> None:
"""Test that create_text_format_config sets strict=True for Pydantic models."""
from pydantic import BaseModel

from agent_framework_azure_ai._shared import create_text_format_config

class TestSchema(BaseModel):
subject: str
summary: str

result = create_text_format_config(TestSchema)

# Verify strict=True is set
assert result["strict"] is True
assert result["name"] == "TestSchema"
assert "schema" in result


@pytest.mark.flaky
@skip_if_azure_ai_integration_tests_disabled
async def test_provider_create_and_get_agent_integration() -> None:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -660,6 +660,7 @@ def _prepare_options(
"json_schema": {
"name": response_format.__name__,
"schema": response_format.model_json_schema(),
"strict": True,
},
}

Expand Down
21 changes: 21 additions & 0 deletions python/packages/core/tests/openai/test_openai_assistants_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -784,6 +784,27 @@ def test_prepare_options_with_mapping_tool(mock_async_openai: MagicMock) -> None
assert run_options["tool_choice"] == "auto"


def test_prepare_options_with_pydantic_response_format(mock_async_openai: MagicMock) -> None:
"""Test _prepare_options sets strict=True for Pydantic response_format."""
from pydantic import BaseModel, ConfigDict

class TestResponse(BaseModel):
name: str
value: int
model_config = ConfigDict(extra="forbid")

chat_client = create_test_openai_assistants_client(mock_async_openai)
messages = [ChatMessage(role=Role.USER, text="Test")]
options = {"response_format": TestResponse}

run_options, _ = chat_client._prepare_options(messages, options) # type: ignore

assert "response_format" in run_options
assert run_options["response_format"]["type"] == "json_schema"
assert run_options["response_format"]["json_schema"]["name"] == "TestResponse"
assert run_options["response_format"]["json_schema"]["strict"] is True


def test_prepare_options_with_system_message(mock_async_openai: MagicMock) -> None:
"""Test _prepare_options with system message converted to instructions."""
chat_client = create_test_openai_assistants_client(mock_async_openai)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ async def main() -> None:
agent = await provider.create_agent(
name="ProductMarketerAgent",
instructions="Return launch briefs as structured JSON.",
# Specify type to use as response
# Specify Pydantic model for structured output via default_options
default_options={"response_format": ReleaseBrief},
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,17 +37,19 @@ async def main() -> None:
AzureCliCredential() as credential,
AzureAIProjectAgentProvider(credential=credential) as provider,
):
# Pass response_format at agent creation time using dict schema format
# Pass response_format via default_options using dict schema format
agent = await provider.create_agent(
name="WeatherDigestAgent",
instructions="Return sample weather digest as structured JSON.",
response_format={
"type": "json_schema",
"json_schema": {
"name": runtime_schema["title"],
"strict": True,
"schema": runtime_schema,
},
default_options={
"response_format": {
"type": "json_schema",
"json_schema": {
"name": runtime_schema["title"],
"strict": True,
"schema": runtime_schema,
},
}
},
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@
"""
Azure AI Agent Provider Response Format Example

This sample demonstrates using AzureAIAgentsProvider with default_options
containing response_format for structured outputs.
This sample demonstrates using AzureAIAgentsProvider with response_format
for structured outputs in two ways:
1. Setting default response_format at agent creation time (default_options)
2. Overriding response_format at runtime (options parameter in agent.run)
"""


Expand All @@ -24,31 +26,57 @@ class WeatherInfo(BaseModel):
model_config = ConfigDict(extra="forbid")


class CityInfo(BaseModel):
"""Structured city information."""

city_name: str
population: int
country: str
model_config = ConfigDict(extra="forbid")


async def main() -> None:
"""Example of using default_options with response_format in AzureAIAgentsProvider."""
"""Example of using response_format at creation time and runtime."""

async with (
AzureCliCredential() as credential,
AzureAIAgentsProvider(credential=credential) as provider,
):
# Create agent with default response_format (WeatherInfo)
agent = await provider.create_agent(
name="WeatherReporter",
instructions="You provide weather reports in structured JSON format.",
name="StructuredReporter",
instructions="Return structured JSON based on the requested format.",
default_options={"response_format": WeatherInfo},
)

query = "What's the weather like in Paris today?"
print(f"User: {query}")
# Request 1: Uses default response_format from agent creation
print("--- Request 1: Using default response_format (WeatherInfo) ---")
query1 = "What's the weather like in Paris today?"
print(f"User: {query1}")

result1 = await agent.run(query1)

if isinstance(result1.value, WeatherInfo):
weather = result1.value
print("Agent:")
print(f" Location: {weather.location}")
print(f" Temperature: {weather.temperature}")
print(f" Conditions: {weather.conditions}")
print(f" Recommendation: {weather.recommendation}")

# Request 2: Override response_format at runtime with CityInfo
print("\n--- Request 2: Runtime override with CityInfo ---")
query2 = "Tell me about Tokyo."
print(f"User: {query2}")

result = await agent.run(query)
result2 = await agent.run(query2, options={"response_format": CityInfo})

if isinstance(result.value, WeatherInfo):
weather = result.value
if isinstance(result2.value, CityInfo):
city = result2.value
print("Agent:")
print(f"Location: {weather.location}")
print(f"Temperature: {weather.temperature}")
print(f"Conditions: {weather.conditions}")
print(f"Recommendation: {weather.recommendation}")
print(f" City: {city.city_name}")
print(f" Population: {city.population}")
print(f" Country: {city.country}")


if __name__ == "__main__":
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@
"""
OpenAI Assistant Provider Response Format Example

This sample demonstrates using OpenAIAssistantProvider with default_options
containing response_format for structured outputs.
This sample demonstrates using OpenAIAssistantProvider with response_format
for structured outputs in two ways:
1. Setting default response_format at agent creation time (default_options)
2. Overriding response_format at runtime (options parameter in agent.run)
"""


Expand All @@ -25,33 +27,59 @@ class WeatherInfo(BaseModel):
model_config = ConfigDict(extra="forbid")


class CityInfo(BaseModel):
"""Structured city information."""

city_name: str
population: int
country: str
model_config = ConfigDict(extra="forbid")


async def main() -> None:
"""Example of using default_options with response_format in OpenAIAssistantProvider."""
"""Example of using response_format at creation time and runtime."""

async with (
AsyncOpenAI() as client,
OpenAIAssistantProvider(client) as provider,
):
# Create agent with default response_format (WeatherInfo)
agent = await provider.create_agent(
name="WeatherReporter",
name="StructuredReporter",
model=os.environ.get("OPENAI_CHAT_MODEL_ID", "gpt-4"),
instructions="You provide weather reports in structured JSON format.",
instructions="Return structured JSON based on the requested format.",
default_options={"response_format": WeatherInfo},
)

try:
query = "What's the weather like in Paris today?"
print(f"User: {query}")
# Request 1: Uses default response_format from agent creation
print("--- Request 1: Using default response_format (WeatherInfo) ---")
query1 = "What's the weather like in Paris today?"
print(f"User: {query1}")

result = await agent.run(query)
result1 = await agent.run(query1)

if isinstance(result.value, WeatherInfo):
weather = result.value
if isinstance(result1.value, WeatherInfo):
weather = result1.value
print("Agent:")
print(f" Location: {weather.location}")
print(f" Temperature: {weather.temperature}")
print(f" Conditions: {weather.conditions}")
print(f" Recommendation: {weather.recommendation}")

# Request 2: Override response_format at runtime with CityInfo
print("\n--- Request 2: Runtime override with CityInfo ---")
query2 = "Tell me about Tokyo."
print(f"User: {query2}")

result2 = await agent.run(query2, options={"response_format": CityInfo})

if isinstance(result2.value, CityInfo):
city = result2.value
print("Agent:")
print(f" City: {city.city_name}")
print(f" Population: {city.population}")
print(f" Country: {city.country}")
finally:
await client.beta.assistants.delete(agent.id)

Expand Down
Loading