mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Python: Add declarative workflow runtime (#2815)
* Further support for declarative python workflows * Add tests. Clean up for typing and formatting * Improvements and cleanup * Typing cleanup. Improve docstrings * Proper code in docstrings * Fix malformed code-block directive in docstring * Remove dead links * PR feedback * Address PR feedback * Address PR feedback * Remove sl * Update devui frontend * More cleanup * Fix uv lock * Skip Py 3.14 tests as powerfx doesn't support it * Fix mypy error * Fix for tool calls * Removed stale docstring * Fix lint * Standardize on .NET namespaces. Revert DevUI changes (bring in later) * Implement remaining items for Python declarative support to match dotnet
This commit is contained in:
committed by
GitHub
Unverified
parent
b2893fbc00
commit
9c094573e8
@@ -39,6 +39,13 @@ from agent_framework_declarative._models import (
|
||||
|
||||
pytestmark = pytest.mark.skipif(sys.version_info >= (3, 14), reason="Skipping on Python 3.14+")
|
||||
|
||||
try:
|
||||
import powerfx # noqa: F401
|
||||
|
||||
_powerfx_available = True
|
||||
except (ImportError, RuntimeError):
|
||||
_powerfx_available = False
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"yaml_content,expected_type,expected_attributes",
|
||||
@@ -456,6 +463,98 @@ def test_agent_schema_dispatch_agent_samples(yaml_file: Path, agent_samples_dir:
|
||||
assert result is not None, f"agent_schema_dispatch returned None for {yaml_file.relative_to(agent_samples_dir)}"
|
||||
|
||||
|
||||
class TestAgentFactoryCreateFromDict:
|
||||
"""Tests for AgentFactory.create_agent_from_dict method."""
|
||||
|
||||
def test_create_agent_from_dict_parses_prompt_agent(self):
|
||||
"""Test that create_agent_from_dict correctly parses a PromptAgent definition."""
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from agent_framework_declarative import AgentFactory
|
||||
|
||||
agent_def = {
|
||||
"kind": "Prompt",
|
||||
"name": "TestAgent",
|
||||
"description": "A test agent",
|
||||
"instructions": "You are a helpful assistant.",
|
||||
}
|
||||
|
||||
# Use a pre-configured chat client to avoid needing model
|
||||
mock_client = MagicMock()
|
||||
mock_client.create_agent.return_value = MagicMock()
|
||||
|
||||
factory = AgentFactory(chat_client=mock_client)
|
||||
agent = factory.create_agent_from_dict(agent_def)
|
||||
|
||||
assert agent is not None
|
||||
|
||||
def test_create_agent_from_dict_matches_yaml(self):
|
||||
"""Test that create_agent_from_dict produces same result as create_agent_from_yaml."""
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from agent_framework_declarative import AgentFactory
|
||||
|
||||
yaml_content = """
|
||||
kind: Prompt
|
||||
name: TestAgent
|
||||
description: A test agent
|
||||
instructions: You are a helpful assistant.
|
||||
"""
|
||||
|
||||
agent_def = {
|
||||
"kind": "Prompt",
|
||||
"name": "TestAgent",
|
||||
"description": "A test agent",
|
||||
"instructions": "You are a helpful assistant.",
|
||||
}
|
||||
|
||||
# Use a pre-configured chat client to avoid needing model
|
||||
mock_client = MagicMock()
|
||||
mock_client.create_agent.return_value = MagicMock()
|
||||
|
||||
factory = AgentFactory(chat_client=mock_client)
|
||||
|
||||
# Create from YAML string
|
||||
agent_from_yaml = factory.create_agent_from_yaml(yaml_content)
|
||||
|
||||
# Create from dict
|
||||
agent_from_dict = factory.create_agent_from_dict(agent_def)
|
||||
|
||||
# Both should produce agents with same name
|
||||
assert agent_from_yaml.name == agent_from_dict.name
|
||||
assert agent_from_yaml.description == agent_from_dict.description
|
||||
|
||||
def test_create_agent_from_dict_invalid_kind_raises(self):
|
||||
"""Test that non-PromptAgent kind raises DeclarativeLoaderError."""
|
||||
from agent_framework_declarative import AgentFactory
|
||||
from agent_framework_declarative._loader import DeclarativeLoaderError
|
||||
|
||||
# Resource kind (not PromptAgent)
|
||||
agent_def = {
|
||||
"kind": "Resource",
|
||||
"name": "TestResource",
|
||||
}
|
||||
|
||||
factory = AgentFactory()
|
||||
with pytest.raises(DeclarativeLoaderError, match="Only definitions for a PromptAgent are supported"):
|
||||
factory.create_agent_from_dict(agent_def)
|
||||
|
||||
def test_create_agent_from_dict_without_model_or_client_raises(self):
|
||||
"""Test that missing both model and chat_client raises DeclarativeLoaderError."""
|
||||
from agent_framework_declarative import AgentFactory
|
||||
from agent_framework_declarative._loader import DeclarativeLoaderError
|
||||
|
||||
agent_def = {
|
||||
"kind": "Prompt",
|
||||
"name": "TestAgent",
|
||||
"instructions": "You are helpful.",
|
||||
}
|
||||
|
||||
factory = AgentFactory()
|
||||
with pytest.raises(DeclarativeLoaderError, match="ChatClient must be provided"):
|
||||
factory.create_agent_from_dict(agent_def)
|
||||
|
||||
|
||||
class TestAgentFactorySafeMode:
|
||||
"""Tests for AgentFactory safe_mode parameter."""
|
||||
|
||||
@@ -499,6 +598,7 @@ instructions: Hello world
|
||||
# The description should NOT be resolved from env (PowerFx fails, returns original)
|
||||
assert agent.description == "=Env.TEST_DESCRIPTION"
|
||||
|
||||
@pytest.mark.skipif(not _powerfx_available, reason="PowerFx engine not available")
|
||||
def test_agent_factory_safe_mode_false_allows_env_in_yaml(self, monkeypatch):
|
||||
"""Test that safe_mode=False allows environment variable access in YAML parsing."""
|
||||
from unittest.mock import MagicMock
|
||||
@@ -558,6 +658,7 @@ model:
|
||||
finally:
|
||||
_safe_mode_context.reset(token)
|
||||
|
||||
@pytest.mark.skipif(not _powerfx_available, reason="PowerFx engine not available")
|
||||
def test_agent_factory_safe_mode_false_resolves_api_key(self, monkeypatch):
|
||||
"""Test safe_mode=False resolves API key from environment."""
|
||||
from agent_framework_declarative._models import _safe_mode_context
|
||||
|
||||
Reference in New Issue
Block a user