diff --git a/python/packages/declarative/agent_framework_declarative/_workflows/_executors_basic.py b/python/packages/declarative/agent_framework_declarative/_workflows/_executors_basic.py index 34153b242a5..76b264488c8 100644 --- a/python/packages/declarative/agent_framework_declarative/_workflows/_executors_basic.py +++ b/python/packages/declarative/agent_framework_declarative/_workflows/_executors_basic.py @@ -53,7 +53,8 @@ async def handle_action( """Handle the SetValue action.""" state = await self._ensure_state_initialized(ctx, trigger) - path = self._action_def.get("path") + # The builder accepts 'variable' in place of 'path' for SetValue + path = self._action_def.get("path") or _get_variable_path(self._action_def) value = self._action_def.get("value") if path: diff --git a/python/packages/declarative/tests/test_graph_coverage.py b/python/packages/declarative/tests/test_graph_coverage.py index 5b150bf497f..7187b317f8c 100644 --- a/python/packages/declarative/tests/test_graph_coverage.py +++ b/python/packages/declarative/tests/test_graph_coverage.py @@ -451,6 +451,34 @@ async def test_set_variable_executor(self, mock_context, mock_state): result = state.get("Local.result") assert result == "test value" + @pytest.mark.parametrize("variable", ["Local.target", {"path": "Local.target"}]) + async def test_set_value_executor_with_variable_field(self, mock_context, mock_state, variable): + """SetValue accepts 'variable' in place of 'path', so it must write to it.""" + from agent_framework_declarative._workflows._executors_basic import SetValueExecutor + + state = DeclarativeWorkflowState(mock_state) + state.initialize() + + action_def = {"kind": "SetValue", "variable": variable, "value": "test value"} + executor = SetValueExecutor(action_def) + await executor.handle_action(ActionTrigger(), mock_context) + + assert state.get("Local.target") == "test value" + + async def test_set_value_executor_path_wins_over_variable(self, mock_context, mock_state): + """When SetValue has both 'path' and 'variable', 'path' is used.""" + from agent_framework_declarative._workflows._executors_basic import SetValueExecutor + + state = DeclarativeWorkflowState(mock_state) + state.initialize() + + action_def = {"kind": "SetValue", "path": "Local.a", "variable": "Local.b", "value": "v"} + executor = SetValueExecutor(action_def) + await executor.handle_action(ActionTrigger(), mock_context) + + assert state.get("Local.a") == "v" + assert state.get("Local.b") is None + async def test_set_variable_executor_with_nested_variable(self, mock_context, mock_state): """Test SetVariableExecutor with nested variable object.""" from agent_framework_declarative._workflows._executors_basic import ( diff --git a/python/packages/declarative/tests/test_workflow_factory.py b/python/packages/declarative/tests/test_workflow_factory.py index 034782a2968..46b3d11a9e1 100644 --- a/python/packages/declarative/tests/test_workflow_factory.py +++ b/python/packages/declarative/tests/test_workflow_factory.py @@ -359,6 +359,39 @@ async def test_devui_declarative_workflow_categorizes_message_input(self, age: i assert any(f"categorized as: {category}" in str(output) for output in outputs) +class TestWorkflowFactorySetValueAlias: + """SetValue's 'variable' alias through a real factory run. Literal values, so no PowerFx needed.""" + + @pytest.mark.parametrize( + "variable_yaml", + ["variable: Local.target", "variable:\n path: Local.target"], + ids=["string", "nested"], + ) + async def test_set_value_variable_alias_persists_in_checkpoint(self, variable_yaml: str) -> None: + """The builder accepts 'variable' for SetValue, so a real run must write and checkpoint it.""" + from agent_framework import InMemoryCheckpointStorage + + from agent_framework_declarative._workflows._declarative_base import DECLARATIVE_STATE_KEY + + storage = InMemoryCheckpointStorage() + workflow = WorkflowFactory(checkpoint_storage=storage).create_workflow_from_yaml(f""" +name: set-value-variable-alias +actions: + - kind: SetValue + {variable_yaml} + value: from variable +""") + await workflow.run({}) + + checkpoints = await storage.list_checkpoints(workflow_name=workflow.name) + assert checkpoints + # Early checkpoints are taken before the declarative state exists. + saved = [ + checkpoint.state.get(DECLARATIVE_STATE_KEY, {}).get("Local", {}).get("target") for checkpoint in checkpoints + ] + assert "from variable" in saved + + @_requires_powerfx class TestWorkflowFactoryExecution: """Tests for workflow execution."""