Create/Get Agent API - fixes and example improvements (#3246)

This commit is contained in:
Dmytro Struk
2026-01-15 20:36:34 -08:00
committed by GitHub
Unverified
parent 975884f32d
commit b773830e4b
9 changed files with 147 additions and 37 deletions
@@ -358,6 +358,7 @@ class AzureAIProjectAgentProvider(Generic[TOptions_co]):
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
@@ -501,6 +501,7 @@ def create_text_format_config(
return ResponseTextFormatConfigurationJsonSchema(
name=response_format.__name__,
schema=schema,
strict=True,
)
if isinstance(response_format, Mapping):
@@ -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:
@@ -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:
@@ -660,6 +660,7 @@ class OpenAIAssistantsClient(
"json_schema": {
"name": response_format.__name__,
"schema": response_format.model_json_schema(),
"strict": True,
},
}
@@ -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)
@@ -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},
)
@@ -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,
},
}
},
)
@@ -9,8 +9,10 @@ from pydantic import BaseModel, ConfigDict
"""
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)
"""
@@ -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}")
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}")
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}")
if __name__ == "__main__":
@@ -10,8 +10,10 @@ from pydantic import BaseModel, ConfigDict
"""
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)
"""
@@ -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)