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:
Evan Mattson
2026-01-13 16:11:21 +09:00
committed by GitHub
Unverified
parent b2893fbc00
commit 9c094573e8
79 changed files with 18310 additions and 111 deletions
@@ -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