Python: Fix declarative package powerfx import crash and response_format kwarg error (#3841)

* Fix declarative package powerfx import crash and response_format kwarg error

* Address PR feedback. Propagate kwargs for declarative workflows

* move tests

* Fix options merge logic
This commit is contained in:
Evan Mattson
2026-02-12 07:01:21 +09:00
committed by GitHub
Unverified
parent 692fcd1888
commit ff91473912
8 changed files with 495 additions and 11 deletions
@@ -1,8 +1,10 @@
# Copyright (c) Microsoft. All rights reserved.
import builtins
import sys
from pathlib import Path
from typing import Any
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
import yaml
@@ -905,3 +907,105 @@ tools:
# Verify project_connection_id is set from connection name
assert mcp_tool.get("project_connection_id") == "my-oauth-connection"
class TestProviderResponseFormat:
"""response_format from outputSchema must be passed inside default_options."""
@staticmethod
def _make_mock_prompt_agent(*, with_output_schema: bool = False) -> MagicMock:
"""Create a mock PromptAgent to avoid serialization complexity."""
mock_model = MagicMock()
mock_model.id = "gpt-4"
mock_model.connection = None
agent = MagicMock()
agent.name = "test-agent"
agent.description = "test"
agent.instructions = "be helpful"
agent.model = mock_model
agent.tools = None
if with_output_schema:
mock_schema = MagicMock()
mock_schema.to_json_schema.return_value = {
"type": "object",
"properties": {"answer": {"type": "string"}},
}
agent.outputSchema = mock_schema
else:
agent.outputSchema = None
return agent
@staticmethod
def _make_mock_provider() -> tuple[MagicMock, AsyncMock]:
"""Create a mock provider class and its instance."""
mock_agent = MagicMock()
mock_provider_instance = AsyncMock()
mock_provider_instance.create_agent = AsyncMock(return_value=mock_agent)
mock_provider_class = MagicMock(return_value=mock_provider_instance)
return mock_provider_class, mock_provider_instance
@pytest.mark.asyncio
async def test_response_format_in_default_options(self):
"""Provider.create_agent() should receive response_format inside default_options."""
from agent_framework_declarative._loader import AgentFactory
prompt_agent = self._make_mock_prompt_agent(with_output_schema=True)
mock_provider_class, mock_provider_instance = self._make_mock_provider()
mapping = {"package": "some_module", "name": "SomeProvider"}
factory = AgentFactory()
original_import = builtins.__import__
def mock_import(name, *args, **kwargs):
if name == "some_module":
mod = MagicMock()
mod.SomeProvider = mock_provider_class
return mod
return original_import(name, *args, **kwargs)
with (
patch.object(builtins, "__import__", side_effect=mock_import),
patch.object(factory, "_parse_tools", return_value=None),
):
await factory._create_agent_with_provider(prompt_agent, mapping)
mock_provider_instance.create_agent.assert_called_once()
call_kwargs = mock_provider_instance.create_agent.call_args.kwargs
assert "response_format" not in call_kwargs
default_options = call_kwargs.get("default_options")
assert default_options is not None
assert "response_format" in default_options
@pytest.mark.asyncio
async def test_no_default_options_without_output_schema(self):
"""When there's no outputSchema, default_options should be None."""
from agent_framework_declarative._loader import AgentFactory
prompt_agent = self._make_mock_prompt_agent(with_output_schema=False)
mock_provider_class, mock_provider_instance = self._make_mock_provider()
mapping = {"package": "some_module", "name": "SomeProvider"}
factory = AgentFactory()
original_import = builtins.__import__
def mock_import(name, *args, **kwargs):
if name == "some_module":
mod = MagicMock()
mod.SomeProvider = mock_provider_class
return mod
return original_import(name, *args, **kwargs)
with (
patch.object(builtins, "__import__", side_effect=mock_import),
patch.object(factory, "_parse_tools", return_value=None),
):
await factory._create_agent_with_provider(prompt_agent, mapping)
call_kwargs = mock_provider_instance.create_agent.call_args.kwargs
assert call_kwargs.get("default_options") is None